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