From d18e136e69f03efe5fd6b8e6536cf8ad9033da1a Mon Sep 17 00:00:00 2001 From: Niki Roo Date: Sun, 19 May 2019 15:24:17 +0200 Subject: [PATCH] ConfigItem upgrade --- src/be/nikiroo/utils/resources/Bundle.java | 12 +- src/be/nikiroo/utils/resources/Meta.java | 18 +- src/be/nikiroo/utils/resources/MetaInfo.java | 128 +++++++++--- src/be/nikiroo/utils/ui/ConfigEditor.java | 35 +++- src/be/nikiroo/utils/ui/ConfigItem.java | 196 ++++++++----------- 5 files changed, 231 insertions(+), 158 deletions(-) diff --git a/src/be/nikiroo/utils/resources/Bundle.java b/src/be/nikiroo/utils/resources/Bundle.java index 5932cff..85abfe7 100644 --- a/src/be/nikiroo/utils/resources/Bundle.java +++ b/src/be/nikiroo/utils/resources/Bundle.java @@ -182,7 +182,7 @@ public class Bundle> { return def; } - + /** * Set the value associated to the given id as a {@link Boolean}. * @@ -196,7 +196,6 @@ public class Bundle> { setString(id.name(), BundleHelper.fromBoolean(value)); } - /** * Return the value associated to the given id as an {@link Integer}. * @@ -240,7 +239,7 @@ public class Bundle> { public void setInteger(E id, int value) { setString(id.name(), BundleHelper.fromInteger(value)); } - + /** * Return the value associated to the given id as a {@link Character}. * @@ -505,12 +504,11 @@ public class Bundle> { String[] list = meta.list(); boolean nullable = meta.nullable(); String def = meta.def(); - String info = meta.info(); boolean array = meta.array(); // Default, empty values -> NULL - if (desc.length() + list.length + info.length() + def.length() == 0 - && !group && nullable && format == Meta.Format.STRING) { + if (desc.length() + list.length + def.length() == 0 && !group + && nullable && format == Meta.Format.STRING) { return null; } @@ -525,7 +523,7 @@ public class Bundle> { } else { builder.append(" (FORMAT: ").append(format) .append(nullable ? "" : " (required)"); - builder.append(") ").append(info); + builder.append(") "); if (list.length > 0) { builder.append("\n# ALLOWED VALUES:"); diff --git a/src/be/nikiroo/utils/resources/Meta.java b/src/be/nikiroo/utils/resources/Meta.java index f316df4..8ed74dc 100644 --- a/src/be/nikiroo/utils/resources/Meta.java +++ b/src/be/nikiroo/utils/resources/Meta.java @@ -50,8 +50,12 @@ public @interface Meta { } /** - * A description of this item (what it is or does, how to explain that item - * to the user). + * A description for this item: what it is or does, how to explain that item + * to the user including what can be used here (i.e., %s = file name, %d = + * file size...). + *

+ * For group, the first line ('\\n'-separated) will be used as a title while + * the rest will be the description. * * @return what it is */ @@ -110,13 +114,9 @@ public @interface Meta { boolean array() default false; /** - * An addition to the format. - *

- * Free info text to help translate, for instance the parameters order and - * type for String translations (i.e., %s = input file name, %d = file size - * in MB). - * - * @return some info + * @deprecated add the info into the description, as only the description + * will be translated. */ + @Deprecated String info() default ""; } diff --git a/src/be/nikiroo/utils/resources/MetaInfo.java b/src/be/nikiroo/utils/resources/MetaInfo.java index 15ff762..117eb15 100644 --- a/src/be/nikiroo/utils/resources/MetaInfo.java +++ b/src/be/nikiroo/utils/resources/MetaInfo.java @@ -62,24 +62,29 @@ public class MetaInfo> implements Iterable> { description = null; } } - if (description == null) { description = meta.description(); if (description == null) { description = ""; } - if (meta.info() != null && !meta.info().isEmpty()) { - if (!description.isEmpty()) { - description += "\n\n"; - } - description += meta.info(); + } + + String name = idToName(id, null); + + // Special rules for groups: + if (meta.group()) { + String groupName = description.split("\n")[0]; + description = description.substring(groupName.length()).trim(); + if (!groupName.isEmpty()) { + name = groupName; } } - String name = id.toString(); - if (name.length() > 1) { - name = name.substring(0, 1) + name.substring(1).toLowerCase(); - name = name.replace("_", " "); + if (meta.def() != null && !meta.def().isEmpty()) { + if (!description.isEmpty()) { + description += "\n\n"; + } + description += "(Default value: " + meta.def() + ")"; } this.name = name; @@ -89,20 +94,34 @@ public class MetaInfo> implements Iterable> { } /** - * The name of this item, deduced from its ID. + * For normal items, this is the name of this item, deduced from its ID (or + * in other words, it is the ID but presented in a displayable form). + *

+ * For group items, this is the first line of the description if it is not + * empty (else, it is the ID in the same way as normal items). *

- * In other words, it is the ID but presented in a displayable form. + * Never NULL. * - * @return the name + * + * @return the name, never NULL */ public String getName() { return name; } /** - * The description of this item (information to present to the user). + * A description for this item: what it is or does, how to explain that item + * to the user including what can be used here (i.e., %s = file name, %d = + * file size...). + *

+ * For group, the first line ('\\n'-separated) will be used as a title while + * the rest will be the description. + *

+ * If a default value is known, it will be specified here, too. + *

+ * Never NULL. * - * @return the description + * @return the description, not NULL */ public String getDescription() { return description; @@ -121,11 +140,21 @@ public class MetaInfo> implements Iterable> { * The allowed list of values that a {@link Format#FIXED_LIST} item is * allowed to be, or a list of suggestions for {@link Format#COMBO_LIST} * items. + *

+ * Will always allow an empty string in addition to the rest. * * @return the list of values */ public String[] getAllowedValues() { - return meta.list(); + String[] list = meta.list(); + + String[] withEmpty = new String[list.length + 1]; + withEmpty[0] = ""; + for (int i = 0; i < list.length; i++) { + withEmpty[i + 1] = list[i]; + } + + return withEmpty; } /** @@ -158,9 +187,17 @@ public class MetaInfo> implements Iterable> { /** * The value stored by this item, as a {@link String}. * + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * * @return the value */ - public String getString() { + public String getString(boolean useDefaultIfEmpty) { + if (value == null && useDefaultIfEmpty) { + return getDefaultString(); + } + return value; } @@ -176,10 +213,14 @@ public class MetaInfo> implements Iterable> { /** * The value stored by this item, as a {@link Boolean}. * + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * * @return the value */ - public Boolean getBoolean() { - return BundleHelper.parseBoolean(getString()); + public Boolean getBoolean(boolean useDefaultIfEmpty) { + return BundleHelper.parseBoolean(getString(useDefaultIfEmpty)); } /** @@ -194,10 +235,14 @@ public class MetaInfo> implements Iterable> { /** * The value stored by this item, as a {@link Character}. * + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * * @return the value */ - public Character getCharacter() { - return BundleHelper.parseCharacter(getString()); + public Character getCharacter(boolean useDefaultIfEmpty) { + return BundleHelper.parseCharacter(getString(useDefaultIfEmpty)); } /** @@ -212,10 +257,14 @@ public class MetaInfo> implements Iterable> { /** * The value stored by this item, as an {@link Integer}. * + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * * @return the value */ - public Integer getInteger() { - return BundleHelper.parseInteger(getString()); + public Integer getInteger(boolean useDefaultIfEmpty) { + return BundleHelper.parseInteger(getString(useDefaultIfEmpty)); } /** @@ -233,10 +282,14 @@ public class MetaInfo> implements Iterable> { *

* The returned colour value is an ARGB value. * + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * * @return the value */ - public Integer getColor() { - return BundleHelper.parseColor(getString()); + public Integer getColor(boolean useDefaultIfEmpty) { + return BundleHelper.parseColor(getString(useDefaultIfEmpty)); } /** @@ -257,10 +310,14 @@ public class MetaInfo> implements Iterable> { * The list of values is comma-separated and each value is surrounded by * double-quotes; backslashes and double-quotes are escaped by a backslash. * + * @param useDefaultIfEmpty + * use the default value instead of NULL if the setting is not + * set + * * @return the value */ - public List getList() { - return BundleHelper.parseList(getString()); + public List getList(boolean useDefaultIfEmpty) { + return BundleHelper.parseList(getString(useDefaultIfEmpty)); } /** @@ -452,6 +509,7 @@ public class MetaInfo> implements Iterable> { if (parent != null) { list.remove(i--); parent.children.add(info); + info.name = idToName(info.id, parent.id); } } @@ -495,4 +553,20 @@ public class MetaInfo> implements Iterable> { return group; } + + static private > String idToName(E id, E prefix) { + String name = id.toString(); + if (prefix != null && name.startsWith(prefix.toString())) { + name = name.substring(prefix.toString().length()); + } + + if (name.length() > 0) { + name = name.substring(0, 1).toUpperCase() + + name.substring(1).toLowerCase(); + } + + name = name.replace("_", " "); + + return name.trim(); + } } diff --git a/src/be/nikiroo/utils/ui/ConfigEditor.java b/src/be/nikiroo/utils/ui/ConfigEditor.java index b2182ad..b384c9d 100644 --- a/src/be/nikiroo/utils/ui/ConfigEditor.java +++ b/src/be/nikiroo/utils/ui/ConfigEditor.java @@ -1,6 +1,7 @@ package be.nikiroo.utils.ui; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; @@ -12,8 +13,11 @@ import javax.swing.JComponent; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; +import javax.swing.JTextArea; import javax.swing.border.EmptyBorder; +import javax.swing.border.TitledBorder; +import be.nikiroo.utils.StringUtils; import be.nikiroo.utils.resources.Bundle; import be.nikiroo.utils.resources.MetaInfo; @@ -52,12 +56,13 @@ public class ConfigEditor> extends JPanel { this.add(scroll, BorderLayout.CENTER); main.setLayout(new BoxLayout(main, BoxLayout.PAGE_AXIS)); - + main.setBorder(new EmptyBorder(5, 5, 5, 5)); + main.add(new JLabel(title)); items = MetaInfo.getItems(type, bundle); for (MetaInfo item : items) { - addItem(main, item); + addItem(main, item, 0); } main.add(createButton("Reset", new ActionListener() { @@ -98,14 +103,32 @@ public class ConfigEditor> extends JPanel { })); } - private void addItem(JPanel main, MetaInfo item) { + private void addItem(JPanel main, MetaInfo item, int nhgap) { if (item.isGroup()) { - // TODO + JPanel bpane = new JPanel(new BorderLayout()); + bpane.setBorder(new TitledBorder(item.getName())); + JPanel pane = new JPanel(); + pane.setBorder(new EmptyBorder(5, 5, 5, 5)); + pane.setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS)); + + String info = item.getDescription(); + info = StringUtils.justifyTexts(info, 100); + if (!info.isEmpty()) { + JTextArea text = new JTextArea(info); + text.setWrapStyleWord(true); + text.setOpaque(false); + text.setForeground(new Color(100, 100, 180)); + text.setEditable(false); + pane.add(text); + } + for (MetaInfo subitem : item) { - addItem(main, subitem); + addItem(pane, subitem, nhgap + 11); } + bpane.add(pane, BorderLayout.CENTER); + main.add(bpane); } else { - main.add(new ConfigItem(item)); + main.add(new ConfigItem(item, nhgap)); } } diff --git a/src/be/nikiroo/utils/ui/ConfigItem.java b/src/be/nikiroo/utils/ui/ConfigItem.java index 3185f13..f3b729d 100644 --- a/src/be/nikiroo/utils/ui/ConfigItem.java +++ b/src/be/nikiroo/utils/ui/ConfigItem.java @@ -15,7 +15,6 @@ import java.io.IOException; import javax.swing.Icon; import javax.swing.ImageIcon; -import javax.swing.InputVerifier; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JColorChooser; @@ -26,8 +25,8 @@ 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.plaf.basic.BasicArrowButton; import be.nikiroo.utils.Image; import be.nikiroo.utils.StringUtils; @@ -69,8 +68,13 @@ public class ConfigItem> extends JPanel { * * @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) { + public ConfigItem(MetaInfo info, int nhgap) { this.setLayout(new BorderLayout()); // TODO: support arrays @@ -81,46 +85,46 @@ public class ConfigItem> extends JPanel { switch (fmt) { case BOOLEAN: - addBooleanField(info); + addBooleanField(info, nhgap); break; case COLOR: - addColorField(info); + addColorField(info, nhgap); break; case FILE: - addBrowseField(info, false); + addBrowseField(info, nhgap, false); break; case DIRECTORY: - addBrowseField(info, true); + addBrowseField(info, nhgap, true); break; case COMBO_LIST: - addComboboxField(info, true); + addComboboxField(info, nhgap, true); break; case FIXED_LIST: - addComboboxField(info, false); + addComboboxField(info, nhgap, false); break; case INT: - addIntField(info); + addIntField(info, nhgap); break; case PASSWORD: - addPasswordField(info); + addPasswordField(info, nhgap); break; case STRING: case LOCALE: // TODO? default: - addStringField(info); + addStringField(info, nhgap); break; } } - private void addStringField(final MetaInfo info) { + private void addStringField(final MetaInfo info, int nhgap) { final JTextField field = new JTextField(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); + field.setText(info.getString(false)); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + field.setText(info.getString(false)); } }); info.addSaveListener(new Runnable() { @@ -130,17 +134,16 @@ public class ConfigItem> extends JPanel { } }); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } - private void addBooleanField(final MetaInfo info) { + private void addBooleanField(final MetaInfo info, int nhgap) { final JCheckBox field = new JCheckBox(); field.setToolTipText(info.getDescription()); - Boolean state = info.getBoolean(); - if (state == null) { - info.getDefaultBoolean(); - } + Boolean state = info.getBoolean(true); // Should not happen! if (state == null) { @@ -155,10 +158,7 @@ public class ConfigItem> extends JPanel { info.addReloadedListener(new Runnable() { @Override public void run() { - Boolean state = info.getBoolean(); - if (state == null) { - info.getDefaultBoolean(); - } + Boolean state = info.getBoolean(true); if (state == null) { state = false; } @@ -173,19 +173,21 @@ public class ConfigItem> extends JPanel { } }); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } - private void addColorField(final MetaInfo info) { + private void addColorField(final MetaInfo info, int nhgap) { final JTextField field = new JTextField(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); + field.setText(info.getString(false)); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + field.setText(info.getString(false)); } }); info.addSaveListener(new Runnable() { @@ -195,38 +197,41 @@ public class ConfigItem> extends JPanel { } }); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); JPanel pane = new JPanel(new BorderLayout()); final JButton colorWheel = new JButton(); - colorWheel.setIcon(getIcon(17, info.getColor())); + colorWheel.setIcon(getIcon(17, info.getColor(true))); colorWheel.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - Color initialColor = new Color(info.getColor(), true); + 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()); - colorWheel.setIcon(getIcon(17, info.getColor())); + 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, final boolean dir) { + private void addBrowseField(final MetaInfo info, int nhgap, + final boolean dir) { final JTextField field = new JTextField(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); + field.setText(info.getString(false)); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + field.setText(info.getString(false)); } }); info.addSaveListener(new Runnable() { @@ -248,30 +253,33 @@ public class ConfigItem> extends JPanel { File file = chooser.getSelectedFile(); if (file != null) { info.setString(file.getAbsolutePath()); - field.setText(info.getString()); + field.setText(info.getString(false)); } } } }); JPanel pane = new JPanel(new BorderLayout()); - this.add(label(info), BorderLayout.WEST); + 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, boolean editable) { + 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()); + field.setSelectedItem(info.getString(false)); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setSelectedItem(info.getString()); + field.setSelectedItem(info.getString(false)); } }); info.addSaveListener(new Runnable() { @@ -281,19 +289,21 @@ public class ConfigItem> extends JPanel { } }); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } - private void addPasswordField(final MetaInfo info) { + private void addPasswordField(final MetaInfo info, int nhgap) { final JPasswordField field = new JPasswordField(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); + field.setText(info.getString(true)); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + field.setText(info.getString(false)); } }); info.addSaveListener(new Runnable() { @@ -303,85 +313,40 @@ public class ConfigItem> extends JPanel { } }); - this.add(label(info), BorderLayout.WEST); + this.add(label(info, nhgap), BorderLayout.WEST); this.add(field, BorderLayout.CENTER); + + setPreferredSize(field); } - private void addIntField(final MetaInfo info) { - final JTextField field = new JTextField(); + private void addIntField(final MetaInfo info, int nhgap) { + final JSpinner field = new JSpinner(); field.setToolTipText(info.getDescription()); - field.setText(info.getString()); - field.setInputVerifier(new InputVerifier() { - @Override - public boolean verify(JComponent input) { - String text = field.getText().trim(); - if (text.startsWith("-")) { - text = text.substring(1).trim(); - } - - return text.replaceAll("[0-9]", "").isEmpty(); - } - }); + field.setValue(info.getInteger(true) == null ? 0 : info + .getInteger(true)); info.addReloadedListener(new Runnable() { @Override public void run() { - field.setText(info.getString()); + field.setValue(info.getInteger(true) == null ? 0 : info + .getInteger(true)); } }); info.addSaveListener(new Runnable() { @Override public void run() { - info.setString(field.getText()); - Integer value = info.getInteger(); + info.setInteger((Integer) field.getValue()); + Integer value = info.getInteger(false); if (value == null) { - info.setString(""); - } else { - info.setInteger(value); + field.setValue(0); } - field.setText(info.getString()); } }); - JButton up = new BasicArrowButton(BasicArrowButton.NORTH); - JButton down = new BasicArrowButton(BasicArrowButton.SOUTH); - - up.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent ae) { - int value = 0; - try { - value = Integer.parseInt(field.getText()); - } catch (NumberFormatException e) { - } - - field.setText(Integer.toString(value + 1)); - } - }); - - down.addActionListener(new ActionListener() { - @Override - public void actionPerformed(ActionEvent ae) { - int value = 0; - try { - value = Integer.parseInt(field.getText()); - } catch (NumberFormatException e) { - } - - field.setText(Integer.toString(value - 1)); - } - }); - - JPanel upDown = new JPanel(new BorderLayout()); - upDown.add(up, BorderLayout.NORTH); - upDown.add(down, BorderLayout.SOUTH); - - JPanel pane = new JPanel(new BorderLayout()); - pane.add(upDown, BorderLayout.WEST); - pane.add(field, BorderLayout.CENTER); + this.add(label(info, nhgap), BorderLayout.WEST); + this.add(field, BorderLayout.CENTER); - this.add(label(info), BorderLayout.WEST); - this.add(pane, BorderLayout.CENTER); + setPreferredSize(field); } /** @@ -389,10 +354,15 @@ public class ConfigItem> extends JPanel { * * @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 */ - private JComponent label(final MetaInfo info) { + private JComponent label(final MetaInfo info, int nhgap) { final JLabel label = new JLabel(info.getName()); Dimension ps = label.getPreferredSize(); @@ -402,7 +372,7 @@ public class ConfigItem> extends JPanel { int w = ps.width; int step = 150; - for (int i = 2 * step; i < 10 * step; i += step) { + for (int i = 2 * step - nhgap; i < 10 * step; i += step) { if (w < i) { w = i; break; @@ -413,7 +383,8 @@ public class ConfigItem> extends JPanel { @Override public void run() { StringBuilder builder = new StringBuilder(); - String text = info.getDescription().replace("\\n", "\n"); + String text = (info.getDescription().replace("\\n", "\n")) + .trim(); for (String line : StringUtils.justifyText(text, 80, Alignment.LEFT)) { if (builder.length() > 0) { @@ -495,4 +466,11 @@ public class ConfigItem> extends JPanel { return new ImageIcon(img); } + + 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)); + } } -- 2.27.0