Formatted text field API (RT-14000, RT-30881)
Martin Sladecek
martin.sladecek at oracle.com
Wed Jun 11 13:59:16 UTC 2014
Binding the property would mean the text field can be edited (the field
is still editable), but it's content cannot be committed (by either
pressing ENTER or focusing out of the field), so it's content is
practically irrelevant.
If you think there's a reasonable use case for this, there should be no
problem with having writable value.
-Martin
On 11.6.2014 15:53, Scott Palmer wrote:
> In FormattedTextField why is the value property returned as a
> read-only property?
> This control should allow bi-directional bindings to the value property.
>
> Scott
>
> On Wed, Jun 11, 2014 at 4:50 AM, Martin Sladecek
> <martin.sladecek at oracle.com> 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