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