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