Formatted text field API (RT-14000, RT-30881)

Martin Sladecek martin.sladecek at oracle.com
Wed Jun 11 14:08:53 UTC 2014


Although I'm not sure the second one can be considered a use-case, I 
agree that somebody might want uneditable field with some format (and 
it's up to the developer to make it uneditable), so I'll make the value rw.

-Martin
On 11.6.2014 16:03, Scott Palmer wrote:
> There are two cases for this:
> - a regular binding could be used with a read-only FormattedTextField
> for cases where you want the text representation shown in the UI and
> have it selectable and readable for copy/paste.
> - a bi-directional binding should "just work"
>
> Scott
>
> On Wed, Jun 11, 2014 at 9:59 AM, Martin Sladecek
> <martin.sladecek at oracle.com> wrote:
>> 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