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