Spec-Zone .ru
спецификации, руководства, описания, API
001/*
002 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation.  Oracle designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Oracle in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
022 * or visit www.oracle.com if you need additional information or have any
023 * questions.
024 */
025
026package javafx.scene.control;
027
028import java.text.BreakIterator;
029import java.util.ArrayList;
030import java.util.Collections;
031import java.util.List;
032import javafx.beans.DefaultProperty;
033import javafx.beans.InvalidationListener;
034import javafx.beans.Observable;
035import javafx.beans.binding.IntegerBinding;
036import javafx.beans.binding.StringBinding;
037import javafx.beans.property.BooleanProperty;
038import javafx.beans.property.ObjectProperty;
039import javafx.beans.property.ReadOnlyIntegerProperty;
040import javafx.beans.property.ReadOnlyIntegerWrapper;
041import javafx.beans.property.ReadOnlyObjectProperty;
042import javafx.beans.property.ReadOnlyObjectWrapper;
043import javafx.beans.property.ReadOnlyStringProperty;
044import javafx.beans.property.ReadOnlyStringWrapper;
045import javafx.beans.property.SimpleBooleanProperty;
046import javafx.beans.property.SimpleStringProperty;
047import javafx.beans.property.StringProperty;
048import javafx.beans.value.ChangeListener;
049import javafx.beans.value.ObservableStringValue;
050import javafx.beans.value.ObservableValue;
051import javafx.css.CssMetaData;
052import javafx.css.FontCssMetaData;
053import javafx.css.PseudoClass;
054import javafx.css.StyleOrigin;
055import javafx.css.StyleableObjectProperty;
056import javafx.css.StyleableProperty;
057import javafx.scene.input.Clipboard;
058import javafx.scene.input.ClipboardContent;
059import javafx.scene.text.Font;
060import com.sun.javafx.Utils;
061import com.sun.javafx.binding.ExpressionHelper;
062import javafx.css.Styleable;
063
064/**
065 * Abstract base class for text input controls.
066 */
067@DefaultProperty("text")
068public abstract class TextInputControl extends Control {
069    /**
070     * Interface representing a text input's content. Since it is an ObservableStringValue,
071     * you can also bind to, or observe the content.
072     */
073    protected interface Content extends ObservableStringValue {
074        /**
075         * Retrieves a subset of the content.
076         *
077         * @param start
078         * @param end
079         */
080        public String get(int start, int end);
081
082        /**
083         * Inserts a sequence of characters into the content.
084         *
085         * @param index
086         * @param text
087         */
088        public void insert(int index, String text, boolean notifyListeners);
089
090        /**
091         * Removes a sequence of characters from the content.
092         *
093         * @param start
094         * @param end
095         */
096        public void delete(int start, int end, boolean notifyListeners);
097
098        /**
099         * Returns the number of characters represented by the content.
100         */
101        public int length();
102    }
103
104    /***************************************************************************
105     *                                                                         *
106     * Constructors                                                            *
107     *                                                                         *
108     **************************************************************************/
109
110    /**
111     * Creates a new TextInputControl. The content is an immutable property and
112     * must be specified (as non-null) at the time of construction.
113     *
114     * @param content a non-null implementation of Content.
115     */
116    protected TextInputControl(final Content content) {
117        this.content = content;
118
119        // Add a listener so that whenever the Content is changed, we notify
120        // listeners of the text property that it is invalid.
121        content.addListener(new InvalidationListener() {
122            @Override public void invalidated(Observable observable) {
123                if (content.length() > 0) {
124                    text.textIsNull = false;
125                }
126                text.invalidate();
127            }
128        });
129
130        // Bind the length to be based on the length of the text property
131        length.bind(new IntegerBinding() {
132            { bind(text); }
133            @Override protected int computeValue() {
134                String txt = text.get();
135                return txt == null ? 0 : txt.length();
136            }
137        });
138
139        // Bind the selected text to be based on the selection and text properties
140        selectedText.bind(new StringBinding() {
141            { bind(selection, text); }
142            @Override protected String computeValue() {
143                String txt = text.get();
144                IndexRange sel = selection.get();
145                if (txt == null || sel == null) return "";
146
147                int start = sel.getStart();
148                int end = sel.getEnd();
149                int length = txt.length();
150                if (end > start + length) end = length;
151                if (start > length-1) start = end = 0;
152                return txt.substring(start, end);
153            }
154        });
155
156        // Specify the default style class
157        getStyleClass().add("text-input");
158    }
159
160    /***************************************************************************
161     *                                                                         *
162     * Properties                                                              *
163     *                                                                         *
164     **************************************************************************/
165
166    /**
167     * The default font to use for text in the TextInputControl. If the TextInputControl's text is
168     * rich text then this font may or may not be used depending on the font
169     * information embedded in the rich text, but in any case where a default
170     * font is required, this font will be used.
171     */
172    public final ObjectProperty<Font> fontProperty() {
173        if (font == null) {
174            font = new StyleableObjectProperty<Font>(Font.getDefault()) {
175
176
177                private boolean fontSetByCss = false;
178
179                @Override
180                public void applyStyle(StyleOrigin newOrigin, Font value) {
181
182                    //
183                    // RT-20727 - if CSS is setting the font, then make sure invalidate doesn't call impl_reapplyCSS
184                    //
185                    try {
186                        // super.applyStyle calls set which might throw if value is bound.
187                        // Have to make sure fontSetByCss is reset.
188                        fontSetByCss = true;
189                        super.applyStyle(newOrigin, value);
190                    } catch(Exception e) {
191                        throw e;
192                    } finally {
193                        fontSetByCss = false;
194                    }
195
196                }
197
198
199                @Override
200                public void set(Font value) {
201                    final Font oldValue = get();
202                    if (value == null ? oldValue == null : value.equals(oldValue)) {
203                        return;
204                    }
205                    super.set(value);
206                }
207
208                @Override
209                protected void invalidated() {
210                    // RT-20727 - if font is changed by calling setFont, then
211                    // css might need to be reapplied since font size affects
212                    // calculated values for styles with relative values
213                    if(fontSetByCss == false) {
214                        TextInputControl.this.impl_reapplyCSS();
215                    }
216                }
217
218                @Override
219                public CssMetaData<TextInputControl,Font> getCssMetaData() {
220                    return StyleableProperties.FONT;
221                }
222
223                @Override
224                public Object getBean() {
225                    return TextInputControl.this;
226                }
227
228                @Override
229                public String getName() {
230                    return "font";
231                }
232            };
233        }
234        return font;
235    }
236
237    private ObjectProperty<Font> font;
238    public final void setFont(Font value) { fontProperty().setValue(value); }
239    public final Font getFont() { return font == null ? Font.getDefault() : font.getValue(); }
240
241    /**
242     * The prompt text to display in the {@code TextInputControl}, or
243     * <tt>null</tt> if no prompt text is displayed.
244     * @since 2.2
245     */
246    private StringProperty promptText = new SimpleStringProperty(this, "promptText", "") {
247        @Override protected void invalidated() {
248            // Strip out newlines
249            String txt = get();
250            if (txt != null && txt.contains("\n")) {
251                txt = txt.replace("\n", "");
252                set(txt);
253            }
254        }
255    };
256    public final StringProperty promptTextProperty() { return promptText; }
257    public final String getPromptText() { return promptText.get(); }
258    public final void setPromptText(String value) { promptText.set(value); }
259
260
261    private final Content content;
262    /**
263     * Returns the text input's content model.
264     */
265    protected final Content getContent() {
266        return content;
267    }
268
269    /**
270     * The textual content of this TextInputControl.
271     */
272    private TextProperty text = new TextProperty();
273    public final String getText() { return text.get(); }
274    public final void setText(String value) { text.set(value); }
275    public final StringProperty textProperty() { return text; }
276
277    /**
278     * The number of characters in the text input.
279     */
280    private ReadOnlyIntegerWrapper length = new ReadOnlyIntegerWrapper(this, "length");
281    public final int getLength() { return length.get(); }
282    public final ReadOnlyIntegerProperty lengthProperty() { return length.getReadOnlyProperty(); }
283
284    /**
285     * Indicates whether this TextInputControl can be edited by the user.
286     */
287    private BooleanProperty editable = new SimpleBooleanProperty(this, "editable", true) {
288        @Override protected void invalidated() {
289            pseudoClassStateChanged(PSEUDO_CLASS_READONLY, get());
290        }
291    };
292    public final boolean isEditable() { return editable.getValue(); }
293    public final void setEditable(boolean value) { editable.setValue(value); }
294    public final BooleanProperty editableProperty() { return editable; }
295
296    /**
297     * The current selection.
298     */
299    private ReadOnlyObjectWrapper<IndexRange> selection = new ReadOnlyObjectWrapper<IndexRange>(this, "selection", new IndexRange(0, 0));
300    public final IndexRange getSelection() { return selection.getValue(); }
301    public final ReadOnlyObjectProperty<IndexRange> selectionProperty() { return selection.getReadOnlyProperty(); }
302
303    /**
304     * Defines the characters in the TextInputControl which are selected
305     */
306    private ReadOnlyStringWrapper selectedText = new ReadOnlyStringWrapper(this, "selectedText");
307    public final String getSelectedText() { return selectedText.get(); }
308    public final ReadOnlyStringProperty selectedTextProperty() { return selectedText.getReadOnlyProperty(); }
309
310    /**
311     * The <code>anchor</code> of the text selection.
312     * The <code>anchor</code> and <code>caretPosition</code> make up the selection
313     * range. Selection must always be specified in terms of begin &lt;= end, but
314     * <code>anchor</code> may be less than, equal to, or greater than the
315     * <code>caretPosition</code>. Depending on how the user selects text,
316     * the anchor might represent the lower or upper bound of the selection.
317     */
318    private ReadOnlyIntegerWrapper anchor = new ReadOnlyIntegerWrapper(this, "anchor", 0);
319    public final int getAnchor() { return anchor.get(); }
320    public final ReadOnlyIntegerProperty anchorProperty() { return anchor.getReadOnlyProperty(); }
321
322    /**
323     * The current position of the caret within the text.
324     * The <code>anchor</code> and <code>caretPosition</code> make up the selection
325     * range. Selection must always be specified in terms of begin &lt;= end, but
326     * <code>anchor</code> may be less than, equal to, or greater than the
327     * <code>caretPosition</code>. Depending on how the user selects text,
328     * the caretPosition might represent the lower or upper bound of the selection.
329     */
330    private ReadOnlyIntegerWrapper caretPosition = new ReadOnlyIntegerWrapper(this, "caretPosition", 0);
331    public final int getCaretPosition() { return caretPosition.get(); }
332    public final ReadOnlyIntegerProperty caretPositionProperty() { return caretPosition.getReadOnlyProperty(); }
333
334    /**
335     * This flag is used to indicate that the text on replace trigger should
336     * NOT update the caret position. Basically it is a flag we use to
337     * indicate that the change to textInputControl.text was from us instead of from
338     * the developer. The language being what it is, it is possible that the
339     * developer is also bound to textInputControl.text and that they will change the
340     * text value before our on replace trigger gets called. We will therefore
341     * have to check the caret position against the text to make sure we don't
342     * get a caret position out of bounds. But otherwise, we don't update
343     * the caret when text is set internally.
344     */
345    private boolean doNotAdjustCaret = false;
346
347    /***************************************************************************
348     *                                                                         *
349     * Methods                                                                 *
350     *                                                                         *
351     **************************************************************************/
352
353    /**
354     * Returns a subset of the text input's content.
355     *
356     * @param start must be a value between 0 and end - 1.
357     * @param end must be less than or equal to the length
358     */
359    public String getText(int start, int end) {
360        // TODO these checks really belong in Content
361        if (start > end) {
362            throw new IllegalArgumentException("The start must be <= the end");
363        }
364
365        if (start < 0
366            || end > getLength()) {
367            throw new IndexOutOfBoundsException();
368        }
369
370        return getContent().get(start, end);
371    }
372
373    /**
374     * Appends a sequence of characters to the content.
375     *
376     * @param text a non null String
377     */
378    public void appendText(String text) {
379        insertText(getLength(), text);
380    }
381
382    /**
383     * Inserts a sequence of characters into the content.
384     *
385     * @param index The location to insert the text.
386     * @param text The text to insert.
387     */
388    public void insertText(int index, String text) {
389        replaceText(index, index, text);
390    }
391
392    /**
393     * Removes a range of characters from the content.
394     *
395     * @param range The range of text to delete. The range object must not be null.
396     *
397     * @see #deleteText(int, int)
398     */
399    public void deleteText(IndexRange range) {
400        replaceText(range, "");
401    }
402
403    /**
404     * Removes a range of characters from the content.
405     *
406     * @param start The starting index in the range, inclusive. This must be &gt;= 0 and &lt; the end.
407     * @param end The ending index in the range, exclusive. This is one-past the last character to
408     *            delete (consistent with the String manipulation methods). This must be &gt; the start,
409     *            and &lt;= the length of the text.
410     */
411    public void deleteText(int start, int end) {
412        replaceText(start, end, "");
413    }
414
415    /**
416     * Replaces a range of characters with the given text.
417     *
418     * @param range The range of text to replace. The range object must not be null.
419     * @param text The text that is to replace the range. This must not be null.
420     *
421     * @see #replaceText(int, int, String)
422     */
423    public void replaceText(IndexRange range, String text) {
424        if (range == null) {
425            throw new NullPointerException();
426        }
427
428        int start = range.getStart();
429        int end = start + range.getLength();
430
431        replaceText(start, end, text);
432    }
433
434    /**
435     * Replaces a range of characters with the given text.
436     *
437     * @param start The starting index in the range, inclusive. This must be &gt;= 0 and &lt; the end.
438     * @param end The ending index in the range, exclusive. This is one-past the last character to
439     *            delete (consistent with the String manipulation methods). This must be &gt; the start,
440     *            and &lt;= the length of the text.
441     * @param text The text that is to replace the range. This must not be null.
442     */
443    public void replaceText(int start, int end, String text) {
444        if (start > end) {
445            throw new IllegalArgumentException();
446        }
447
448        if (text == null) {
449            throw new NullPointerException();
450        }
451
452        if (start < 0
453            || end > getLength()) {
454            throw new IndexOutOfBoundsException();
455        }
456
457        if (!this.text.isBound()) {
458            getContent().delete(start, end, text.isEmpty());
459            getContent().insert(start, text, true);
460
461            start += text.length();
462            selectRange(start, start);
463        }
464    }
465
466    /**
467     * Transfers the currently selected range in the text to the clipboard,
468     * removing the current selection.
469     */
470    public void cut() {
471        copy();
472        IndexRange selection = getSelection();
473        deleteText(selection.getStart(), selection.getEnd());
474    }
475
476    /**
477     * Transfers the currently selected range in the text to the clipboard,
478     * leaving the current selection.
479     */
480     public void copy() {
481        final String selectedText = getSelectedText();
482        if (selectedText.length() > 0) {
483            final ClipboardContent content = new ClipboardContent();
484            content.putString(selectedText);
485            Clipboard.getSystemClipboard().setContent(content);
486        }
487    }
488
489    /**
490     * Transfers the contents in the clipboard into this text,
491     * replacing the current selection.  If there is no selection, the contents
492     * in the clipboard is inserted at the current caret position.
493     */
494    public void paste() {
495        final Clipboard clipboard = Clipboard.getSystemClipboard();
496        if (clipboard.hasString()) {
497            final String text = clipboard.getString();
498            if (text != null) {
499                replaceSelection(text);
500            }
501        }
502    }
503
504    /**
505     * Moves the selection backward one char in the text. This may have the
506     * effect of deselecting, depending on the location of the anchor relative
507     * to the caretPosition. This function effectively just moves the caretPosition.
508     */
509    public void selectBackward() {
510        if (getCaretPosition() > 0 && getLength() > 0) {
511            // because the anchor stays put, by moving the caret to the left
512            // we ensure that a selection is registered and that it is correct
513            selectRange(getAnchor(), Character.offsetByCodePoints(getText(), getCaretPosition(), -1));
514        }
515    }
516
517    /**
518     * Moves the selection forward one char in the text. This may have the
519     * effect of deselecting, depending on the location of the anchor relative
520     * to the caretPosition. This function effectively just moves the caret forward.
521     */
522    public void selectForward() {
523        final int textLength = getLength();
524        if (textLength > 0 && getCaretPosition() < textLength) {
525            selectRange(getAnchor(), Character.offsetByCodePoints(getText(), getCaretPosition(), 1));
526        }
527    }
528
529    /**
530     * The break iterator instance.  Right now, it is only used to perform
531     * previous/next word navigation.
532     */
533    private BreakIterator breakIterator;
534
535    /**
536     * Moves the caret to the beginning of previous word. This function
537     * also has the effect of clearing the selection.
538     */
539    public void previousWord() {
540        previousWord(false);
541    }
542
543    /**
544     * Moves the caret to the beginning of next word. This function
545     * also has the effect of clearing the selection.
546     */
547    public void nextWord() {
548        nextWord(false);
549    }
550
551    /**
552     * Moves the caret to the end of the next word. This function
553     * also has the effect of clearing the selection.
554     */
555    public void endOfNextWord() {
556        endOfNextWord(false);
557    }
558
559    /**
560     * Moves the caret to the beginning of previous word. This does not cause
561     * the selection to be cleared. Rather, the anchor stays put and the caretPosition is
562     * moved to the beginning of previous word.
563     */
564    public void selectPreviousWord() {
565        previousWord(true);
566    }
567
568    /**
569     * Moves the caret to the beginning of next word. This does not cause
570     * the selection to be cleared. Rather, the anchor stays put and the caretPosition is
571     * moved to the beginning of next word.
572     */
573    public void selectNextWord() {
574        nextWord(true);
575    }
576
577    /**
578     * Moves the caret to the end of the next word. This does not cause
579     * the selection to be cleared.
580     */
581    public void selectEndOfNextWord() {
582        endOfNextWord(true);
583    }
584
585    private void previousWord(boolean select) {
586        final int textLength = getLength();
587        final String text = getText();
588        if (textLength <= 0) {
589            return;
590        }
591
592        if (breakIterator == null) {
593            breakIterator = BreakIterator.getWordInstance();
594        }
595        breakIterator.setText(text);
596
597        int pos = breakIterator.preceding(Utils.clamp(0, getCaretPosition(), textLength - 1));
598
599        // Skip the non-word region, then move/select to the beginning of the word.
600        while (pos != BreakIterator.DONE &&
601               !Character.isLetter(text.charAt(Utils.clamp(0, pos, textLength-1)))) {
602            pos = breakIterator.preceding(Utils.clamp(0, pos, textLength-1));
603        }
604
605        // move/select
606        selectRange(select ? getAnchor() : pos, pos);
607    }
608
609    private void nextWord(boolean select) {
610        final int textLength = getLength();
611        final String text = getText();
612        if (textLength <= 0) {
613            return;
614        }
615
616        if (breakIterator == null) {
617            breakIterator = BreakIterator.getWordInstance();
618        }
619        breakIterator.setText(text);
620
621        int last = breakIterator.following(Utils.clamp(0, getCaretPosition(), textLength-1));
622        int current = breakIterator.next();
623
624        // skip the non-word region, then move/select to the beginning of the word.
625        while (current != BreakIterator.DONE) {
626            for (int p=last; p<=current; p++) {
627                if (Character.isLetter(text.charAt(Utils.clamp(0, p, textLength-1)))) {
628                    if (select) {
629                        selectRange(getAnchor(), p);
630                    } else {
631                        selectRange(p, p);
632                    }
633                    return;
634                }
635            }
636            last = current;
637            current = breakIterator.next();
638        }
639
640        // move/select to the end
641        if (select) {
642            selectRange(getAnchor(), textLength);
643        } else {
644            end();
645        }
646    }
647
648    private void endOfNextWord(boolean select) {
649        final int textLength = getLength();
650        final String text = getText();
651        if (textLength <= 0) {
652            return;
653        }
654
655        if (breakIterator == null) {
656            breakIterator = BreakIterator.getWordInstance();
657        }
658        breakIterator.setText(text);
659
660        int last = breakIterator.following(Utils.clamp(0, getCaretPosition(), textLength-1));
661        int current = breakIterator.next();
662
663        // skip the non-word region, then move/select to the end of the word.
664        while (current != BreakIterator.DONE) {
665            for (int p=last; p<=current; p++) {
666                if (!Character.isLetter(text.charAt(Utils.clamp(0, p, textLength-1)))) {
667                    if (select) {
668                        selectRange(getAnchor(), p);
669                    } else {
670                        selectRange(p, p);
671                    }
672                    return;
673                }
674            }
675            last = current;
676            current = breakIterator.next();
677        }
678
679        // move/select to the end
680        if (select) {
681            selectRange(getAnchor(), textLength);
682        } else {
683            end();
684        }
685    }
686
687    /**
688     * Selects all text in the text input.
689     */
690    public void selectAll() {
691        selectRange(0, getLength());
692    }
693
694    /**
695     * Moves the caret to before the first char of the text. This function
696     * also has the effect of clearing the selection.
697     */
698    public void home() {
699        // user wants to go to start
700        selectRange(0, 0);
701    }
702
703    /**
704     * Moves the caret to after the last char of the text. This function
705     * also has the effect of clearing the selection.
706     */
707    public void end() {
708        // user wants to go to end
709        final int textLength = getLength();
710        if (textLength > 0) {
711            selectRange(textLength, textLength);
712        }
713    }
714
715    /**
716     * Moves the caret to before the first char of text. This does not cause
717     * the selection to be cleared. Rather, the anchor stays put and the
718     * caretPosition is moved to before the first char.
719     */
720    public void selectHome() {
721        selectRange(getAnchor(), 0);
722    }
723
724    /**
725     * Moves the caret to after the last char of text. This does not cause
726     * the selection to be cleared. Rather, the anchor stays put and the
727     * caretPosition is moved to after the last char.
728     */
729    public void selectEnd() {
730        final int textLength = getLength();
731        if (textLength > 0) selectRange(getAnchor(), textLength);
732    }
733
734    /**
735     * Deletes the character that precedes the current caret position from the
736     * text if there is no selection, or deletes the selection if there is one.
737     * This function returns true if the deletion succeeded, false otherwise.
738     */
739    public boolean deletePreviousChar() {
740        boolean failed = true;
741        if (isEditable() && !isDisabled()) {
742            final String text = getText();
743            final int dot = getCaretPosition();
744            final int mark = getAnchor();
745            if (dot != mark) {
746                // there is a selection of text to remove
747                replaceSelection("");
748                failed = false;
749            } else if (dot > 0) {
750                // The caret is not at the beginning, so remove some characters.
751                // Typically you'd only be removing a single character, but
752                // in some cases you must remove two depending on the unicode
753                // characters
754                int p = Character.offsetByCodePoints(text, dot, -1);
755                doNotAdjustCaret = true;
756                deleteText(p, dot);
757                selectRange(p, p);
758                failed = false;
759                doNotAdjustCaret = false;
760            }
761        }
762        return !failed;
763    }
764
765    /**
766     * Deletes the character that follows the current caret position from the
767     * text if there is no selection, or deletes the selection if there is one.
768     * This function returns true if the deletion succeeded, false otherwise.
769     */
770    public boolean deleteNextChar() {
771        boolean failed = true;
772        if (isEditable() && !isDisabled()) {
773            final String text = getText();
774            final int dot = getCaretPosition();
775            final int mark = getAnchor();
776            if (dot != mark) {
777                // there is a selection of text to remove
778                replaceSelection("");
779                int newDot = Math.min(dot, mark);
780                selectRange(newDot, newDot);
781                failed = false;
782            } else if (text.length() > 0 && dot < text.length()) {
783                // The caret is not at the end, so remove some characters.
784                // Typically you'd only be removing a single character, but
785                // in some cases you must remove two depending on the unicode
786                // characters
787                int p = Character.offsetByCodePoints(text, dot, 1);
788                doNotAdjustCaret = true;
789                //setText(text.substring(0, dot) + text.substring(dot + delChars));
790                deleteText(dot, p);
791                failed = false;
792                doNotAdjustCaret = false;
793            }
794        }
795        return !failed;
796    }
797
798    /**
799     * Moves the caret position forward. If there is no selection, then the
800     * caret position is moved one character forward. If there is a selection,
801     * then the caret position is moved to the end of the selection and
802     * the selection cleared.
803     */
804    public void forward() {
805        // user has moved caret to the right
806        final int textLength = getLength();
807        final int dot = getCaretPosition();
808        final int mark = getAnchor();
809        if (dot != mark) {
810            int pos = Math.max(dot, mark);
811            selectRange(pos, pos);
812        } else if (dot < textLength && textLength > 0) {
813            int pos = Character.offsetByCodePoints(getText(), dot, 1);
814            selectRange(pos, pos);
815        }
816        deselect();
817    }
818
819    /**
820     * Moves the caret position backward. If there is no selection, then the
821     * caret position is moved one character backward. If there is a selection,
822     * then the caret position is moved to the beginning of the selection and
823     * the selection cleared.
824     *
825     * @expert This function is intended to be used by experts, primarily
826     *         by those implementing new Skins or Behaviors. It is not common
827     *         for developers or designers to access this function directly.
828     * @since JavaFX 1.3
829     */
830    public void backward() {
831        // user has moved caret to the left
832        final int textLength = getLength();
833        final int dot = getCaretPosition();
834        final int mark = getAnchor();
835        if (dot != mark) {
836            int pos = Math.min(dot, mark);
837            selectRange(pos, pos);
838        } else if (dot > 0 && textLength > 0) {
839            int pos = Character.offsetByCodePoints(getText(), dot, -1);
840            selectRange(pos, pos);
841        }
842        deselect();
843    }
844
845    /**
846     * Positions the caret to the position indicated by {@code pos}. This
847     * function will also clear the selection.
848     */
849    public void positionCaret(int pos) {
850        final int p = Utils.clamp(0, pos, getLength());
851        selectRange(p, p);
852    }
853
854    /**
855     * Positions the caret to the position indicated by {@code pos} and extends
856     * the selection, if there is one. If there is no selection, then a
857     * selection is formed where the anchor is at the current caret position
858     * and the caretPosition is moved to pos.
859     */
860    public void selectPositionCaret(int pos) {
861        selectRange(getAnchor(), Utils.clamp(0, pos, getLength()));
862    }
863
864    /**
865     * Positions the anchor and caretPosition explicitly.
866     */
867    public void selectRange(int anchor, int caretPosition) {
868        this.caretPosition.set(Utils.clamp(0, caretPosition, getLength()));
869        this.anchor.set(Utils.clamp(0, anchor, getLength()));
870        this.selection.set(IndexRange.normalize(getAnchor(), getCaretPosition()));
871    }
872
873    /**
874     * This function will extend the selection to include the specified pos.
875     * This is different from selectPositionCaret in that it does not simply
876     * move the caret. Rather, it will reposition the caret and anchor as necessary
877     * to ensure that pos becomes the new caret and the far other end of the
878     * selection becomes the anchor.
879     */
880    public void extendSelection(int pos) {
881        final int p = Utils.clamp(0, pos, getLength());
882        final int dot = getCaretPosition();
883        final int mark = getAnchor();
884        int start = Math.min(dot, mark);
885        int end = Math.max(dot, mark);
886        if (p < start) {
887            selectRange(end, p);
888        } else {
889            selectRange(start, p);
890        }
891    }
892
893    /**
894     * Clears the text.
895     */
896    public void clear() {
897        deselect();
898        if (!text.isBound()) {
899            setText("");
900        }
901    }
902
903    /**
904     * Clears the selection.
905     */
906    public void deselect() {
907        // set the anchor equal to the caret position, which clears the selection
908        // while also preserving the caret position
909        selectRange(getCaretPosition(), getCaretPosition());
910    }
911
912    /**
913     * Replaces the selection with the given replacement String. If there is
914     * no selection, then the replacement text is simply inserted at the current
915     * caret position. If there was a selection, then the selection is cleared
916     * and the given replacement text inserted.
917     */
918    public void replaceSelection(String replacement) {
919        if (text.isBound()) return;
920
921        if (replacement == null) {
922            throw new NullPointerException();
923        }
924
925        final int dot = getCaretPosition();
926        final int mark = getAnchor();
927        int start = Math.min(dot, mark);
928        int end = Math.max(dot, mark);
929        int pos = dot;
930
931        if (getLength() == 0) {
932            doNotAdjustCaret = true;
933            setText(replacement);
934            selectRange(getLength(), getLength());
935            doNotAdjustCaret = false;
936        } else {
937            deselect();
938            // RT-16566: Need to take into account stripping of chars into caret pos
939            doNotAdjustCaret = true;
940            int oldLength = getLength();
941            end = Math.min(end, oldLength);
942            if (end > start) {
943                getContent().delete(start, end, replacement.isEmpty());
944                oldLength -= (end - start);
945            }
946            getContent().insert(start, replacement, true);
947            // RT-16566: Need to take into account stripping of chars into caret pos
948            final int p = start + getLength() - oldLength;
949            selectRange(p, p);
950            doNotAdjustCaret = false;
951        }
952    }
953
954    // Used by TextArea, although there are probably other better ways of
955    // doing this.
956    void textUpdated() { }
957
958    /**
959     * A little utility method for stripping out unwanted characters.
960     * 
961     * @param txt
962     * @param stripNewlines
963     * @param stripTabs
964     * @return The string after having the unwanted characters stripped out.
965     */
966    static String filterInput(String txt, boolean stripNewlines, boolean stripTabs) {
967        // Most of the time, when text is inserted, there are no illegal
968        // characters. So we'll do a "cheap" check for illegal characters.
969        // If we find one, we'll do a longer replace algorithm. In the
970        // case of illegal characters, this may at worst be an O(2n) solution.
971        // Strip out any characters that are outside the printed range
972        if (containsInvalidCharacters(txt, stripNewlines, stripTabs)) {
973            StringBuilder s = new StringBuilder(txt.length());
974            for (int i=0; i<txt.length(); i++) {
975                final char c = txt.charAt(i);
976                if (!isInvalidCharacter(c, stripNewlines, stripTabs)) {
977                    s.append(c);
978                }
979            }
980            txt = s.toString();
981        }
982        return txt;
983    }
984
985    static boolean containsInvalidCharacters(String txt, boolean newlineIllegal, boolean tabIllegal) {
986        for (int i=0; i<txt.length(); i++) {
987            final char c = txt.charAt(i);
988            if (isInvalidCharacter(c, newlineIllegal, tabIllegal)) return true;
989        }
990        return false;
991    }
992
993    private static boolean isInvalidCharacter(char c, boolean newlineIllegal, boolean tabIllegal) {
994        if (c == 0x7F) return true;
995        if (c == 0xA) return newlineIllegal;
996        if (c == 0x9) return tabIllegal;
997        if (c < 0x20) return true;
998        return false;
999    }
1000
1001    // It can be bound, in which case we will force it to be an eager
1002    // binding so that we update the content eagerly
1003    // It can be bidirectionally bound, which basically will just work
1004    // If somebody changes the content directly, it will be notified and
1005    // send an invalidation event.
1006    private class TextProperty extends StringProperty {
1007        // This is used only when the property is bound
1008        private ObservableValue<? extends String> observable = null;
1009        // Added to the observable when bound
1010        private InvalidationListener listener = null;
1011        // Used for event handling
1012        private ExpressionHelper<String> helper = null;
1013        // The developer my set the Text property to null. Although
1014        // the Content must be given an empty String, we must still
1015        // treat the value as though it were null, so that a subsequent
1016        // getText() will return null.
1017        private boolean textIsNull = false;
1018
1019        @Override public String get() {
1020            // Since we force eager binding and content is always up to date,
1021            // we just need to get it from content and not through the binding
1022            return textIsNull ? null : content.get();
1023        }
1024
1025        @Override public void set(String value) {
1026            if (isBound()) {
1027                throw new java.lang.RuntimeException("A bound value cannot be set.");
1028            }
1029            doSet(value);
1030            markInvalid();
1031        }
1032
1033        private void invalidate() {
1034            markInvalid();
1035        }
1036
1037        @Override public void bind(ObservableValue<? extends String> observable) {
1038            if (observable == null) {
1039                throw new NullPointerException("Cannot bind to null");
1040            }
1041            if (!observable.equals(this.observable)) {
1042                unbind();
1043                this.observable = observable;
1044                if (listener == null) {
1045                    listener = new Listener();
1046                }
1047                this.observable.addListener(listener);
1048                markInvalid();
1049                doSet(observable.getValue());
1050            }
1051        }
1052
1053        @Override public void unbind() {
1054            if (observable != null) {
1055                doSet(observable.getValue());
1056                observable.removeListener(listener);
1057                observable = null;
1058            }
1059        }
1060
1061        @Override public boolean isBound() {
1062            return observable != null;
1063        }
1064
1065        @Override public void addListener(InvalidationListener listener) {
1066            helper = ExpressionHelper.addListener(helper, this, listener);
1067        }
1068
1069        @Override public void removeListener(InvalidationListener listener) {
1070            helper = ExpressionHelper.removeListener(helper, listener);
1071        }
1072
1073        @Override public void addListener(ChangeListener<? super String> listener) {
1074            helper = ExpressionHelper.addListener(helper, this, listener);
1075        }
1076
1077        @Override public void removeListener(ChangeListener<? super String> listener) {
1078            helper = ExpressionHelper.removeListener(helper, listener);
1079        }
1080
1081        @Override public Object getBean() {
1082            return TextInputControl.this;
1083        }
1084
1085        @Override public String getName() {
1086            return "text";
1087        }
1088
1089        private void fireValueChangedEvent() {
1090            ExpressionHelper.fireValueChangedEvent(helper);
1091        }
1092
1093        private void markInvalid() {
1094            fireValueChangedEvent();
1095        }
1096
1097        private void doSet(String value) {
1098            // Guard against the null value.
1099            textIsNull = value == null;
1100            if (value == null) value = "";
1101            // Update the content
1102            content.delete(0, content.length(), value.isEmpty());
1103            content.insert(0, value, true);
1104            if (!doNotAdjustCaret) {
1105                selectRange(0, 0);
1106                textUpdated();
1107            }
1108        }
1109
1110        private class Listener implements InvalidationListener {
1111            @Override
1112            public void invalidated(Observable valueModel) {
1113                // We now need to force it to be eagerly recomputed
1114                // because we need to push these changes to the
1115                // content model. Because changing the model ends
1116                // up calling invalidate and markInvalid, the
1117                // listeners will all be notified.
1118                doSet(observable.getValue());
1119            }
1120        }
1121    }
1122
1123
1124    /***************************************************************************
1125     *                                                                         *
1126     * Stylesheet Handling                                                     *
1127     *                                                                         *
1128     **************************************************************************/
1129
1130
1131    private static final PseudoClass PSEUDO_CLASS_READONLY
1132            = PseudoClass.getPseudoClass("readonly");
1133
1134    /**
1135     * @treatAsPrivate implementation detail
1136     */
1137    private static class StyleableProperties {
1138        private static final FontCssMetaData<TextInputControl> FONT =
1139            new FontCssMetaData<TextInputControl>("-fx-font", Font.getDefault()) {
1140
1141            @Override
1142            public boolean isSettable(TextInputControl n) {
1143                return n.font == null || !n.font.isBound();
1144            }
1145
1146            @Override
1147            public StyleableProperty<Font> getStyleableProperty(TextInputControl n) {
1148                return (StyleableProperty<Font>)n.fontProperty();
1149            }
1150        };
1151
1152        private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1153        static {
1154            final List<CssMetaData<? extends Styleable, ?>> styleables =
1155                new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
1156            styleables.add(FONT);
1157            STYLEABLES = Collections.unmodifiableList(styleables);
1158        }
1159    }
1160
1161    /**
1162     * @return The CssMetaData associated with this class, which may include the
1163     * CssMetaData of its super classes.
1164     */
1165    public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1166        return StyleableProperties.STYLEABLES;
1167    }
1168
1169    /**
1170     * {@inheritDoc}
1171     */
1172    @Override
1173    public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
1174        return getClassCssMetaData();
1175    }
1176
1177}