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