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