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