Formatted text field API (RT-14000, RT-30881)
Martin Sladecek
martin.sladecek at oracle.com
Wed Jul 2 07:49:40 UTC 2014
There's a new proposal here: https://javafx-jira.kenai.com/browse/RT-14000
I merged FormattedTextField and content filter to a single Formatter
class, that's now part of the TextInputControl.
-Martin
On 06/11/2014 10:50 AM, Martin Sladecek wrote:
> Hello,
> I would like to start some discussion about formatted text field API.
> The related JIRA issues are RT-14000 (formatted text field) and
> RT-30881 (content filter).
>
> The RT-30881 defines a content filter for all text input controls (in
> TextInputControl class), like TextField and TextArea. This was
> originally implemented by Richard Bair and briefly discussed here:
> http://mail.openjdk.java.net/pipermail/openjfx-dev/2012-December/004687.html.
> I've tried to build formatted text field on top of that (and made some
> changes to the API) since content filtering is something most
> formatted text fields will use.
>
> First, the TextInputControl additions:
>
> * contentFilter property of type ObjectProperty<ContentFilter>
>
> So let's look at the content filter and content change (both nested
> classes of TextInputControl):
>
> /**
> * Content Filter specifies the filter to be used with {@link
> TextInputControl#contentFilterProperty()}.
> * It allow user to intercept and modify any change done to the
> text content.
> * To avoid content that's not valid for the filter, it's possible
> to assign a default value supplier for the filter.
> * <p>
> * The filter itself is an {@code UnaryOperator} that accepts
> {@link javafx.scene.control.TextInputControl.ContentChange} object.
> * It should return a {@link
> javafx.scene.control.TextInputControl.ContentChange} object that
> contains the actual (filtered)
> * change. Returning null rejects the change.
> * <p>
> * If default value supplier is provided, it is used when the
> {@code ContentFilter} is assigned to a {@code TextInputControl}
> * and it's current text is invalid. It is expected that the
> provided default value is accepted by the filtering operator.
> */
> public static class ContentFilter {
>
> /**
> * Creates a new filter with the providing filtering operator.
> * @param filter the filtering operator
> *
> * @throws java.lang.NullPointerException if filter is null
> */
> public ContentFilter(UnaryOperator<ContentChange> filter) {}
>
> /**
> * Creates a new filter with the providing filtering operator
> and default value supplier.
> * @param filter the filtering operator
> * @param defaultValue the default value or null
> *
> * @throws java.lang.NullPointerException if filter is null
> */
> public ContentFilter(UnaryOperator<ContentChange> filter,
> Supplier<String> defaultValue) {}
>
> /**
> * The filtering operator of this filter.
> * @return the operator
> */
> public UnaryOperator<ContentChange> getFilter() {}
>
> /**
> * The default value provider of this filter
> * @return the default value provider or null
> */
> public Supplier<String> getDefaultValue() {}
>
> /**
> * Chains this filter with a filtering operator. The other
> filtering operator is used only if this
> * filter's operator rejects the operation. The default value
> of the new {@code ContentFilter} is the same
> * as of this filter.
> * @param other the filtering operator to chain
> * @return a new ContentFilter as described above
> */
> public ContentFilter orApply(UnaryOperator<ContentChange>
> other) {}
>
> /**
> * Chains this filter with a filtering operator of another
> {@code ContentFilter}. The other filtering operator is used only if this
> * filter's operator rejects the operation. The default value
> of the new {@code ContentFilter} is the same
> * as of this filter.
> * @param other the filter to chain
> * @return a new ContentFilter as described above
> */
> public ContentFilter orApply(ContentFilter other) {}
>
> }
>
> /**
> * Contains the state representing a change in the content for a
> * TextInputControl. This object is passed to any registered
> * {@code contentFilter} on the TextInputControl whenever the text
> * for the TextInputControl is modified.
> * <p>
> * This class contains state and convenience methods for
> determining what
> * change occurred on the control. It also has a reference to the
> * TextInputControl itself so that the developer may query any
> other
> * state on the control. Note that you should never modify the
> state
> * of the control directly from within the contentFilter handler.
> * </p>
> * <p>
> * The ContentChange is mutable, but not observable. It should
> be used
> * only for the life of a single change. It is intended that the
> * ContentChange will be modified from within the contentFilter.
> * </p>
> */
> public static final class ContentChange implements Cloneable{
>
> /**
> * Gets the control associated with this change.
> * @return The control associated with this change. This will
> never be null.
> */
> public final TextInputControl getControl() {}
>
> /**
> * Gets the start index into the {@link
> javafx.scene.control.TextInputControl#getText()}
> * for the modification. This will always be a value > 0 and
> * <= {@link
> javafx.scene.control.TextInputControl#getLength()}.
> *
> * @return The start index
> */
> public final int getStart() {}
>
> /**
> * Sets the start index for the range to be modified. This
> value must
> * always be a value > 0 and <= {@link
> javafx.scene.control.TextInputControl#getLength()}
> * <p>
> * <b>Note</b> that setting start before the current end will
> also change end to the same value.
> *
> * @param value the new start index
> */
> public final void setStart(int value) {}
>
> /**
> * Gets the end index into the {@link
> javafx.scene.control.TextInputControl#getText()}
> * for the modification. This will always be a value >
> {@link #getStart()} and
> * <= {@link
> javafx.scene.control.TextInputControl#getLength()}.
> *
> * @return The end index
> */
> public final int getEnd() {}
>
> /**
> * Sets the end index for the range to be modified. This value
> must
> * always be a value > {@link #getStart()} and <= {@link
> javafx.scene.control.TextInputControl#getLength()}.
> * Note that there is an order dependency between modifying
> the start
> * and end values. They must always be modified in order such
> that they
> * do not violate their constraints.
> *
> * @param value The new end index
> */
> public final void setEnd(int value) {}
>
> /**
> * A convenience method returning an IndexRange representing the
> * start and end values.
> *
> * @return a non-null IndexRange representing the range from
> start to end.
> */
> public final IndexRange getRange() {}
>
> /**
> * A convenience method assigning both the start and end values
> * together, in such a way as to ensure they are valid with
> respect to
> * each other. One way to use this method is to set the range
> like this:
> * {@code
> * change.setRange(IndexRange.normalize(newStart, newEnd));
> * }
> *
> * @param value The new range. Cannot be null.
> */
> public final void setRange(IndexRange value) {}
>
> /**
> * A convenience method assigning both the start and end values
> * together, in such a way as to ensure they are valid with
> respect to
> * each other. The start must be less than or equal to the end.
> *
> * @param start The new start value. Must be a valid start value
> * @param end The new end value. Must be a valid end value
> */
> public final void setRange(int start, int end) {}
>
> /**
> * Gets the new anchor. This value will always be > 0 and
> * <= {@link #getProposedControlText()}{@code}.getLength()}
> *
> * @return The new anchor position
> */
> public final int getNewAnchor() {}
>
> /**
> * Sets the new anchor. This value must be > 0 and
> * <= {@link
> #getProposedControlText()}{@code}.getLength()}. Note that there
> * is an order dependence here, in that the anchor should be
> * specified after the new text has been specified.
> *
> * @param value The new anchor position
> */
> public final void setNewAnchor(int value) {}
>
> /**
> * Gets the new caret position. This value will always be >
> 0 and
> * <= {@link #getProposedControlText()}{@code}.getLength()}
> *
> * @return The new caret position
> */
> public final int getNewCaretPosition() {}
>
> /**
> * Sets the new caret position. This value must be > 0 and
> * <= {@link
> #getProposedControlText()}{@code}.getLength()}. Note that there
> * is an order dependence here, in that the caret position
> should be
> * specified after the new text has been specified.
> *
> * @param value The new caret position
> */
> public final void setNewCaretPosition(int value) {}
>
> /**
> * Gets the text used in this change. For example, this may be
> new
> * text being added, or text which is replacing all the
> control's text
> * within the range of start and end. Typically it is an empty
> string
> * only for cases where the range is being deleted.
> *
> * @return The text involved in this change. This will never
> be null.
> */
> public final String getText() {}
>
> /**
> * Sets the text to use in this change. This is used to
> replace the
> * range from start to end, if such a range exists, or to
> insert text
> * at the position represented by start == end.
> *
> * @param value The text. This cannot be null.
> */
> public final void setText(String value) {}
>
> /**
> * Gets the complete new text which will be used on the
> control after
> * this change. Note that some controls (such as TextField)
> may do further
> * filtering after the change is made (such as stripping out
> newlines)
> * such that you cannot assume that the newText will be
> exactly the same
> * as what is finally set as the content on the control,
> however it is
> * correct to assume that this is the case for the purpose of
> computing
> * the new caret position and new anchor position (as those
> values supplied
> * will be modified as necessary after the control has
> stripped any
> * additional characters that the control might strip).
> *
> * @return The controls proposed new text at the time of this
> call, according
> * to the state set for start, end, and text
> properties on this ContentChange object.
> */
> public final String getProposedControlText() {}
>
> /**
> * Gets whether this change was in response to text being
> added. Note that
> * after the ContentChange object is modified by the
> contentFilter (by one
> * of the setters) the return value of this method is not
> altered. It answers
> * as to whether this change was fired as a result of text
> being added,
> * not whether text will end up being added in the end.
> *
> * <p>
> * Text may have been added either cause it was {@link
> #isInserted()},
> * {@link #isAppended()}, or {@link #isPrepended()}.
> * </p>
> *
> * @return true if text was being added
> */
> public final boolean isAdded() {}
>
> /**
> * Gets whether this change was in response to text being
> deleted. Note that
> * after the ContentChange object is modified by the
> contentFilter (by one
> * of the setters) the return value of this method is not
> altered. It answers
> * as to whether this change was fired as a result of text
> being deleted,
> * not whether text will end up being deleted in the end.
> *
> * @return true if text was being deleted
> */
> public final boolean isDeleted() {}
>
> /**
> * Gets whether this change was in response to text being
> added, and in
> * particular, inserted into the midst of the control text. If
> this is
> * true, then {@link #isAdded()} will return true.
> *
> * @return true if text was being inserted
> */
> public final boolean isInserted() {}
>
> /**
> * Gets whether this change was in response to text being
> added, and in
> * particular, appended to the end of the control text. If
> this is
> * true, then {@link #isAdded()} will return true.
> *
> * @return true if text was being appended
> */
> public final boolean isAppended() {}
>
> /**
> * Gets whether this change was in response to text being
> added, and in
> * particular, prepended at the start of the control text. If
> this is
> * true, then {@link #isAdded()} will return true.
> *
> * @return true if text was being prepended
> */
> public final boolean isPrepended() {}
>
> /**
> * Gets whether this change was in response to text being
> replaced. Note that
> * after the ContentChange object is modified by the
> contentFilter (by one
> * of the setters) the return value of this method is not
> altered. It answers
> * as to whether this change was fired as a result of text
> being replaced,
> * not whether text will end up being replaced in the end.
> *
> * @return true if text was being replaced
> */
> public final boolean isReplaced() {}
>
> @Override
> public ContentChange clone() {}
> }
>
>
>
> The new FormattedTextField class relies on the content filtering and
> adds a concept of values convertible to/from a text:
>
> **
> * FormattedTextField is a special kind of TextField that handles
> conversion between a value of type {@code T} and
> * a text of this TextField.
> * <p>
> * There are two types of converters:
> * <ul>
> * <li>{@link #valueConverterProperty()} represents the default
> converter between the text and values</li>
> * <li>{@link #editConverterProperty()} is a special converter
> that takes precedence when the textfield is being edited.
> * This allows to have a edit-friendly format and a nice display
> format for the value</li>
> * </ul>
> *
> * <p>
> * When editing the text field, the value is not updated until either
> {@code ENTER} key is pressed or focus is lost.
> * If the conversion fail, the last known valid value is used instead.
> *
> * @param <T> the value type
> */
> public final class FormattedTextField<T> extends TextField{
>
> /**
> * Creates a formatted text field with the specified converter and
> a null value.
> * @param valueConverter the value converter
> */
> public FormattedTextField(StringConverter<T> valueConverter) {}
>
> /**
> * Creates a formatted text field with the specified converter and
> a value
> * @param valueConverter the value converter
> * @param value the initial value
> */
> public FormattedTextField(StringConverter<T> valueConverter, T
> value) {}
>
> /**
> * This represents the current value of the formatted text field.
> If a {@link #valueConverterProperty()} is provided,
> * and the text field is not being edited, the value is a
> representation of the text in the text field.
> */
> public final ReadOnlyObjectProperty<T> valueProperty() {}
> public final void setValue(T value) {}
> public final T getValue() {}
>
> /**
> * The default converter between the values and text.
> * Note that changing the converter might lead to a change of
> value, but only if the text can be converted by the new
> * converter. Otherwise, the current value is converted to a text,
> which is set to the field.
> * @see #editConverterProperty()
> */
> public final ObjectProperty<StringConverter<T>>
> valueConverterProperty() {}
> public final void setValueConverter(StringConverter<T> converter) {}
> public final StringConverter<T> getValueConverter() {}
>
> /**
> * Converter between values and text when the field is being edited.
> * @see #valueConverterProperty()
> */
> public final ObjectProperty<StringConverter<T>>
> editConverterProperty() {}
> public final void setEditConverter(StringConverter<T> converter) {}
> public final StringConverter<T> getEditConverter() {}
>
> }
>
> You can find the whole patch here:
> https://javafx-jira.kenai.com/secure/attachment/44678/rt-30881_14000_proposal.patch
> There are some examples for content filtering in RT-30881. I'll attach
> some formatted text field samples soon.
>
> Thanks,
> -Martin
>
More information about the openjfx-dev
mailing list