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