1 /*
   2  * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javafx.scene.control;
  27 
  28 import com.sun.javafx.scene.control.FormatterAccessor;
  29 import javafx.beans.DefaultProperty;
  30 import javafx.beans.InvalidationListener;
  31 import javafx.beans.Observable;
  32 import javafx.beans.binding.IntegerBinding;
  33 import javafx.beans.binding.StringBinding;
  34 import javafx.beans.property.BooleanProperty;
  35 import javafx.beans.property.ObjectProperty;
  36 import javafx.beans.property.ObjectPropertyBase;
  37 import javafx.beans.property.ReadOnlyBooleanProperty;
  38 import javafx.beans.property.ReadOnlyBooleanWrapper;
  39 import javafx.beans.property.ReadOnlyIntegerProperty;
  40 import javafx.beans.property.ReadOnlyIntegerWrapper;
  41 import javafx.beans.property.ReadOnlyObjectProperty;
  42 import javafx.beans.property.ReadOnlyObjectWrapper;
  43 import javafx.beans.property.ReadOnlyStringProperty;
  44 import javafx.beans.property.ReadOnlyStringWrapper;
  45 import javafx.beans.property.SimpleBooleanProperty;
  46 import javafx.beans.property.SimpleStringProperty;
  47 import javafx.beans.property.StringProperty;
  48 import javafx.beans.value.ChangeListener;
  49 import javafx.beans.value.ObservableStringValue;
  50 import javafx.beans.value.ObservableValue;
  51 import javafx.beans.value.WritableValue;
  52 import javafx.css.CssMetaData;
  53 import javafx.css.FontCssMetaData;
  54 import javafx.css.PseudoClass;
  55 import javafx.css.StyleOrigin;
  56 import javafx.css.Styleable;
  57 import javafx.css.StyleableObjectProperty;
  58 import javafx.css.StyleableProperty;
  59 import javafx.scene.AccessibleAction;
  60 import javafx.scene.AccessibleAttribute;
  61 import javafx.scene.input.Clipboard;
  62 import javafx.scene.input.ClipboardContent;
  63 import javafx.scene.text.Font;
  64 
  65 import java.text.BreakIterator;
  66 import java.util.ArrayList;
  67 import java.util.Collections;
  68 import java.util.List;
  69 
  70 import com.sun.javafx.util.Utils;
  71 import com.sun.javafx.binding.ExpressionHelper;
  72 import com.sun.javafx.scene.NodeHelper;
  73 import javafx.util.StringConverter;
  74 
  75 /**
  76  * Abstract base class for text input controls.
  77  * @since JavaFX 2.0
  78  */
  79 @DefaultProperty("text")
  80 public abstract class TextInputControl extends Control {
  81     /**
  82      * Interface representing a text input's content. Since it is an ObservableStringValue,
  83      * you can also bind to, or observe the content.
  84      * @since JavaFX 2.0
  85      */
  86     protected interface Content extends ObservableStringValue {
  87         /**
  88          * Retrieves a subset of the content.
  89          *
  90          * @param start the start
  91          * @param end the end
  92          * @return a subset of the content
  93          */
  94         public String get(int start, int end);
  95 
  96         /**
  97          * Inserts a sequence of characters into the content.
  98          *
  99          * @param index the index
 100          * @param text the text string
 101          * @param notifyListeners the notify listener flag
 102          * @since JavaFX 2.1
 103          */
 104         public void insert(int index, String text, boolean notifyListeners);
 105 
 106         /**
 107          * Removes a sequence of characters from the content.
 108          *
 109          * @param start the start
 110          * @param end the end
 111          * @param notifyListeners the notify listener flag
 112          * @since JavaFX 2.1
 113          */
 114         public void delete(int start, int end, boolean notifyListeners);
 115 
 116         /**
 117          * Returns the number of characters represented by the content.
 118          * @return the number of characters
 119          */
 120         public int length();
 121     }
 122 
 123     /***************************************************************************
 124      *                                                                         *
 125      * Constructors                                                            *
 126      *                                                                         *
 127      **************************************************************************/
 128 
 129     /**
 130      * Creates a new TextInputControl. The content is an immutable property and
 131      * must be specified (as non-null) at the time of construction.
 132      *
 133      * @param content a non-null implementation of Content.
 134      */
 135     protected TextInputControl(final Content content) {
 136         this.content = content;
 137 
 138         // Add a listener so that whenever the Content is changed, we notify
 139         // listeners of the text property that it is invalid.
 140         content.addListener(observable -> {
 141             if (content.length() > 0) {
 142                 text.textIsNull = false;
 143             }
 144             text.controlContentHasChanged();
 145         });
 146 
 147         // Bind the length to be based on the length of the text property
 148         length.bind(new IntegerBinding() {
 149             { bind(text); }
 150             @Override protected int computeValue() {
 151                 String txt = text.get();
 152                 return txt == null ? 0 : txt.length();
 153             }
 154         });
 155 
 156         // Bind the selected text to be based on the selection and text properties
 157         selectedText.bind(new StringBinding() {
 158             { bind(selection, text); }
 159             @Override protected String computeValue() {
 160                 String txt = text.get();
 161                 IndexRange sel = selection.get();
 162                 if (txt == null || sel == null) return "";
 163 
 164                 int start = sel.getStart();
 165                 int end = sel.getEnd();
 166                 int length = txt.length();
 167                 if (end > start + length) end = length;
 168                 if (start > length-1) start = end = 0;
 169                 return txt.substring(start, end);
 170             }
 171         });
 172 
 173         focusedProperty().addListener((ob, o, n) -> {
 174             if (n) {
 175                 if (getTextFormatter() != null) {
 176                     updateText(getTextFormatter());
 177                 }
 178             } else {
 179                 commitValue();
 180             }
 181         });
 182 
 183         // Specify the default style class
 184         getStyleClass().add("text-input");
 185     }
 186 
 187     /***************************************************************************
 188      *                                                                         *
 189      * Properties                                                              *
 190      *                                                                         *
 191      **************************************************************************/
 192 
 193     /**
 194      * The default font to use for text in the TextInputControl. If the TextInputControl's text is
 195      * rich text then this font may or may not be used depending on the font
 196      * information embedded in the rich text, but in any case where a default
 197      * font is required, this font will be used.
 198      * @return the font property
 199      * @since JavaFX 8.0
 200      */
 201     public final ObjectProperty<Font> fontProperty() {
 202         if (font == null) {
 203             font = new StyleableObjectProperty<Font>(Font.getDefault()) {
 204 
 205 
 206                 private boolean fontSetByCss = false;
 207 
 208                 @Override
 209                 public void applyStyle(StyleOrigin newOrigin, Font value) {
 210 
 211                     //
 212                     // RT-20727 - if CSS is setting the font, then make sure invalidate doesn't call NodeHelper.reapplyCSS
 213                     //
 214                     try {
 215                         // super.applyStyle calls set which might throw if value is bound.
 216                         // Have to make sure fontSetByCss is reset.
 217                         fontSetByCss = true;
 218                         super.applyStyle(newOrigin, value);
 219                     } catch(Exception e) {
 220                         throw e;
 221                     } finally {
 222                         fontSetByCss = false;
 223                     }
 224 
 225                 }
 226 
 227 
 228                 @Override
 229                 public void set(Font value) {
 230                     final Font oldValue = get();
 231                     if (value == null ? oldValue == null : value.equals(oldValue)) {
 232                         return;
 233                     }
 234                     super.set(value);
 235                 }
 236 
 237                 @Override
 238                 protected void invalidated() {
 239                     // RT-20727 - if font is changed by calling setFont, then
 240                     // css might need to be reapplied since font size affects
 241                     // calculated values for styles with relative values
 242                     if(fontSetByCss == false) {
 243                         NodeHelper.reapplyCSS(TextInputControl.this);
 244                     }
 245                 }
 246 
 247                 @Override
 248                 public CssMetaData<TextInputControl,Font> getCssMetaData() {
 249                     return StyleableProperties.FONT;
 250                 }
 251 
 252                 @Override
 253                 public Object getBean() {
 254                     return TextInputControl.this;
 255                 }
 256 
 257                 @Override
 258                 public String getName() {
 259                     return "font";
 260                 }
 261             };
 262         }
 263         return font;
 264     }
 265 
 266     private ObjectProperty<Font> font;
 267     public final void setFont(Font value) { fontProperty().setValue(value); }
 268     public final Font getFont() { return font == null ? Font.getDefault() : font.getValue(); }
 269 
 270     /**
 271      * The prompt text to display in the {@code TextInputControl}. If set to null or an empty string, no
 272      * prompt text is displayed.
 273      *
 274      * @defaultValue An empty String
 275      * @since JavaFX 2.2
 276      */
 277     private StringProperty promptText = new SimpleStringProperty(this, "promptText", "") {
 278         @Override protected void invalidated() {
 279             // Strip out newlines
 280             String txt = get();
 281             if (txt != null && txt.contains("\n")) {
 282                 txt = txt.replace("\n", "");
 283                 set(txt);
 284             }
 285         }
 286     };
 287     public final StringProperty promptTextProperty() { return promptText; }
 288     public final String getPromptText() { return promptText.get(); }
 289     public final void setPromptText(String value) { promptText.set(value); }
 290 
 291 
 292     /**
 293      * The property contains currently attached {@link TextFormatter}.
 294      * Since the value is part of the {@code Formatter}, changing the TextFormatter will update the text based on the new textFormatter.
 295      *
 296      * @defaultValue null
 297      * @since JavaFX 8u40
 298      */
 299     private final ObjectProperty<TextFormatter<?>> textFormatter = new ObjectPropertyBase<TextFormatter<?>>() {
 300 
 301         private TextFormatter<?> oldFormatter = null;
 302 
 303         @Override
 304         public Object getBean() {
 305             return TextInputControl.this;
 306         }
 307 
 308         @Override
 309         public String getName() {
 310             return "textFormatter";
 311         }
 312 
 313         @Override
 314         protected void invalidated() {
 315             final TextFormatter<?> formatter = get();
 316             try {
 317                 if (formatter != null) {
 318                     try {
 319                         formatter.bindToControl(f -> updateText(f));
 320                     } catch (IllegalStateException e) {
 321                         if (isBound()) {
 322                             unbind();
 323                         }
 324                         set(null);
 325                         throw e;
 326                     }
 327                     if (!isFocused()) {
 328                         updateText(get());
 329                     }
 330                 }
 331 
 332                 if (oldFormatter != null) {
 333                     oldFormatter.unbindFromControl();
 334                 }
 335             } finally {
 336                 oldFormatter = formatter;
 337             }
 338         }
 339     };
 340     public final ObjectProperty<TextFormatter<?>> textFormatterProperty() { return textFormatter; }
 341     public final TextFormatter<?> getTextFormatter() { return textFormatter.get(); }
 342     public final void setTextFormatter(TextFormatter<?> value) { textFormatter.set(value); }
 343 
 344     private final Content content;
 345     /**
 346      * Returns the text input's content model.
 347      * @return the text input's content model
 348      */
 349     protected final Content getContent() {
 350         return content;
 351     }
 352 
 353     /**
 354      * The textual content of this TextInputControl.
 355      */
 356     private TextProperty text = new TextProperty();
 357     public final String getText() { return text.get(); }
 358     public final void setText(String value) { text.set(value); }
 359     public final StringProperty textProperty() { return text; }
 360 
 361     /**
 362      * The number of characters in the text input.
 363      */
 364     private ReadOnlyIntegerWrapper length = new ReadOnlyIntegerWrapper(this, "length");
 365     public final int getLength() { return length.get(); }
 366     public final ReadOnlyIntegerProperty lengthProperty() { return length.getReadOnlyProperty(); }
 367 
 368     /**
 369      * Indicates whether this TextInputControl can be edited by the user.
 370      */
 371     private BooleanProperty editable = new SimpleBooleanProperty(this, "editable", true) {
 372         @Override protected void invalidated() {
 373             pseudoClassStateChanged(PSEUDO_CLASS_READONLY, ! get());
 374         }
 375     };
 376     public final boolean isEditable() { return editable.getValue(); }
 377     public final void setEditable(boolean value) { editable.setValue(value); }
 378     public final BooleanProperty editableProperty() { return editable; }
 379 
 380     /**
 381      * The current selection.
 382      */
 383     private ReadOnlyObjectWrapper<IndexRange> selection = new ReadOnlyObjectWrapper<IndexRange>(this, "selection", new IndexRange(0, 0));
 384     public final IndexRange getSelection() { return selection.getValue(); }
 385     public final ReadOnlyObjectProperty<IndexRange> selectionProperty() { return selection.getReadOnlyProperty(); }
 386 
 387     /**
 388      * Defines the characters in the TextInputControl which are selected
 389      */
 390     private ReadOnlyStringWrapper selectedText = new ReadOnlyStringWrapper(this, "selectedText");
 391     public final String getSelectedText() { return selectedText.get(); }
 392     public final ReadOnlyStringProperty selectedTextProperty() { return selectedText.getReadOnlyProperty(); }
 393 
 394     /**
 395      * The <code>anchor</code> of the text selection.
 396      * The <code>anchor</code> and <code>caretPosition</code> make up the selection
 397      * range. Selection must always be specified in terms of begin &lt;= end, but
 398      * <code>anchor</code> may be less than, equal to, or greater than the
 399      * <code>caretPosition</code>. Depending on how the user selects text,
 400      * the anchor might represent the lower or upper bound of the selection.
 401      */
 402     private ReadOnlyIntegerWrapper anchor = new ReadOnlyIntegerWrapper(this, "anchor", 0);
 403     public final int getAnchor() { return anchor.get(); }
 404     public final ReadOnlyIntegerProperty anchorProperty() { return anchor.getReadOnlyProperty(); }
 405 
 406     /**
 407      * The current position of the caret within the text.
 408      * The <code>anchor</code> and <code>caretPosition</code> make up the selection
 409      * range. Selection must always be specified in terms of begin &lt;= end, but
 410      * <code>anchor</code> may be less than, equal to, or greater than the
 411      * <code>caretPosition</code>. Depending on how the user selects text,
 412      * the caretPosition might represent the lower or upper bound of the selection.
 413      */
 414     private ReadOnlyIntegerWrapper caretPosition = new ReadOnlyIntegerWrapper(this, "caretPosition", 0);
 415     public final int getCaretPosition() { return caretPosition.get(); }
 416     public final ReadOnlyIntegerProperty caretPositionProperty() { return caretPosition.getReadOnlyProperty(); }
 417 
 418     private UndoRedoChange undoChangeHead = new UndoRedoChange();
 419     private UndoRedoChange undoChange = undoChangeHead;
 420     private boolean createNewUndoRecord = false;
 421 
 422     /**
 423      * The property describes if it's currently possible to undo the latest change of the content that was done.
 424      * @defaultValue false
 425      * @since JavaFX 8u40
 426      */
 427     private final ReadOnlyBooleanWrapper undoable = new ReadOnlyBooleanWrapper(this, "undoable", false);
 428     public final boolean isUndoable() { return undoable.get(); }
 429     public final ReadOnlyBooleanProperty undoableProperty() { return undoable.getReadOnlyProperty(); }
 430 
 431 
 432     /**
 433      * The property describes if it's currently possible to redo the latest change of the content that was undone.
 434      * @defaultValue false
 435      * @since JavaFX 8u40
 436      */
 437     private final ReadOnlyBooleanWrapper redoable = new ReadOnlyBooleanWrapper(this, "redoable", false);
 438     public final boolean isRedoable() { return redoable.get(); }
 439     public final ReadOnlyBooleanProperty redoableProperty() { return redoable.getReadOnlyProperty(); }
 440 
 441     /***************************************************************************
 442      *                                                                         *
 443      * Methods                                                                 *
 444      *                                                                         *
 445      **************************************************************************/
 446 
 447     /**
 448      * Returns a subset of the text input's content.
 449      *
 450      * @param start must be a value between 0 and end - 1.
 451      * @param end must be less than or equal to the length
 452      * @return the subset of the text input's content
 453      */
 454     public String getText(int start, int end) {
 455         if (start > end) {
 456             throw new IllegalArgumentException("The start must be <= the end");
 457         }
 458 
 459         if (start < 0
 460             || end > getLength()) {
 461             throw new IndexOutOfBoundsException();
 462         }
 463 
 464         return getContent().get(start, end);
 465     }
 466 
 467     /**
 468      * Appends a sequence of characters to the content.
 469      *
 470      * @param text a non null String
 471      */
 472     public void appendText(String text) {
 473         insertText(getLength(), text);
 474     }
 475 
 476     /**
 477      * Inserts a sequence of characters into the content.
 478      *
 479      * @param index The location to insert the text.
 480      * @param text The text to insert.
 481      */
 482     public void insertText(int index, String text) {
 483         replaceText(index, index, text);
 484     }
 485 
 486     /**
 487      * Removes a range of characters from the content.
 488      *
 489      * @param range The range of text to delete. The range object must not be null.
 490      *
 491      * @see #deleteText(int, int)
 492      */
 493     public void deleteText(IndexRange range) {
 494         replaceText(range, "");
 495     }
 496 
 497     /**
 498      * Removes a range of characters from the content.
 499      *
 500      * @param start The starting index in the range, inclusive. This must be &gt;= 0 and &lt; the end.
 501      * @param end The ending index in the range, exclusive. This is one-past the last character to
 502      *            delete (consistent with the String manipulation methods). This must be &gt; the start,
 503      *            and &lt;= the length of the text.
 504      */
 505     public void deleteText(int start, int end) {
 506         replaceText(start, end, "");
 507     }
 508 
 509     /**
 510      * Replaces a range of characters with the given text.
 511      *
 512      * @param range The range of text to replace. The range object must not be null.
 513      * @param text The text that is to replace the range. This must not be null.
 514      *
 515      * @see #replaceText(int, int, String)
 516      */
 517     public void replaceText(IndexRange range, String text) {
 518         final int start = range.getStart();
 519         final int end = start + range.getLength();
 520         replaceText(start, end, text);
 521     }
 522 
 523     /**
 524      * Replaces a range of characters with the given text.
 525      *
 526      * @param start The starting index in the range, inclusive. This must be &gt;= 0 and &lt; the end.
 527      * @param end The ending index in the range, exclusive. This is one-past the last character to
 528      *            delete (consistent with the String manipulation methods). This must be &gt; the start,
 529      *            and &lt;= the length of the text.
 530      * @param text The text that is to replace the range. This must not be null.
 531      */
 532     public void replaceText(final int start, final int end, final String text) {
 533         if (start > end) {
 534             throw new IllegalArgumentException();
 535         }
 536 
 537         if (text == null) {
 538             throw new NullPointerException();
 539         }
 540 
 541         if (start < 0
 542             || end > getLength()) {
 543             throw new IndexOutOfBoundsException();
 544         }
 545 
 546         if (!this.text.isBound()) {
 547             final int oldLength = getLength();
 548             TextFormatter<?> formatter = getTextFormatter();
 549             TextFormatter.Change change = new TextFormatter.Change(this, getFormatterAccessor(), start, end, text);
 550             if (formatter != null && formatter.getFilter() != null) {
 551                 change = formatter.getFilter().apply(change);
 552                 if (change == null) {
 553                     return;
 554                 }
 555             }
 556 
 557             // Update the content
 558             updateContent(change, oldLength == 0);
 559 
 560         }
 561     }
 562 
 563     private void updateContent(TextFormatter.Change change, boolean forceNewUndoRecord) {
 564         final boolean nonEmptySelection = getSelection().getLength() > 0;
 565         String oldText = getText(change.start, change.end);
 566         int adjustmentAmount = replaceText(change.start, change.end, change.text, change.getAnchor(), change.getCaretPosition());
 567         String newText = getText(change.start, change.start + change.text.length() - adjustmentAmount);
 568         if (newText.equals(oldText)) {
 569             // Undo record not required as there is no change in the text.
 570             return;
 571         }
 572 
 573         // If you select some stuff and type anything, then we need to
 574         // create an undo record. If the range is a single character and
 575         // is right next to the index of the last undo record end index, then
 576         // we don't need to create a new undo record. In all other cases
 577         // we do.
 578         int endOfUndoChange = undoChange == undoChangeHead ? -1 : undoChange.start + undoChange.newText.length();
 579         if (createNewUndoRecord || nonEmptySelection || endOfUndoChange == -1 || forceNewUndoRecord ||
 580                 (endOfUndoChange != change.start && endOfUndoChange != change.end) || change.end - change.start > 1) {
 581             undoChange = undoChange.add(change.start, oldText, newText);
 582         } else if (change.start != change.end && change.text.isEmpty()) {
 583             // I know I am deleting, and am located at the end of the range of the current undo record
 584             if (undoChange.newText.length() > 0) {
 585                 undoChange.newText = undoChange.newText.substring(0, change.start - undoChange.start);
 586                 if (undoChange.newText.isEmpty()) {
 587                     // throw away this undo change record
 588                     undoChange = undoChange.discard();
 589                 }
 590             } else {
 591                 if (change.start == endOfUndoChange) {
 592                     undoChange.oldText += oldText;
 593                 } else { // end == endOfUndoChange
 594                     undoChange.oldText = oldText + undoChange.oldText;
 595                     undoChange.start--;
 596                 }
 597             }
 598         } else {
 599             // I know I am adding, and am located at the end of the range of the current undo record
 600             undoChange.newText += newText;
 601         }
 602         updateUndoRedoState();
 603     }
 604 
 605     /**
 606      * Transfers the currently selected range in the text to the clipboard,
 607      * removing the current selection.
 608      */
 609     public void cut() {
 610         copy();
 611         IndexRange selection = getSelection();
 612         deleteText(selection.getStart(), selection.getEnd());
 613     }
 614 
 615     /**
 616      * Transfers the currently selected range in the text to the clipboard,
 617      * leaving the current selection.
 618      */
 619      public void copy() {
 620         final String selectedText = getSelectedText();
 621         if (selectedText.length() > 0) {
 622             final ClipboardContent content = new ClipboardContent();
 623             content.putString(selectedText);
 624             Clipboard.getSystemClipboard().setContent(content);
 625         }
 626     }
 627 
 628     /**
 629      * Transfers the contents in the clipboard into this text,
 630      * replacing the current selection.  If there is no selection, the contents
 631      * in the clipboard is inserted at the current caret position.
 632      */
 633     public void paste() {
 634         final Clipboard clipboard = Clipboard.getSystemClipboard();
 635         if (clipboard.hasString()) {
 636             final String text = clipboard.getString();
 637             if (text != null) {
 638                 createNewUndoRecord = true;
 639                 try {
 640                     replaceSelection(text);
 641                 } finally {
 642                     createNewUndoRecord = false;
 643                 }
 644             }
 645         }
 646     }
 647 
 648     /**
 649      * Moves the selection backward one char in the text. This may have the
 650      * effect of deselecting, depending on the location of the anchor relative
 651      * to the caretPosition. This function effectively just moves the caretPosition.
 652      */
 653     public void selectBackward() {
 654         if (getCaretPosition() > 0 && getLength() > 0) {
 655             // because the anchor stays put, by moving the caret to the left
 656             // we ensure that a selection is registered and that it is correct
 657             if (charIterator == null) {
 658                 charIterator = BreakIterator.getCharacterInstance();
 659             }
 660             charIterator.setText(getText());
 661             selectRange(getAnchor(), charIterator.preceding(getCaretPosition()));
 662         }
 663     }
 664 
 665     /**
 666      * Moves the selection forward one char in the text. This may have the
 667      * effect of deselecting, depending on the location of the anchor relative
 668      * to the caretPosition. This function effectively just moves the caret forward.
 669      */
 670     public void selectForward() {
 671         final int textLength = getLength();
 672         if (textLength > 0 && getCaretPosition() < textLength) {
 673             if (charIterator == null) {
 674                 charIterator = BreakIterator.getCharacterInstance();
 675             }
 676             charIterator.setText(getText());
 677             selectRange(getAnchor(), charIterator.following(getCaretPosition()));
 678         }
 679     }
 680 
 681     /**
 682      * The break iterator instances for navigation over words and complex characters.
 683      */
 684     private BreakIterator charIterator;
 685     private BreakIterator wordIterator;
 686 
 687     /**
 688      * Moves the caret to the beginning of previous word. This function
 689      * also has the effect of clearing the selection.
 690      */
 691     public void previousWord() {
 692         previousWord(false);
 693     }
 694 
 695     /**
 696      * Moves the caret to the beginning of next word. This function
 697      * also has the effect of clearing the selection.
 698      */
 699     public void nextWord() {
 700         nextWord(false);
 701     }
 702 
 703     /**
 704      * Moves the caret to the end of the next word. This function
 705      * also has the effect of clearing the selection.
 706      */
 707     public void endOfNextWord() {
 708         endOfNextWord(false);
 709     }
 710 
 711     /**
 712      * Moves the caret to the beginning of previous word. This does not cause
 713      * the selection to be cleared. Rather, the anchor stays put and the caretPosition is
 714      * moved to the beginning of previous word.
 715      */
 716     public void selectPreviousWord() {
 717         previousWord(true);
 718     }
 719 
 720     /**
 721      * Moves the caret to the beginning of next word. This does not cause
 722      * the selection to be cleared. Rather, the anchor stays put and the caretPosition is
 723      * moved to the beginning of next word.
 724      */
 725     public void selectNextWord() {
 726         nextWord(true);
 727     }
 728 
 729     /**
 730      * Moves the caret to the end of the next word. This does not cause
 731      * the selection to be cleared.
 732      */
 733     public void selectEndOfNextWord() {
 734         endOfNextWord(true);
 735     }
 736 
 737     private void previousWord(boolean select) {
 738         final int textLength = getLength();
 739         final String text = getText();
 740         if (textLength <= 0) {
 741             return;
 742         }
 743 
 744         if (wordIterator == null) {
 745             wordIterator = BreakIterator.getWordInstance();
 746         }
 747         wordIterator.setText(text);
 748 
 749         int pos = wordIterator.preceding(Utils.clamp(0, getCaretPosition(), textLength));
 750 
 751         // Skip the non-word region, then move/select to the beginning of the word.
 752         while (pos != BreakIterator.DONE &&
 753                !Character.isLetterOrDigit(text.charAt(Utils.clamp(0, pos, textLength-1)))) {
 754             pos = wordIterator.preceding(Utils.clamp(0, pos, textLength));
 755         }
 756 
 757         // move/select
 758         selectRange(select ? getAnchor() : pos, pos);
 759     }
 760 
 761     private void nextWord(boolean select) {
 762         final int textLength = getLength();
 763         final String text = getText();
 764         if (textLength <= 0) {
 765             return;
 766         }
 767 
 768         if (wordIterator == null) {
 769             wordIterator = BreakIterator.getWordInstance();
 770         }
 771         wordIterator.setText(text);
 772 
 773         int last = wordIterator.following(Utils.clamp(0, getCaretPosition(), textLength-1));
 774         int current = wordIterator.next();
 775 
 776         // Skip whitespace characters to the beginning of next word, but
 777         // stop at newline. Then move the caret or select a range.
 778         while (current != BreakIterator.DONE) {
 779             for (int p=last; p<=current; p++) {
 780                 char ch = text.charAt(Utils.clamp(0, p, textLength-1));
 781                 // Avoid using Character.isSpaceChar() and Character.isWhitespace(),
 782                 // because they include LINE_SEPARATOR, PARAGRAPH_SEPARATOR, etc.
 783                 if (ch != ' ' && ch != '\t') {
 784                     if (select) {
 785                         selectRange(getAnchor(), p);
 786                     } else {
 787                         selectRange(p, p);
 788                     }
 789                     return;
 790                 }
 791             }
 792             last = current;
 793             current = wordIterator.next();
 794         }
 795 
 796         // move/select to the end
 797         if (select) {
 798             selectRange(getAnchor(), textLength);
 799         } else {
 800             end();
 801         }
 802     }
 803 
 804     private void endOfNextWord(boolean select) {
 805         final int textLength = getLength();
 806         final String text = getText();
 807         if (textLength <= 0) {
 808             return;
 809         }
 810 
 811         if (wordIterator == null) {
 812             wordIterator = BreakIterator.getWordInstance();
 813         }
 814         wordIterator.setText(text);
 815 
 816         int last = wordIterator.following(Utils.clamp(0, getCaretPosition(), textLength));
 817         int current = wordIterator.next();
 818 
 819         // skip the non-word region, then move/select to the end of the word.
 820         while (current != BreakIterator.DONE) {
 821             for (int p=last; p<=current; p++) {
 822                 if (!Character.isLetterOrDigit(text.charAt(Utils.clamp(0, p, textLength-1)))) {
 823                     if (select) {
 824                         selectRange(getAnchor(), p);
 825                     } else {
 826                         selectRange(p, p);
 827                     }
 828                     return;
 829                 }
 830             }
 831             last = current;
 832             current = wordIterator.next();
 833         }
 834 
 835         // move/select to the end
 836         if (select) {
 837             selectRange(getAnchor(), textLength);
 838         } else {
 839             end();
 840         }
 841     }
 842 
 843     /**
 844      * Selects all text in the text input.
 845      */
 846     public void selectAll() {
 847         selectRange(0, getLength());
 848     }
 849 
 850     /**
 851      * Moves the caret to before the first char of the text. This function
 852      * also has the effect of clearing the selection.
 853      */
 854     public void home() {
 855         // user wants to go to start
 856         selectRange(0, 0);
 857     }
 858 
 859     /**
 860      * Moves the caret to after the last char of the text. This function
 861      * also has the effect of clearing the selection.
 862      */
 863     public void end() {
 864         // user wants to go to end
 865         final int textLength = getLength();
 866         if (textLength > 0) {
 867             selectRange(textLength, textLength);
 868         }
 869     }
 870 
 871     /**
 872      * Moves the caret to before the first char of text. This does not cause
 873      * the selection to be cleared. Rather, the anchor stays put and the
 874      * caretPosition is moved to before the first char.
 875      */
 876     public void selectHome() {
 877         selectRange(getAnchor(), 0);
 878     }
 879 
 880     /**
 881      * Moves the caret to after the last char of text. This does not cause
 882      * the selection to be cleared. Rather, the anchor stays put and the
 883      * caretPosition is moved to after the last char.
 884      */
 885     public void selectEnd() {
 886         final int textLength = getLength();
 887         if (textLength > 0) selectRange(getAnchor(), textLength);
 888     }
 889 
 890     /**
 891      * Deletes the character that precedes the current caret position from the
 892      * text if there is no selection, or deletes the selection if there is one.
 893      * This function returns true if the deletion succeeded, false otherwise.
 894      * @return true if the deletion succeeded, false otherwise
 895      */
 896     public boolean deletePreviousChar() {
 897         boolean failed = true;
 898         if (isEditable() && !isDisabled()) {
 899             final String text = getText();
 900             final int dot = getCaretPosition();
 901             final int mark = getAnchor();
 902             if (dot != mark) {
 903                 // there is a selection of text to remove
 904                 replaceSelection("");
 905                 failed = false;
 906             } else if (dot > 0) {
 907                 // The caret is not at the beginning, so remove some characters.
 908                 // Typically you'd only be removing a single character, but
 909                 // in some cases you must remove two depending on the unicode
 910                 // characters
 911                 // Note: Do not use charIterator here, because we do want to
 912                 // break up clusters when deleting backwards.
 913                 int p = Character.offsetByCodePoints(text, dot, -1);
 914                 deleteText(p, dot);
 915                 failed = false;
 916             }
 917         }
 918         return !failed;
 919     }
 920 
 921     /**
 922      * Deletes the character that follows the current caret position from the
 923      * text if there is no selection, or deletes the selection if there is one.
 924      * This function returns true if the deletion succeeded, false otherwise.
 925      * @return true if the deletion succeeded, false otherwise
 926      */
 927     public boolean deleteNextChar() {
 928         boolean failed = true;
 929         if (isEditable() && !isDisabled()) {
 930             final int textLength = getLength();
 931             final String text = getText();
 932             final int dot = getCaretPosition();
 933             final int mark = getAnchor();
 934             if (dot != mark) {
 935                 // there is a selection of text to remove
 936                 replaceSelection("");
 937                 failed = false;
 938             } else if (textLength > 0 && dot < textLength) {
 939                 // The caret is not at the end, so remove some characters.
 940                 // Typically you'd only be removing a single character, but
 941                 // in some cases you must remove two depending on the unicode
 942                 // characters
 943                 if (charIterator == null) {
 944                     charIterator = BreakIterator.getCharacterInstance();
 945                 }
 946                 charIterator.setText(text);
 947                 int p = charIterator.following(dot);
 948                 deleteText(dot, p);
 949                 failed = false;
 950             }
 951         }
 952         return !failed;
 953     }
 954 
 955     /**
 956      * Moves the caret position forward. If there is no selection, then the
 957      * caret position is moved one character forward. If there is a selection,
 958      * then the caret position is moved to the end of the selection and
 959      * the selection cleared.
 960      */
 961     public void forward() {
 962         // user has moved caret to the right
 963         final int textLength = getLength();
 964         final int dot = getCaretPosition();
 965         final int mark = getAnchor();
 966         if (dot != mark) {
 967             int pos = Math.max(dot, mark);
 968             selectRange(pos, pos);
 969         } else if (dot < textLength && textLength > 0) {
 970             if (charIterator == null) {
 971                 charIterator = BreakIterator.getCharacterInstance();
 972             }
 973             charIterator.setText(getText());
 974             int pos = charIterator.following(dot);
 975             selectRange(pos, pos);
 976         }
 977         deselect();
 978     }
 979 
 980     /**
 981      * Moves the caret position backward. If there is no selection, then the
 982      * caret position is moved one character backward. If there is a selection,
 983      * then the caret position is moved to the beginning of the selection and
 984      * the selection cleared.
 985      *
 986      * Note: This function is intended to be used by experts, primarily
 987      *       by those implementing new Skins or Behaviors. It is not common
 988      *       for developers or designers to access this function directly.
 989      */
 990     public void backward() {
 991         // user has moved caret to the left
 992         final int textLength = getLength();
 993         final int dot = getCaretPosition();
 994         final int mark = getAnchor();
 995         if (dot != mark) {
 996             int pos = Math.min(dot, mark);
 997             selectRange(pos, pos);
 998         } else if (dot > 0 && textLength > 0) {
 999             if (charIterator == null) {
1000                 charIterator = BreakIterator.getCharacterInstance();
1001             }
1002             charIterator.setText(getText());
1003             int pos = charIterator.preceding(dot);
1004             selectRange(pos, pos);
1005         }
1006         deselect();
1007     }
1008 
1009     /**
1010      * Positions the caret to the position indicated by {@code pos}. This
1011      * function will also clear the selection.
1012      * @param pos the position
1013      */
1014     public void positionCaret(int pos) {
1015         final int p = Utils.clamp(0, pos, getLength());
1016         selectRange(p, p);
1017     }
1018 
1019     /**
1020      * Positions the caret to the position indicated by {@code pos} and extends
1021      * the selection, if there is one. If there is no selection, then a
1022      * selection is formed where the anchor is at the current caret position
1023      * and the caretPosition is moved to pos.
1024      * @param pos the position
1025      */
1026     public void selectPositionCaret(int pos) {
1027         selectRange(getAnchor(), Utils.clamp(0, pos, getLength()));
1028     }
1029 
1030     /**
1031      * Positions the anchor and caretPosition explicitly.
1032      * @param anchor the anchor
1033      * @param caretPosition the caretPosition
1034      */
1035     public void selectRange(int anchor, int caretPosition) {
1036         caretPosition = Utils.clamp(0, caretPosition, getLength());
1037         anchor = Utils.clamp(0, anchor, getLength());
1038 
1039         TextFormatter.Change change = new TextFormatter.Change(this, getFormatterAccessor(), anchor, caretPosition);
1040         TextFormatter<?> formatter = getTextFormatter();
1041         if (formatter != null && formatter.getFilter() != null) {
1042             change = formatter.getFilter().apply(change);
1043             if (change == null) {
1044                 return;
1045             }
1046         }
1047 
1048         updateContent(change, false);
1049     }
1050 
1051     private void doSelectRange(int anchor, int caretPosition) {
1052         this.caretPosition.set(Utils.clamp(0, caretPosition, getLength()));
1053         this.anchor.set(Utils.clamp(0, anchor, getLength()));
1054         this.selection.set(IndexRange.normalize(getAnchor(), getCaretPosition()));
1055         notifyAccessibleAttributeChanged(AccessibleAttribute.SELECTION_START);
1056     }
1057 
1058     /**
1059      * This function will extend the selection to include the specified pos.
1060      * This is different from selectPositionCaret in that it does not simply
1061      * move the caret. Rather, it will reposition the caret and anchor as necessary
1062      * to ensure that pos becomes the new caret and the far other end of the
1063      * selection becomes the anchor.
1064      * @param pos the position
1065      */
1066     public void extendSelection(int pos) {
1067         final int p = Utils.clamp(0, pos, getLength());
1068         final int dot = getCaretPosition();
1069         final int mark = getAnchor();
1070         int start = Math.min(dot, mark);
1071         int end = Math.max(dot, mark);
1072         if (p < start) {
1073             selectRange(end, p);
1074         } else {
1075             selectRange(start, p);
1076         }
1077     }
1078 
1079     /**
1080      * Clears the text.
1081      */
1082     public void clear() {
1083         deselect();
1084         if (!text.isBound()) {
1085             setText("");
1086         }
1087     }
1088 
1089     /**
1090      * Clears the selection.
1091      */
1092     public void deselect() {
1093         // set the anchor equal to the caret position, which clears the selection
1094         // while also preserving the caret position
1095         selectRange(getCaretPosition(), getCaretPosition());
1096     }
1097 
1098     /**
1099      * Replaces the selection with the given replacement String. If there is
1100      * no selection, then the replacement text is simply inserted at the current
1101      * caret position. If there was a selection, then the selection is cleared
1102      * and the given replacement text inserted.
1103      * @param replacement the replacement string
1104      */
1105     public void replaceSelection(String replacement) {
1106         replaceText(getSelection(), replacement);
1107     }
1108 
1109     /**
1110      * If possible, undoes the last modification. If {@link #isUndoable()} returns
1111      * false, then calling this method has no effect.
1112      * @since JavaFX 8u40
1113      */
1114     public final void undo() {
1115         if (isUndoable()) {
1116             // Apply reverse change here
1117             final int start = undoChange.start;
1118             final String newText = undoChange.newText;
1119             final String oldText = undoChange.oldText;
1120 
1121             if (newText != null) {
1122                 getContent().delete(start, start + newText.length(), oldText.isEmpty());
1123             }
1124 
1125             if (oldText != null) {
1126                 getContent().insert(start, oldText, true);
1127                 doSelectRange(start, start + oldText.length());
1128             } else {
1129                 doSelectRange(start, start + newText.length());
1130             }
1131 
1132             undoChange = undoChange.prev;
1133         }
1134         updateUndoRedoState();
1135     }
1136 
1137     /**
1138      * If possible, redoes the last undone modification. If {@link #isRedoable()} returns
1139      * false, then calling this method has no effect.
1140      * @since JavaFX 8u40
1141      */
1142     public final void redo() {
1143         if (isRedoable()) {
1144             // Apply change here
1145             undoChange = undoChange.next;
1146             final int start = undoChange.start;
1147             final String newText = undoChange.newText;
1148             final String oldText = undoChange.oldText;
1149 
1150             if (oldText != null) {
1151                 getContent().delete(start, start + oldText.length(), newText.isEmpty());
1152             }
1153 
1154             if (newText != null) {
1155                 getContent().insert(start, newText, true);
1156                 doSelectRange(start + newText.length(), start + newText.length());
1157             } else {
1158                 doSelectRange(start, start);
1159             }
1160         }
1161         updateUndoRedoState();
1162         // else beep ?
1163     }
1164 
1165     // Used by TextArea, although there are probably other better ways of
1166     // doing this.
1167     void textUpdated() { }
1168 
1169     private void resetUndoRedoState() {
1170         undoChange = undoChangeHead;
1171         undoChange.next = null;
1172         updateUndoRedoState();
1173     }
1174 
1175     private void updateUndoRedoState() {
1176         undoable.set(undoChange != undoChangeHead);
1177         redoable.set(undoChange.next != null);
1178     }
1179 
1180     private boolean filterAndSet(String value) {
1181         // Send the new value through the textFormatter, if one exists.
1182         TextFormatter<?> formatter = getTextFormatter();
1183         int length = content.length();
1184         if (formatter != null && formatter.getFilter() != null && !text.isBound()) {
1185             TextFormatter.Change change = new TextFormatter.Change(
1186                     TextInputControl.this, getFormatterAccessor(), 0, length, value, 0, 0);
1187             change = formatter.getFilter().apply(change);
1188             if (change == null) {
1189                 return false;
1190             }
1191             replaceText(change.start, change.end, change.text, change.getAnchor(), change.getCaretPosition());
1192         } else {
1193             replaceText(0, length, value, 0, 0);
1194         }
1195         return true;
1196     }
1197 
1198     /**
1199      * This is what is ultimately called by every code path that will update
1200      * the content (except for undo / redo). The input into this method has
1201      * already run through the textFormatter where appropriate.
1202      *
1203      * @param start            The start index into the existing text which
1204      *                         will be replaced by the new value
1205      * @param end              The end index into the existing text which will
1206      *                         be replaced by the new value. As with
1207      *                         String.replace this is a lastIndex+1 value
1208      * @param value            The new text value
1209      * @param anchor           The new selection anchor after the change is made
1210      * @param caretPosition    The new selection caretPosition after the change
1211      *                         is made.
1212      * @return The amount of adjustment made to the end / anchor / caretPosition to
1213      *         accommodate for subsequent filtering (such as the filtering of
1214      *         new lines by the TextField)
1215      */
1216     private int replaceText(int start, int end, String value, int anchor, int caretPosition) {
1217         // RT-16566: Need to take into account stripping of chars into the
1218         // final anchor & caret position
1219         int length = getLength();
1220         int adjustmentAmount = 0;
1221         if (end != start) {
1222             getContent().delete(start, end, value.isEmpty());
1223             length -= (end - start);
1224         }
1225         if (value != null) {
1226             getContent().insert(start, value, true);
1227             adjustmentAmount = value.length() - (getLength() - length);
1228             anchor -= adjustmentAmount;
1229             caretPosition -= adjustmentAmount;
1230         }
1231         doSelectRange(anchor, caretPosition);
1232         return adjustmentAmount;
1233     }
1234 
1235     private <T> void updateText(TextFormatter<T> formatter) {
1236         T value = formatter.getValue();
1237         StringConverter<T> converter = formatter.getValueConverter();
1238         if (converter != null) {
1239             String text = converter.toString(value);
1240             if (text == null) text = "";
1241             replaceText(0, getLength(), text, text.length(), text.length());
1242         }
1243     }
1244 
1245     /**
1246      * Commit the current text and convert it to a value.
1247      * @since JavaFX 8u40
1248      */
1249     public final void commitValue() {
1250         if (getTextFormatter() != null) {
1251             getTextFormatter().updateValue(getText());
1252         }
1253     }
1254 
1255     /**
1256      * If the field is currently being edited, this call will set text to the last commited value.
1257      * @since JavaFX 8u40
1258      */
1259     public final void cancelEdit() {
1260         if (getTextFormatter() != null) {
1261             updateText(getTextFormatter());
1262         }
1263     }
1264 
1265     private FormatterAccessor accessor;
1266 
1267     private FormatterAccessor getFormatterAccessor() {
1268         if (accessor == null) {
1269             accessor = new TextInputControlFromatterAccessor();
1270         }
1271         return accessor;
1272     }
1273 
1274 
1275     /**
1276      * A little utility method for stripping out unwanted characters.
1277      *
1278      * @param txt
1279      * @param stripNewlines
1280      * @param stripTabs
1281      * @return The string after having the unwanted characters stripped out.
1282      */
1283     static String filterInput(String txt, boolean stripNewlines, boolean stripTabs) {
1284         // Most of the time, when text is inserted, there are no illegal
1285         // characters. So we'll do a "cheap" check for illegal characters.
1286         // If we find one, we'll do a longer replace algorithm. In the
1287         // case of illegal characters, this may at worst be an O(2n) solution.
1288         // Strip out any characters that are outside the printed range
1289         if (containsInvalidCharacters(txt, stripNewlines, stripTabs)) {
1290             StringBuilder s = new StringBuilder(txt.length());
1291             for (int i=0; i<txt.length(); i++) {
1292                 final char c = txt.charAt(i);
1293                 if (!isInvalidCharacter(c, stripNewlines, stripTabs)) {
1294                     s.append(c);
1295                 }
1296             }
1297             txt = s.toString();
1298         }
1299         return txt;
1300     }
1301 
1302     static boolean containsInvalidCharacters(String txt, boolean newlineIllegal, boolean tabIllegal) {
1303         for (int i=0; i<txt.length(); i++) {
1304             final char c = txt.charAt(i);
1305             if (isInvalidCharacter(c, newlineIllegal, tabIllegal)) return true;
1306         }
1307         return false;
1308     }
1309 
1310     private static boolean isInvalidCharacter(char c, boolean newlineIllegal, boolean tabIllegal) {
1311         if (c == 0x7F) return true;
1312         if (c == 0xA) return newlineIllegal;
1313         if (c == 0x9) return tabIllegal;
1314         if (c < 0x20) return true;
1315         return false;
1316     }
1317 
1318     // It can be bound, in which case we will force it to be an eager
1319     // binding so that we update the content eagerly
1320     // It can be bidirectionally bound, which basically will just work
1321     // If somebody changes the content directly, it will be notified and
1322     // send an invalidation event.
1323     private class TextProperty extends StringProperty {
1324         // This is used only when the property is bound
1325         private ObservableValue<? extends String> observable = null;
1326         // Added to the observable when bound
1327         private InvalidationListener listener = null;
1328         // Used for event handling
1329         private ExpressionHelper<String> helper = null;
1330         // The developer my set the Text property to null. Although
1331         // the Content must be given an empty String, we must still
1332         // treat the value as though it were null, so that a subsequent
1333         // getText() will return null.
1334         private boolean textIsNull = false;
1335 
1336         @Override public String get() {
1337             // Since we force eager binding and content is always up to date,
1338             // we just need to get it from content and not through the binding
1339             return textIsNull ? null : content.get();
1340         }
1341 
1342         @Override public void set(String value) {
1343             if (isBound()) {
1344                 throw new java.lang.RuntimeException("A bound value cannot be set.");
1345             }
1346             doSet(value);
1347             markInvalid();
1348         }
1349 
1350         /**
1351          * Called whenever the content on the control has changed (as determined
1352          * by a listener on the content).
1353          */
1354         private void controlContentHasChanged() {
1355             markInvalid();
1356             notifyAccessibleAttributeChanged(AccessibleAttribute.TEXT);
1357         }
1358 
1359         @Override public void bind(ObservableValue<? extends String> observable) {
1360             if (observable == null) {
1361                 throw new NullPointerException("Cannot bind to null");
1362             }
1363             if (!observable.equals(this.observable)) {
1364                 unbind();
1365                 this.observable = observable;
1366                 if (listener == null) {
1367                     listener = new Listener();
1368                 }
1369                 this.observable.addListener(listener);
1370                 markInvalid();
1371                 doSet(observable.getValue());
1372             }
1373         }
1374 
1375         @Override public void unbind() {
1376             if (observable != null) {
1377                 doSet(observable.getValue());
1378                 observable.removeListener(listener);
1379                 observable = null;
1380             }
1381         }
1382 
1383         @Override public boolean isBound() {
1384             return observable != null;
1385         }
1386 
1387         @Override public void addListener(InvalidationListener listener) {
1388             helper = ExpressionHelper.addListener(helper, this, listener);
1389         }
1390 
1391         @Override public void removeListener(InvalidationListener listener) {
1392             helper = ExpressionHelper.removeListener(helper, listener);
1393         }
1394 
1395         @Override public void addListener(ChangeListener<? super String> listener) {
1396             helper = ExpressionHelper.addListener(helper, this, listener);
1397         }
1398 
1399         @Override public void removeListener(ChangeListener<? super String> listener) {
1400             helper = ExpressionHelper.removeListener(helper, listener);
1401         }
1402 
1403         @Override public Object getBean() {
1404             return TextInputControl.this;
1405         }
1406 
1407         @Override public String getName() {
1408             return "text";
1409         }
1410 
1411         private void fireValueChangedEvent() {
1412             ExpressionHelper.fireValueChangedEvent(helper);
1413         }
1414 
1415         private void markInvalid() {
1416             fireValueChangedEvent();
1417         }
1418 
1419         /**
1420          * doSet is called whenever the setText() method was called directly
1421          * on the TextInputControl, or when the text property was bound,
1422          * unbound, or reacted to a binding invalidation. It is *not* called
1423          * when modifications to the content happened indirectly, such as
1424          * through the replaceText / replaceSelection methods.
1425          *
1426          * @param value The new value
1427          */
1428         private void doSet(String value) {
1429             // Guard against the null value.
1430             textIsNull = value == null;
1431             if (value == null) value = "";
1432 
1433             if (!filterAndSet(value)) return;
1434 
1435             if (getTextFormatter() != null) {
1436                 getTextFormatter().updateValue(getText());
1437             }
1438 
1439             textUpdated();
1440 
1441             // If the programmer has directly manipulated the text property
1442             // or has it bound up, then we will clear out any modifications
1443             // from the undo manager as we must suppose that the control is
1444             // being reused, for example, between forms.
1445             resetUndoRedoState();
1446         }
1447 
1448         private class Listener implements InvalidationListener {
1449             @Override
1450             public void invalidated(Observable valueModel) {
1451                 // We now need to force it to be eagerly recomputed
1452                 // because we need to push these changes to the
1453                 // content model. Because changing the model ends
1454                 // up calling invalidate and markInvalid, the
1455                 // listeners will all be notified.
1456                 doSet(observable.getValue());
1457             }
1458         }
1459     }
1460 
1461     /**
1462      * Used to form a linked-list of Undo / Redo changes. Each UndoRedoChange
1463      * records the old and new text, and the start index. It also has
1464      * the links to the previous and next Changes in the chain. There
1465      * are two special UndoRedoChange objects in this chain representing the
1466      * head and the tail so we can have beforeFirst and afterLast
1467      * behavior as necessary.
1468      */
1469     static class UndoRedoChange {
1470         int start;
1471         String oldText;
1472         String newText;
1473         UndoRedoChange prev;
1474         UndoRedoChange next;
1475 
1476         UndoRedoChange() { }
1477 
1478         public UndoRedoChange add(int start, String oldText, String newText) {
1479             UndoRedoChange c = new UndoRedoChange();
1480             c.start = start;
1481             c.oldText = oldText;
1482             c.newText = newText;
1483             c.prev = this;
1484             next = c;
1485             return c;
1486         }
1487 
1488         public UndoRedoChange discard() {
1489             prev.next = next;
1490             return prev;
1491         }
1492 
1493         // Handy to use when debugging, just put it in undo or redo
1494         // method or replaceText to see what is happening to the undo
1495         // history as it occurs.
1496         void debugPrint() {
1497             UndoRedoChange c = this;
1498             System.out.print("[");
1499             while (c != null) {
1500                 System.out.print(c.toString());
1501                 if (c.next != null) System.out.print(", ");
1502                 c = c.next;
1503             }
1504             System.out.println("]");
1505         }
1506 
1507         @Override public String toString() {
1508             if (oldText == null && newText == null) {
1509                 return "head";
1510             }
1511             if (oldText.isEmpty() && !newText.isEmpty()) {
1512                 return "added '" + newText + "' at index " + start;
1513             } else if (!oldText.isEmpty() && !newText.isEmpty()) {
1514                 return "replaced '" + oldText + "' with '" + newText + "' at index " + start;
1515             } else {
1516                 return "deleted '" + oldText + "' at index " + start;
1517             }
1518         }
1519     }
1520 
1521     /***************************************************************************
1522      *                                                                         *
1523      * Stylesheet Handling                                                     *
1524      *                                                                         *
1525      **************************************************************************/
1526 
1527 
1528     private static final PseudoClass PSEUDO_CLASS_READONLY
1529             = PseudoClass.getPseudoClass("readonly");
1530 
1531     private static class StyleableProperties {
1532         private static final FontCssMetaData<TextInputControl> FONT =
1533             new FontCssMetaData<TextInputControl>("-fx-font", Font.getDefault()) {
1534 
1535             @Override
1536             public boolean isSettable(TextInputControl n) {
1537                 return n.font == null || !n.font.isBound();
1538             }
1539 
1540             @Override
1541             public StyleableProperty<Font> getStyleableProperty(TextInputControl n) {
1542                 return (StyleableProperty<Font>)(WritableValue<Font>)n.fontProperty();
1543             }
1544         };
1545 
1546         private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES;
1547         static {
1548             final List<CssMetaData<? extends Styleable, ?>> styleables =
1549                 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData());
1550             styleables.add(FONT);
1551             STYLEABLES = Collections.unmodifiableList(styleables);
1552         }
1553     }
1554 
1555     /**
1556      * @return The CssMetaData associated with this class, which may include the
1557      * CssMetaData of its superclasses.
1558      * @since JavaFX 8.0
1559      */
1560     public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() {
1561         return StyleableProperties.STYLEABLES;
1562     }
1563 
1564     /**
1565      * {@inheritDoc}
1566      * @since JavaFX 8.0
1567      */
1568     @Override
1569     public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() {
1570         return getClassCssMetaData();
1571     }
1572 
1573 
1574     /***************************************************************************
1575      *                                                                         *
1576      * Accessibility handling                                                  *
1577      *                                                                         *
1578      **************************************************************************/
1579 
1580     /** {@inheritDoc} */
1581     @Override
1582     public Object queryAccessibleAttribute(AccessibleAttribute attribute, Object... parameters) {
1583         switch (attribute) {
1584             case TEXT: {
1585                 String accText = getAccessibleText();
1586                 if (accText != null && !accText.isEmpty()) return accText;
1587 
1588                 String text = getText();
1589                 if (text == null || text.isEmpty()) {
1590                     text = getPromptText();
1591                 }
1592                 return text;
1593             }
1594             case EDITABLE: return isEditable();
1595             case SELECTION_START: return getSelection().getStart();
1596             case SELECTION_END: return getSelection().getEnd();
1597             case CARET_OFFSET: return getCaretPosition();
1598             case FONT: return getFont();
1599             default: return super.queryAccessibleAttribute(attribute, parameters);
1600         }
1601     }
1602 
1603     /** {@inheritDoc} */
1604     @Override
1605     public void executeAccessibleAction(AccessibleAction action, Object... parameters) {
1606         switch (action) {
1607             case SET_TEXT: {
1608                 String value = (String) parameters[0];
1609                 if (value != null) setText(value);
1610                 break;
1611             }
1612             case SET_TEXT_SELECTION: {
1613                 Integer start = (Integer) parameters[0];
1614                 Integer end = (Integer) parameters[1];
1615                 if (start != null && end != null) {
1616                     selectRange(start,  end);
1617                 }
1618                 break;
1619             }
1620             default: super.executeAccessibleAction(action, parameters);
1621         }
1622     }
1623 
1624     private class TextInputControlFromatterAccessor implements FormatterAccessor {
1625         @Override
1626         public int getTextLength() {
1627             return TextInputControl.this.getLength();
1628         }
1629 
1630         @Override
1631         public String getText(int begin, int end) {
1632             return TextInputControl.this.getText(begin, end);
1633         }
1634 
1635         @Override
1636         public int getCaret() {
1637             return TextInputControl.this.getCaretPosition();
1638         }
1639 
1640         @Override
1641         public int getAnchor() {
1642             return TextInputControl.this.getAnchor();
1643         }
1644     }
1645 
1646 }