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