Spec-Zone .ru
спецификации, руководства, описания, API
|
001/* 002 * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. 003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 004 * 005 * This code is free software; you can redistribute it and/or modify it 006 * under the terms of the GNU General Public License version 2 only, as 007 * published by the Free Software Foundation. Oracle designates this 008 * particular file as subject to the "Classpath" exception as provided 009 * by Oracle in the LICENSE file that accompanied this code. 010 * 011 * This code is distributed in the hope that it will be useful, but WITHOUT 012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 014 * version 2 for more details (a copy is included in the LICENSE file that 015 * accompanied this code). 016 * 017 * You should have received a copy of the GNU General Public License version 018 * 2 along with this work; if not, write to the Free Software Foundation, 019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 020 * 021 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 022 * or visit www.oracle.com if you need additional information or have any 023 * questions. 024 */ 025 026package javafx.scene.control; 027 028import java.text.BreakIterator; 029import java.util.ArrayList; 030import java.util.Collections; 031import java.util.List; 032import javafx.beans.DefaultProperty; 033import javafx.beans.InvalidationListener; 034import javafx.beans.Observable; 035import javafx.beans.binding.IntegerBinding; 036import javafx.beans.binding.StringBinding; 037import javafx.beans.property.BooleanProperty; 038import javafx.beans.property.ObjectProperty; 039import javafx.beans.property.ReadOnlyIntegerProperty; 040import javafx.beans.property.ReadOnlyIntegerWrapper; 041import javafx.beans.property.ReadOnlyObjectProperty; 042import javafx.beans.property.ReadOnlyObjectWrapper; 043import javafx.beans.property.ReadOnlyStringProperty; 044import javafx.beans.property.ReadOnlyStringWrapper; 045import javafx.beans.property.SimpleBooleanProperty; 046import javafx.beans.property.SimpleStringProperty; 047import javafx.beans.property.StringProperty; 048import javafx.beans.value.ChangeListener; 049import javafx.beans.value.ObservableStringValue; 050import javafx.beans.value.ObservableValue; 051import javafx.css.CssMetaData; 052import javafx.css.FontCssMetaData; 053import javafx.css.PseudoClass; 054import javafx.css.StyleOrigin; 055import javafx.css.StyleableObjectProperty; 056import javafx.css.StyleableProperty; 057import javafx.scene.input.Clipboard; 058import javafx.scene.input.ClipboardContent; 059import javafx.scene.text.Font; 060import com.sun.javafx.Utils; 061import com.sun.javafx.binding.ExpressionHelper; 062import javafx.css.Styleable; 063 064/** 065 * Abstract base class for text input controls. 066 */ 067@DefaultProperty("text") 068public abstract class TextInputControl extends Control { 069 /** 070 * Interface representing a text input's content. Since it is an ObservableStringValue, 071 * you can also bind to, or observe the content. 072 */ 073 protected interface Content extends ObservableStringValue { 074 /** 075 * Retrieves a subset of the content. 076 * 077 * @param start 078 * @param end 079 */ 080 public String get(int start, int end); 081 082 /** 083 * Inserts a sequence of characters into the content. 084 * 085 * @param index 086 * @param text 087 */ 088 public void insert(int index, String text, boolean notifyListeners); 089 090 /** 091 * Removes a sequence of characters from the content. 092 * 093 * @param start 094 * @param end 095 */ 096 public void delete(int start, int end, boolean notifyListeners); 097 098 /** 099 * Returns the number of characters represented by the content. 100 */ 101 public int length(); 102 } 103 104 /*************************************************************************** 105 * * 106 * Constructors * 107 * * 108 **************************************************************************/ 109 110 /** 111 * Creates a new TextInputControl. The content is an immutable property and 112 * must be specified (as non-null) at the time of construction. 113 * 114 * @param content a non-null implementation of Content. 115 */ 116 protected TextInputControl(final Content content) { 117 this.content = content; 118 119 // Add a listener so that whenever the Content is changed, we notify 120 // listeners of the text property that it is invalid. 121 content.addListener(new InvalidationListener() { 122 @Override public void invalidated(Observable observable) { 123 if (content.length() > 0) { 124 text.textIsNull = false; 125 } 126 text.invalidate(); 127 } 128 }); 129 130 // Bind the length to be based on the length of the text property 131 length.bind(new IntegerBinding() { 132 { bind(text); } 133 @Override protected int computeValue() { 134 String txt = text.get(); 135 return txt == null ? 0 : txt.length(); 136 } 137 }); 138 139 // Bind the selected text to be based on the selection and text properties 140 selectedText.bind(new StringBinding() { 141 { bind(selection, text); } 142 @Override protected String computeValue() { 143 String txt = text.get(); 144 IndexRange sel = selection.get(); 145 if (txt == null || sel == null) return ""; 146 147 int start = sel.getStart(); 148 int end = sel.getEnd(); 149 int length = txt.length(); 150 if (end > start + length) end = length; 151 if (start > length-1) start = end = 0; 152 return txt.substring(start, end); 153 } 154 }); 155 156 // Specify the default style class 157 getStyleClass().add("text-input"); 158 } 159 160 /*************************************************************************** 161 * * 162 * Properties * 163 * * 164 **************************************************************************/ 165 166 /** 167 * The default font to use for text in the TextInputControl. If the TextInputControl's text is 168 * rich text then this font may or may not be used depending on the font 169 * information embedded in the rich text, but in any case where a default 170 * font is required, this font will be used. 171 */ 172 public final ObjectProperty<Font> fontProperty() { 173 if (font == null) { 174 font = new StyleableObjectProperty<Font>(Font.getDefault()) { 175 176 177 private boolean fontSetByCss = false; 178 179 @Override 180 public void applyStyle(StyleOrigin newOrigin, Font value) { 181 182 // 183 // RT-20727 - if CSS is setting the font, then make sure invalidate doesn't call impl_reapplyCSS 184 // 185 try { 186 // super.applyStyle calls set which might throw if value is bound. 187 // Have to make sure fontSetByCss is reset. 188 fontSetByCss = true; 189 super.applyStyle(newOrigin, value); 190 } catch(Exception e) { 191 throw e; 192 } finally { 193 fontSetByCss = false; 194 } 195 196 } 197 198 199 @Override 200 public void set(Font value) { 201 final Font oldValue = get(); 202 if (value == null ? oldValue == null : value.equals(oldValue)) { 203 return; 204 } 205 super.set(value); 206 } 207 208 @Override 209 protected void invalidated() { 210 // RT-20727 - if font is changed by calling setFont, then 211 // css might need to be reapplied since font size affects 212 // calculated values for styles with relative values 213 if(fontSetByCss == false) { 214 TextInputControl.this.impl_reapplyCSS(); 215 } 216 } 217 218 @Override 219 public CssMetaData<TextInputControl,Font> getCssMetaData() { 220 return StyleableProperties.FONT; 221 } 222 223 @Override 224 public Object getBean() { 225 return TextInputControl.this; 226 } 227 228 @Override 229 public String getName() { 230 return "font"; 231 } 232 }; 233 } 234 return font; 235 } 236 237 private ObjectProperty<Font> font; 238 public final void setFont(Font value) { fontProperty().setValue(value); } 239 public final Font getFont() { return font == null ? Font.getDefault() : font.getValue(); } 240 241 /** 242 * The prompt text to display in the {@code TextInputControl}, or 243 * <tt>null</tt> if no prompt text is displayed. 244 * @since 2.2 245 */ 246 private StringProperty promptText = new SimpleStringProperty(this, "promptText", "") { 247 @Override protected void invalidated() { 248 // Strip out newlines 249 String txt = get(); 250 if (txt != null && txt.contains("\n")) { 251 txt = txt.replace("\n", ""); 252 set(txt); 253 } 254 } 255 }; 256 public final StringProperty promptTextProperty() { return promptText; } 257 public final String getPromptText() { return promptText.get(); } 258 public final void setPromptText(String value) { promptText.set(value); } 259 260 261 private final Content content; 262 /** 263 * Returns the text input's content model. 264 */ 265 protected final Content getContent() { 266 return content; 267 } 268 269 /** 270 * The textual content of this TextInputControl. 271 */ 272 private TextProperty text = new TextProperty(); 273 public final String getText() { return text.get(); } 274 public final void setText(String value) { text.set(value); } 275 public final StringProperty textProperty() { return text; } 276 277 /** 278 * The number of characters in the text input. 279 */ 280 private ReadOnlyIntegerWrapper length = new ReadOnlyIntegerWrapper(this, "length"); 281 public final int getLength() { return length.get(); } 282 public final ReadOnlyIntegerProperty lengthProperty() { return length.getReadOnlyProperty(); } 283 284 /** 285 * Indicates whether this TextInputControl can be edited by the user. 286 */ 287 private BooleanProperty editable = new SimpleBooleanProperty(this, "editable", true) { 288 @Override protected void invalidated() { 289 pseudoClassStateChanged(PSEUDO_CLASS_READONLY, get()); 290 } 291 }; 292 public final boolean isEditable() { return editable.getValue(); } 293 public final void setEditable(boolean value) { editable.setValue(value); } 294 public final BooleanProperty editableProperty() { return editable; } 295 296 /** 297 * The current selection. 298 */ 299 private ReadOnlyObjectWrapper<IndexRange> selection = new ReadOnlyObjectWrapper<IndexRange>(this, "selection", new IndexRange(0, 0)); 300 public final IndexRange getSelection() { return selection.getValue(); } 301 public final ReadOnlyObjectProperty<IndexRange> selectionProperty() { return selection.getReadOnlyProperty(); } 302 303 /** 304 * Defines the characters in the TextInputControl which are selected 305 */ 306 private ReadOnlyStringWrapper selectedText = new ReadOnlyStringWrapper(this, "selectedText"); 307 public final String getSelectedText() { return selectedText.get(); } 308 public final ReadOnlyStringProperty selectedTextProperty() { return selectedText.getReadOnlyProperty(); } 309 310 /** 311 * The <code>anchor</code> of the text selection. 312 * The <code>anchor</code> and <code>caretPosition</code> make up the selection 313 * range. Selection must always be specified in terms of begin <= end, but 314 * <code>anchor</code> may be less than, equal to, or greater than the 315 * <code>caretPosition</code>. Depending on how the user selects text, 316 * the anchor might represent the lower or upper bound of the selection. 317 */ 318 private ReadOnlyIntegerWrapper anchor = new ReadOnlyIntegerWrapper(this, "anchor", 0); 319 public final int getAnchor() { return anchor.get(); } 320 public final ReadOnlyIntegerProperty anchorProperty() { return anchor.getReadOnlyProperty(); } 321 322 /** 323 * The current position of the caret within the text. 324 * The <code>anchor</code> and <code>caretPosition</code> make up the selection 325 * range. Selection must always be specified in terms of begin <= end, but 326 * <code>anchor</code> may be less than, equal to, or greater than the 327 * <code>caretPosition</code>. Depending on how the user selects text, 328 * the caretPosition might represent the lower or upper bound of the selection. 329 */ 330 private ReadOnlyIntegerWrapper caretPosition = new ReadOnlyIntegerWrapper(this, "caretPosition", 0); 331 public final int getCaretPosition() { return caretPosition.get(); } 332 public final ReadOnlyIntegerProperty caretPositionProperty() { return caretPosition.getReadOnlyProperty(); } 333 334 /** 335 * This flag is used to indicate that the text on replace trigger should 336 * NOT update the caret position. Basically it is a flag we use to 337 * indicate that the change to textInputControl.text was from us instead of from 338 * the developer. The language being what it is, it is possible that the 339 * developer is also bound to textInputControl.text and that they will change the 340 * text value before our on replace trigger gets called. We will therefore 341 * have to check the caret position against the text to make sure we don't 342 * get a caret position out of bounds. But otherwise, we don't update 343 * the caret when text is set internally. 344 */ 345 private boolean doNotAdjustCaret = false; 346 347 /*************************************************************************** 348 * * 349 * Methods * 350 * * 351 **************************************************************************/ 352 353 /** 354 * Returns a subset of the text input's content. 355 * 356 * @param start must be a value between 0 and end - 1. 357 * @param end must be less than or equal to the length 358 */ 359 public String getText(int start, int end) { 360 // TODO these checks really belong in Content 361 if (start > end) { 362 throw new IllegalArgumentException("The start must be <= the end"); 363 } 364 365 if (start < 0 366 || end > getLength()) { 367 throw new IndexOutOfBoundsException(); 368 } 369 370 return getContent().get(start, end); 371 } 372 373 /** 374 * Appends a sequence of characters to the content. 375 * 376 * @param text a non null String 377 */ 378 public void appendText(String text) { 379 insertText(getLength(), text); 380 } 381 382 /** 383 * Inserts a sequence of characters into the content. 384 * 385 * @param index The location to insert the text. 386 * @param text The text to insert. 387 */ 388 public void insertText(int index, String text) { 389 replaceText(index, index, text); 390 } 391 392 /** 393 * Removes a range of characters from the content. 394 * 395 * @param range The range of text to delete. The range object must not be null. 396 * 397 * @see #deleteText(int, int) 398 */ 399 public void deleteText(IndexRange range) { 400 replaceText(range, ""); 401 } 402 403 /** 404 * Removes a range of characters from the content. 405 * 406 * @param start The starting index in the range, inclusive. This must be >= 0 and < the end. 407 * @param end The ending index in the range, exclusive. This is one-past the last character to 408 * delete (consistent with the String manipulation methods). This must be > the start, 409 * and <= the length of the text. 410 */ 411 public void deleteText(int start, int end) { 412 replaceText(start, end, ""); 413 } 414 415 /** 416 * Replaces a range of characters with the given text. 417 * 418 * @param range The range of text to replace. The range object must not be null. 419 * @param text The text that is to replace the range. This must not be null. 420 * 421 * @see #replaceText(int, int, String) 422 */ 423 public void replaceText(IndexRange range, String text) { 424 if (range == null) { 425 throw new NullPointerException(); 426 } 427 428 int start = range.getStart(); 429 int end = start + range.getLength(); 430 431 replaceText(start, end, text); 432 } 433 434 /** 435 * Replaces a range of characters with the given text. 436 * 437 * @param start The starting index in the range, inclusive. This must be >= 0 and < the end. 438 * @param end The ending index in the range, exclusive. This is one-past the last character to 439 * delete (consistent with the String manipulation methods). This must be > the start, 440 * and <= the length of the text. 441 * @param text The text that is to replace the range. This must not be null. 442 */ 443 public void replaceText(int start, int end, String text) { 444 if (start > end) { 445 throw new IllegalArgumentException(); 446 } 447 448 if (text == null) { 449 throw new NullPointerException(); 450 } 451 452 if (start < 0 453 || end > getLength()) { 454 throw new IndexOutOfBoundsException(); 455 } 456 457 if (!this.text.isBound()) { 458 getContent().delete(start, end, text.isEmpty()); 459 getContent().insert(start, text, true); 460 461 start += text.length(); 462 selectRange(start, start); 463 } 464 } 465 466 /** 467 * Transfers the currently selected range in the text to the clipboard, 468 * removing the current selection. 469 */ 470 public void cut() { 471 copy(); 472 IndexRange selection = getSelection(); 473 deleteText(selection.getStart(), selection.getEnd()); 474 } 475 476 /** 477 * Transfers the currently selected range in the text to the clipboard, 478 * leaving the current selection. 479 */ 480 public void copy() { 481 final String selectedText = getSelectedText(); 482 if (selectedText.length() > 0) { 483 final ClipboardContent content = new ClipboardContent(); 484 content.putString(selectedText); 485 Clipboard.getSystemClipboard().setContent(content); 486 } 487 } 488 489 /** 490 * Transfers the contents in the clipboard into this text, 491 * replacing the current selection. If there is no selection, the contents 492 * in the clipboard is inserted at the current caret position. 493 */ 494 public void paste() { 495 final Clipboard clipboard = Clipboard.getSystemClipboard(); 496 if (clipboard.hasString()) { 497 final String text = clipboard.getString(); 498 if (text != null) { 499 replaceSelection(text); 500 } 501 } 502 } 503 504 /** 505 * Moves the selection backward one char in the text. This may have the 506 * effect of deselecting, depending on the location of the anchor relative 507 * to the caretPosition. This function effectively just moves the caretPosition. 508 */ 509 public void selectBackward() { 510 if (getCaretPosition() > 0 && getLength() > 0) { 511 // because the anchor stays put, by moving the caret to the left 512 // we ensure that a selection is registered and that it is correct 513 selectRange(getAnchor(), Character.offsetByCodePoints(getText(), getCaretPosition(), -1)); 514 } 515 } 516 517 /** 518 * Moves the selection forward one char in the text. This may have the 519 * effect of deselecting, depending on the location of the anchor relative 520 * to the caretPosition. This function effectively just moves the caret forward. 521 */ 522 public void selectForward() { 523 final int textLength = getLength(); 524 if (textLength > 0 && getCaretPosition() < textLength) { 525 selectRange(getAnchor(), Character.offsetByCodePoints(getText(), getCaretPosition(), 1)); 526 } 527 } 528 529 /** 530 * The break iterator instance. Right now, it is only used to perform 531 * previous/next word navigation. 532 */ 533 private BreakIterator breakIterator; 534 535 /** 536 * Moves the caret to the beginning of previous word. This function 537 * also has the effect of clearing the selection. 538 */ 539 public void previousWord() { 540 previousWord(false); 541 } 542 543 /** 544 * Moves the caret to the beginning of next word. This function 545 * also has the effect of clearing the selection. 546 */ 547 public void nextWord() { 548 nextWord(false); 549 } 550 551 /** 552 * Moves the caret to the end of the next word. This function 553 * also has the effect of clearing the selection. 554 */ 555 public void endOfNextWord() { 556 endOfNextWord(false); 557 } 558 559 /** 560 * Moves the caret to the beginning of previous word. This does not cause 561 * the selection to be cleared. Rather, the anchor stays put and the caretPosition is 562 * moved to the beginning of previous word. 563 */ 564 public void selectPreviousWord() { 565 previousWord(true); 566 } 567 568 /** 569 * Moves the caret to the beginning of next word. This does not cause 570 * the selection to be cleared. Rather, the anchor stays put and the caretPosition is 571 * moved to the beginning of next word. 572 */ 573 public void selectNextWord() { 574 nextWord(true); 575 } 576 577 /** 578 * Moves the caret to the end of the next word. This does not cause 579 * the selection to be cleared. 580 */ 581 public void selectEndOfNextWord() { 582 endOfNextWord(true); 583 } 584 585 private void previousWord(boolean select) { 586 final int textLength = getLength(); 587 final String text = getText(); 588 if (textLength <= 0) { 589 return; 590 } 591 592 if (breakIterator == null) { 593 breakIterator = BreakIterator.getWordInstance(); 594 } 595 breakIterator.setText(text); 596 597 int pos = breakIterator.preceding(Utils.clamp(0, getCaretPosition(), textLength - 1)); 598 599 // Skip the non-word region, then move/select to the beginning of the word. 600 while (pos != BreakIterator.DONE && 601 !Character.isLetter(text.charAt(Utils.clamp(0, pos, textLength-1)))) { 602 pos = breakIterator.preceding(Utils.clamp(0, pos, textLength-1)); 603 } 604 605 // move/select 606 selectRange(select ? getAnchor() : pos, pos); 607 } 608 609 private void nextWord(boolean select) { 610 final int textLength = getLength(); 611 final String text = getText(); 612 if (textLength <= 0) { 613 return; 614 } 615 616 if (breakIterator == null) { 617 breakIterator = BreakIterator.getWordInstance(); 618 } 619 breakIterator.setText(text); 620 621 int last = breakIterator.following(Utils.clamp(0, getCaretPosition(), textLength-1)); 622 int current = breakIterator.next(); 623 624 // skip the non-word region, then move/select to the beginning of the word. 625 while (current != BreakIterator.DONE) { 626 for (int p=last; p<=current; p++) { 627 if (Character.isLetter(text.charAt(Utils.clamp(0, p, textLength-1)))) { 628 if (select) { 629 selectRange(getAnchor(), p); 630 } else { 631 selectRange(p, p); 632 } 633 return; 634 } 635 } 636 last = current; 637 current = breakIterator.next(); 638 } 639 640 // move/select to the end 641 if (select) { 642 selectRange(getAnchor(), textLength); 643 } else { 644 end(); 645 } 646 } 647 648 private void endOfNextWord(boolean select) { 649 final int textLength = getLength(); 650 final String text = getText(); 651 if (textLength <= 0) { 652 return; 653 } 654 655 if (breakIterator == null) { 656 breakIterator = BreakIterator.getWordInstance(); 657 } 658 breakIterator.setText(text); 659 660 int last = breakIterator.following(Utils.clamp(0, getCaretPosition(), textLength-1)); 661 int current = breakIterator.next(); 662 663 // skip the non-word region, then move/select to the end of the word. 664 while (current != BreakIterator.DONE) { 665 for (int p=last; p<=current; p++) { 666 if (!Character.isLetter(text.charAt(Utils.clamp(0, p, textLength-1)))) { 667 if (select) { 668 selectRange(getAnchor(), p); 669 } else { 670 selectRange(p, p); 671 } 672 return; 673 } 674 } 675 last = current; 676 current = breakIterator.next(); 677 } 678 679 // move/select to the end 680 if (select) { 681 selectRange(getAnchor(), textLength); 682 } else { 683 end(); 684 } 685 } 686 687 /** 688 * Selects all text in the text input. 689 */ 690 public void selectAll() { 691 selectRange(0, getLength()); 692 } 693 694 /** 695 * Moves the caret to before the first char of the text. This function 696 * also has the effect of clearing the selection. 697 */ 698 public void home() { 699 // user wants to go to start 700 selectRange(0, 0); 701 } 702 703 /** 704 * Moves the caret to after the last char of the text. This function 705 * also has the effect of clearing the selection. 706 */ 707 public void end() { 708 // user wants to go to end 709 final int textLength = getLength(); 710 if (textLength > 0) { 711 selectRange(textLength, textLength); 712 } 713 } 714 715 /** 716 * Moves the caret to before the first char of text. This does not cause 717 * the selection to be cleared. Rather, the anchor stays put and the 718 * caretPosition is moved to before the first char. 719 */ 720 public void selectHome() { 721 selectRange(getAnchor(), 0); 722 } 723 724 /** 725 * Moves the caret to after the last char of text. This does not cause 726 * the selection to be cleared. Rather, the anchor stays put and the 727 * caretPosition is moved to after the last char. 728 */ 729 public void selectEnd() { 730 final int textLength = getLength(); 731 if (textLength > 0) selectRange(getAnchor(), textLength); 732 } 733 734 /** 735 * Deletes the character that precedes the current caret position from the 736 * text if there is no selection, or deletes the selection if there is one. 737 * This function returns true if the deletion succeeded, false otherwise. 738 */ 739 public boolean deletePreviousChar() { 740 boolean failed = true; 741 if (isEditable() && !isDisabled()) { 742 final String text = getText(); 743 final int dot = getCaretPosition(); 744 final int mark = getAnchor(); 745 if (dot != mark) { 746 // there is a selection of text to remove 747 replaceSelection(""); 748 failed = false; 749 } else if (dot > 0) { 750 // The caret is not at the beginning, so remove some characters. 751 // Typically you'd only be removing a single character, but 752 // in some cases you must remove two depending on the unicode 753 // characters 754 int p = Character.offsetByCodePoints(text, dot, -1); 755 doNotAdjustCaret = true; 756 deleteText(p, dot); 757 selectRange(p, p); 758 failed = false; 759 doNotAdjustCaret = false; 760 } 761 } 762 return !failed; 763 } 764 765 /** 766 * Deletes the character that follows the current caret position from the 767 * text if there is no selection, or deletes the selection if there is one. 768 * This function returns true if the deletion succeeded, false otherwise. 769 */ 770 public boolean deleteNextChar() { 771 boolean failed = true; 772 if (isEditable() && !isDisabled()) { 773 final String text = getText(); 774 final int dot = getCaretPosition(); 775 final int mark = getAnchor(); 776 if (dot != mark) { 777 // there is a selection of text to remove 778 replaceSelection(""); 779 int newDot = Math.min(dot, mark); 780 selectRange(newDot, newDot); 781 failed = false; 782 } else if (text.length() > 0 && dot < text.length()) { 783 // The caret is not at the end, so remove some characters. 784 // Typically you'd only be removing a single character, but 785 // in some cases you must remove two depending on the unicode 786 // characters 787 int p = Character.offsetByCodePoints(text, dot, 1); 788 doNotAdjustCaret = true; 789 //setText(text.substring(0, dot) + text.substring(dot + delChars)); 790 deleteText(dot, p); 791 failed = false; 792 doNotAdjustCaret = false; 793 } 794 } 795 return !failed; 796 } 797 798 /** 799 * Moves the caret position forward. If there is no selection, then the 800 * caret position is moved one character forward. If there is a selection, 801 * then the caret position is moved to the end of the selection and 802 * the selection cleared. 803 */ 804 public void forward() { 805 // user has moved caret to the right 806 final int textLength = getLength(); 807 final int dot = getCaretPosition(); 808 final int mark = getAnchor(); 809 if (dot != mark) { 810 int pos = Math.max(dot, mark); 811 selectRange(pos, pos); 812 } else if (dot < textLength && textLength > 0) { 813 int pos = Character.offsetByCodePoints(getText(), dot, 1); 814 selectRange(pos, pos); 815 } 816 deselect(); 817 } 818 819 /** 820 * Moves the caret position backward. If there is no selection, then the 821 * caret position is moved one character backward. If there is a selection, 822 * then the caret position is moved to the beginning of the selection and 823 * the selection cleared. 824 * 825 * @expert This function is intended to be used by experts, primarily 826 * by those implementing new Skins or Behaviors. It is not common 827 * for developers or designers to access this function directly. 828 * @since JavaFX 1.3 829 */ 830 public void backward() { 831 // user has moved caret to the left 832 final int textLength = getLength(); 833 final int dot = getCaretPosition(); 834 final int mark = getAnchor(); 835 if (dot != mark) { 836 int pos = Math.min(dot, mark); 837 selectRange(pos, pos); 838 } else if (dot > 0 && textLength > 0) { 839 int pos = Character.offsetByCodePoints(getText(), dot, -1); 840 selectRange(pos, pos); 841 } 842 deselect(); 843 } 844 845 /** 846 * Positions the caret to the position indicated by {@code pos}. This 847 * function will also clear the selection. 848 */ 849 public void positionCaret(int pos) { 850 final int p = Utils.clamp(0, pos, getLength()); 851 selectRange(p, p); 852 } 853 854 /** 855 * Positions the caret to the position indicated by {@code pos} and extends 856 * the selection, if there is one. If there is no selection, then a 857 * selection is formed where the anchor is at the current caret position 858 * and the caretPosition is moved to pos. 859 */ 860 public void selectPositionCaret(int pos) { 861 selectRange(getAnchor(), Utils.clamp(0, pos, getLength())); 862 } 863 864 /** 865 * Positions the anchor and caretPosition explicitly. 866 */ 867 public void selectRange(int anchor, int caretPosition) { 868 this.caretPosition.set(Utils.clamp(0, caretPosition, getLength())); 869 this.anchor.set(Utils.clamp(0, anchor, getLength())); 870 this.selection.set(IndexRange.normalize(getAnchor(), getCaretPosition())); 871 } 872 873 /** 874 * This function will extend the selection to include the specified pos. 875 * This is different from selectPositionCaret in that it does not simply 876 * move the caret. Rather, it will reposition the caret and anchor as necessary 877 * to ensure that pos becomes the new caret and the far other end of the 878 * selection becomes the anchor. 879 */ 880 public void extendSelection(int pos) { 881 final int p = Utils.clamp(0, pos, getLength()); 882 final int dot = getCaretPosition(); 883 final int mark = getAnchor(); 884 int start = Math.min(dot, mark); 885 int end = Math.max(dot, mark); 886 if (p < start) { 887 selectRange(end, p); 888 } else { 889 selectRange(start, p); 890 } 891 } 892 893 /** 894 * Clears the text. 895 */ 896 public void clear() { 897 deselect(); 898 if (!text.isBound()) { 899 setText(""); 900 } 901 } 902 903 /** 904 * Clears the selection. 905 */ 906 public void deselect() { 907 // set the anchor equal to the caret position, which clears the selection 908 // while also preserving the caret position 909 selectRange(getCaretPosition(), getCaretPosition()); 910 } 911 912 /** 913 * Replaces the selection with the given replacement String. If there is 914 * no selection, then the replacement text is simply inserted at the current 915 * caret position. If there was a selection, then the selection is cleared 916 * and the given replacement text inserted. 917 */ 918 public void replaceSelection(String replacement) { 919 if (text.isBound()) return; 920 921 if (replacement == null) { 922 throw new NullPointerException(); 923 } 924 925 final int dot = getCaretPosition(); 926 final int mark = getAnchor(); 927 int start = Math.min(dot, mark); 928 int end = Math.max(dot, mark); 929 int pos = dot; 930 931 if (getLength() == 0) { 932 doNotAdjustCaret = true; 933 setText(replacement); 934 selectRange(getLength(), getLength()); 935 doNotAdjustCaret = false; 936 } else { 937 deselect(); 938 // RT-16566: Need to take into account stripping of chars into caret pos 939 doNotAdjustCaret = true; 940 int oldLength = getLength(); 941 end = Math.min(end, oldLength); 942 if (end > start) { 943 getContent().delete(start, end, replacement.isEmpty()); 944 oldLength -= (end - start); 945 } 946 getContent().insert(start, replacement, true); 947 // RT-16566: Need to take into account stripping of chars into caret pos 948 final int p = start + getLength() - oldLength; 949 selectRange(p, p); 950 doNotAdjustCaret = false; 951 } 952 } 953 954 // Used by TextArea, although there are probably other better ways of 955 // doing this. 956 void textUpdated() { } 957 958 /** 959 * A little utility method for stripping out unwanted characters. 960 * 961 * @param txt 962 * @param stripNewlines 963 * @param stripTabs 964 * @return The string after having the unwanted characters stripped out. 965 */ 966 static String filterInput(String txt, boolean stripNewlines, boolean stripTabs) { 967 // Most of the time, when text is inserted, there are no illegal 968 // characters. So we'll do a "cheap" check for illegal characters. 969 // If we find one, we'll do a longer replace algorithm. In the 970 // case of illegal characters, this may at worst be an O(2n) solution. 971 // Strip out any characters that are outside the printed range 972 if (containsInvalidCharacters(txt, stripNewlines, stripTabs)) { 973 StringBuilder s = new StringBuilder(txt.length()); 974 for (int i=0; i<txt.length(); i++) { 975 final char c = txt.charAt(i); 976 if (!isInvalidCharacter(c, stripNewlines, stripTabs)) { 977 s.append(c); 978 } 979 } 980 txt = s.toString(); 981 } 982 return txt; 983 } 984 985 static boolean containsInvalidCharacters(String txt, boolean newlineIllegal, boolean tabIllegal) { 986 for (int i=0; i<txt.length(); i++) { 987 final char c = txt.charAt(i); 988 if (isInvalidCharacter(c, newlineIllegal, tabIllegal)) return true; 989 } 990 return false; 991 } 992 993 private static boolean isInvalidCharacter(char c, boolean newlineIllegal, boolean tabIllegal) { 994 if (c == 0x7F) return true; 995 if (c == 0xA) return newlineIllegal; 996 if (c == 0x9) return tabIllegal; 997 if (c < 0x20) return true; 998 return false; 999 } 1000 1001 // It can be bound, in which case we will force it to be an eager 1002 // binding so that we update the content eagerly 1003 // It can be bidirectionally bound, which basically will just work 1004 // If somebody changes the content directly, it will be notified and 1005 // send an invalidation event. 1006 private class TextProperty extends StringProperty { 1007 // This is used only when the property is bound 1008 private ObservableValue<? extends String> observable = null; 1009 // Added to the observable when bound 1010 private InvalidationListener listener = null; 1011 // Used for event handling 1012 private ExpressionHelper<String> helper = null; 1013 // The developer my set the Text property to null. Although 1014 // the Content must be given an empty String, we must still 1015 // treat the value as though it were null, so that a subsequent 1016 // getText() will return null. 1017 private boolean textIsNull = false; 1018 1019 @Override public String get() { 1020 // Since we force eager binding and content is always up to date, 1021 // we just need to get it from content and not through the binding 1022 return textIsNull ? null : content.get(); 1023 } 1024 1025 @Override public void set(String value) { 1026 if (isBound()) { 1027 throw new java.lang.RuntimeException("A bound value cannot be set."); 1028 } 1029 doSet(value); 1030 markInvalid(); 1031 } 1032 1033 private void invalidate() { 1034 markInvalid(); 1035 } 1036 1037 @Override public void bind(ObservableValue<? extends String> observable) { 1038 if (observable == null) { 1039 throw new NullPointerException("Cannot bind to null"); 1040 } 1041 if (!observable.equals(this.observable)) { 1042 unbind(); 1043 this.observable = observable; 1044 if (listener == null) { 1045 listener = new Listener(); 1046 } 1047 this.observable.addListener(listener); 1048 markInvalid(); 1049 doSet(observable.getValue()); 1050 } 1051 } 1052 1053 @Override public void unbind() { 1054 if (observable != null) { 1055 doSet(observable.getValue()); 1056 observable.removeListener(listener); 1057 observable = null; 1058 } 1059 } 1060 1061 @Override public boolean isBound() { 1062 return observable != null; 1063 } 1064 1065 @Override public void addListener(InvalidationListener listener) { 1066 helper = ExpressionHelper.addListener(helper, this, listener); 1067 } 1068 1069 @Override public void removeListener(InvalidationListener listener) { 1070 helper = ExpressionHelper.removeListener(helper, listener); 1071 } 1072 1073 @Override public void addListener(ChangeListener<? super String> listener) { 1074 helper = ExpressionHelper.addListener(helper, this, listener); 1075 } 1076 1077 @Override public void removeListener(ChangeListener<? super String> listener) { 1078 helper = ExpressionHelper.removeListener(helper, listener); 1079 } 1080 1081 @Override public Object getBean() { 1082 return TextInputControl.this; 1083 } 1084 1085 @Override public String getName() { 1086 return "text"; 1087 } 1088 1089 private void fireValueChangedEvent() { 1090 ExpressionHelper.fireValueChangedEvent(helper); 1091 } 1092 1093 private void markInvalid() { 1094 fireValueChangedEvent(); 1095 } 1096 1097 private void doSet(String value) { 1098 // Guard against the null value. 1099 textIsNull = value == null; 1100 if (value == null) value = ""; 1101 // Update the content 1102 content.delete(0, content.length(), value.isEmpty()); 1103 content.insert(0, value, true); 1104 if (!doNotAdjustCaret) { 1105 selectRange(0, 0); 1106 textUpdated(); 1107 } 1108 } 1109 1110 private class Listener implements InvalidationListener { 1111 @Override 1112 public void invalidated(Observable valueModel) { 1113 // We now need to force it to be eagerly recomputed 1114 // because we need to push these changes to the 1115 // content model. Because changing the model ends 1116 // up calling invalidate and markInvalid, the 1117 // listeners will all be notified. 1118 doSet(observable.getValue()); 1119 } 1120 } 1121 } 1122 1123 1124 /*************************************************************************** 1125 * * 1126 * Stylesheet Handling * 1127 * * 1128 **************************************************************************/ 1129 1130 1131 private static final PseudoClass PSEUDO_CLASS_READONLY 1132 = PseudoClass.getPseudoClass("readonly"); 1133 1134 /** 1135 * @treatAsPrivate implementation detail 1136 */ 1137 private static class StyleableProperties { 1138 private static final FontCssMetaData<TextInputControl> FONT = 1139 new FontCssMetaData<TextInputControl>("-fx-font", Font.getDefault()) { 1140 1141 @Override 1142 public boolean isSettable(TextInputControl n) { 1143 return n.font == null || !n.font.isBound(); 1144 } 1145 1146 @Override 1147 public StyleableProperty<Font> getStyleableProperty(TextInputControl n) { 1148 return (StyleableProperty<Font>)n.fontProperty(); 1149 } 1150 }; 1151 1152 private static final List<CssMetaData<? extends Styleable, ?>> STYLEABLES; 1153 static { 1154 final List<CssMetaData<? extends Styleable, ?>> styleables = 1155 new ArrayList<CssMetaData<? extends Styleable, ?>>(Control.getClassCssMetaData()); 1156 styleables.add(FONT); 1157 STYLEABLES = Collections.unmodifiableList(styleables); 1158 } 1159 } 1160 1161 /** 1162 * @return The CssMetaData associated with this class, which may include the 1163 * CssMetaData of its super classes. 1164 */ 1165 public static List<CssMetaData<? extends Styleable, ?>> getClassCssMetaData() { 1166 return StyleableProperties.STYLEABLES; 1167 } 1168 1169 /** 1170 * {@inheritDoc} 1171 */ 1172 @Override 1173 public List<CssMetaData<? extends Styleable, ?>> getControlCssMetaData() { 1174 return getClassCssMetaData(); 1175 } 1176 1177}