/** * */ package ecologylab.appframework.types.prefs; import java.util.ArrayList; import java.util.LinkedHashMap; import ecologylab.collections.Scope; import ecologylab.serialization.ElementState; import ecologylab.serialization.TranslationContext; import ecologylab.serialization.annotations.simpl_classes; import ecologylab.serialization.annotations.simpl_collection; import ecologylab.serialization.annotations.simpl_inherit; import ecologylab.serialization.annotations.simpl_scalar; import ecologylab.serialization.annotations.simpl_tag; import ecologylab.serialization.types.ScalarType; /** * Metadata about a Preference. Defines information to enable editing the Preference. * * @author andruid * */ @simpl_inherit public abstract class MetaPref<T> extends ElementState implements WidgetTypes { /** The global registry of Pref objects. Used for providing lookup services. */ static final Scope<MetaPref> allMetaPrefsMap = new Scope<MetaPref>(); /** * Unique identifier for Preference name with convenient lookup in automatically generated * HashMap. */ @simpl_tag("id") @simpl_scalar String m_id; /** * This is the short text that appears in the Swing panel for editing the value. */ @simpl_scalar String description; /** * This is longer text about that describes what the Preference does and more about the * implications of its value. */ @simpl_scalar String helpText; /** * Type of graphical user interface component used to interact with it. Must be a constant defined * in the interface WidgetTypes If this value is left as null, it should default to TEXT_FIELD. */ @simpl_scalar String widget; /** * Categories enable tabbed panes of preferences to be edited. */ @simpl_scalar String category; /** * Whether or not the application has to restart for this pref change to take effect. */ @simpl_scalar boolean requiresRestart; /** * optional; for preferences with three or more choices */ @simpl_collection @simpl_classes( { ChoiceBoolean.class, ChoiceFloat.class, ChoiceInt.class }) ArrayList<Choice<T>> choices; ScalarType<T> scalarType; /** * LinkedHashMap to make locating exact choices easier */ private LinkedHashMap<String, Choice<T>> choiceList; ValueChangedListener valueChangedListener; // @xml_attribute T defaultValue; // @xml_nested RangeState<T> range; /** * Instantiate. * * @param scalarType * TODO */ public MetaPref(ScalarType scalarType) { super(); this.scalarType = scalarType; } /** * Gets the default value of a MetaPref; type-specific behavior. * * @return Default value of MetaPref */ public abstract T getDefaultValue(); /** * Gets the min value of the range of a MetaPref. * * @return Min value of a MetaPref */ public abstract T getMinValue(); /** * Gets the max value of the range of a MetaPref. * * @return Max value of a MetaPref */ public abstract T getMaxValue(); /** * Gets the category for a MetaPref. * * @return Category of MetaPref */ public String getCategory() { return category; } /** * Gets the description for a MetaPref. * * @return Description of MetaPref */ public String getDescription() { String result = description; if (requiresRestart) result += "<br><weak><i>Requires restart to take effect.</i></weak>"; return result; } /** * Gets the help text for a MetaPref. * * @return Help Text of MetaPref */ public String getHelpText() { return helpText; } /** * Gets the ID of a MetaPref. This is a unique identifier. * * @return ID (unique) of MetaPref. */ public String getID() { return m_id; } /** * Gets the Choices of a MetaPref. This is optional and may be null. * * @return ID (unique) of MetaPref. */ public ArrayList<Choice<T>> getChoices() { return choices; } /** * Returns true if the metaPref has choices. False otherwise * * @return */ public boolean hasChoices() { return choices != null; } /** * Get a Choice from the list of choices, whose value matches the value passed in. * * @param value * Value to find * @return Choice whose value equals the passed in value */ public Choice<T> getChoiceByValue(String value) { if (choiceList == null) populateChoiceList(); return choiceList.get(value); } /** * Get Choice's name, for a choice whose value that matches the given value. * * @param value * @return Name of choice */ public String getChoiceNameByValue(String value) { return getChoiceByValue(value).getName(); } /** * Get Choice's name, for the choice at the given index. * * @param index * @return Name of choice */ public String getChoiceNameByIndex(int index) { return choices.get(index).getName(); } /** * Get the Choice at the given index. * * @param index * @return Name of choice */ public Choice<T> getChoiceByIndex(int index) { return choices.get(index); } /** * Get the index of a given Choice. * * @param choice * Given choice * @return Index of Choice in choices */ public int getIndexByChoice(Choice<T> choice) { if (choiceList == null) populateChoiceList(); return choices.indexOf(choice); } /** * Get the index of a Choice with the given value. * * @param value * @return index of Choice with given value */ public int getIndexByValue(String value) { Choice<T> thisChoice = getChoiceByValue(value); return getIndexByChoice(thisChoice); } /** * Populate choiceList; this allows for easy searching by value. */ private void populateChoiceList() { choiceList = new LinkedHashMap<String, Choice<T>>(); for (Choice<T> choice : this.choices) { choiceList.put(choice.getValue().toString(), choice); } } /** * Gives printed output showing id, description, category, helpText, widget, default value, and * choices for a MetaPref. */ public void print() { println(this.m_id + '\n' + this.description + '\n' + this.category + '\n' + this.helpText + '\n' + this.widget); println("" + this.getDefaultValue()); if (choices != null) { for (Choice<T> choice : choices) { println("Choice: " + choice.name + ", " + choice.label); } } println("\n"); } /** * Returns whether or not a widget uses radio buttons. * * @return True = Uses radio buttons. False = Doesn't. */ public boolean widgetIsRadio() { if (RADIO_BUTTONS.equals(widget)) return true; return false; } /** * Returns whether or not a widget uses a slider. * * @return True = Uses a slider. False = Doesn't. */ public boolean widgetIsSlider() { if (SLIDER.equals(widget)) return true; return false; } /** * Returns whether or not a widget uses a spinner. * * @return True = Uses a spinner. False = Doesn't. */ public boolean widgetIsSpinner() { if (SPINNER.equals(widget)) return true; return false; } /** * Returns whether or not a widget uses one or more text fields. * * @return True = Uses text field(s). False = Doesn't. */ public boolean widgetIsTextField() { if (TEXT_FIELD.equals(widget)) return true; return false; } /** * Returns whether or not a widget uses check boxes. * * @return True = Uses check boxes. False = Doesn't. */ public boolean widgetIsCheckBox() { if (CHECK_BOX.equals(widget)) return true; return false; } /** * Returns whether or not a widget uses a drop down menu. * * @return True = Uses a drop down menu. False = Doesn't. */ public boolean widgetIsDropDown() { if (DROP_DOWN.equals(widget)) return true; return false; } /** * Returns whether or not a widget uses a color chooser. * * @return True = Uses a color chooser. False = Doesn't. */ public boolean widgetIsColorChooser() { if (COLOR_CHOOSER.equals(widget)) return true; return false; } /** * Returns whether or not a widget uses a file chooser. * * @return True = Uses a file chooser. False = Doesn't. */ public boolean widgetIsFileChooser() { if (FILE_CHOOSER.equals(widget)) return true; return false; } /** * Construct a new instance of the Pref that matches this. * * @return */ protected abstract Pref<T> getPrefInstance(); /** * Create an instance of our associated type, from a String. * * @param string * @return */ public T getInstance(String string) { return scalarType.getInstance(string); } public T getInstance(T value) { return value; } /** * Construct a new instance of the Pref that matches this. Use this to fill-in the default value. * * @return */ public Pref<T> getDefaultPrefInstance() { Pref<T> result = getPrefInstance(); result.name = m_id; result.setValue(this.getDefaultValue()); return result; } /** * Get the Pref object associated with this. That is, look for one in the registry. Return it if * there is one. Otherwise, get a default Pref based on this, register it, and return it. * * @return The Pref object associated with this MetaPref. */ public Pref<T> getAssociatedPref() { Pref result = Pref.lookupPref(m_id); if (result == null) { result = getDefaultPrefInstance(); result.register(); } return result; } /** * Set object that receives a callback when the value of a Pref is changed through the * PrefsEditor. * * @param valueChangedListener */ public void setValueChangedListener(ValueChangedListener valueChangedListener) { this.valueChangedListener = valueChangedListener; } /** * Set object that receives a callback when the value of a Pref is changed through the * PrefsEditor. * * @param valueChangedListener */ public static void setValueChangedListener(String metaPrefId, ValueChangedListener valueChangedListener) { MetaPref metaPref = lookup(metaPrefId); if (metaPref != null) metaPref.setValueChangedListener(valueChangedListener); } /** * Create an entry for this in the allMetaPrefsMap. * */ void register() { allMetaPrefsMap.put(this.m_id, this); } /** * Look up a MetaPref by name in the map of all MetaPrefs * * @param id * Name of MetaPref * * @return MetaPref with the given id */ public static MetaPref lookup(String id) { MetaPref metaPref = allMetaPrefsMap.get(id); return metaPref; } public ValueChangedListener getValueChangedListener() { return valueChangedListener; } @Override public void deserializationPostHook(TranslationContext translationContext, Object object) { MetaPref metaPref = (MetaPref) object; if (metaPref.parent() instanceof MetaPrefSet) { ((MetaPrefSet) metaPref.parent()).addEntryToCategoryMap(metaPref); metaPref.getAssociatedPref(); // create one if needed } else { weird("parent of metaPref should be MetaPrefSet."); } } /* * public boolean isWithinRange(T newValue) { return (range == null) ? true : * range.isWithinRange(newValue); } */ }