/* * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javafx.scene.control.skin; import com.sun.javafx.scene.control.Properties; import com.sun.javafx.scene.control.skin.FXVK; import com.sun.javafx.scene.input.ExtendedInputMethodRequests; import javafx.animation.KeyFrame; import javafx.animation.Timeline; import javafx.application.ConditionalFeature; import javafx.application.Platform; import javafx.beans.binding.BooleanBinding; import javafx.beans.binding.ObjectBinding; import javafx.beans.property.BooleanProperty; import javafx.beans.property.ObjectProperty; import javafx.beans.property.SimpleBooleanProperty; import javafx.beans.value.ObservableBooleanValue; import javafx.beans.value.ObservableObjectValue; import javafx.collections.ObservableList; import javafx.css.CssMetaData; import javafx.css.Styleable; import javafx.css.StyleableBooleanProperty; import javafx.css.StyleableObjectProperty; import javafx.css.StyleableProperty; import javafx.geometry.NodeOrientation; import javafx.geometry.Point2D; import javafx.geometry.Rectangle2D; import javafx.scene.AccessibleAction; import javafx.scene.Node; import javafx.scene.Scene; import javafx.scene.control.IndexRange; import javafx.scene.control.SkinBase; import javafx.scene.control.TextInputControl; import javafx.scene.input.InputMethodEvent; import javafx.scene.input.InputMethodHighlight; import javafx.scene.input.InputMethodTextRun; import javafx.scene.layout.StackPane; import javafx.scene.paint.Color; import javafx.scene.paint.Paint; import javafx.scene.shape.ClosePath; import javafx.scene.shape.HLineTo; import javafx.scene.shape.Line; import javafx.scene.shape.LineTo; import javafx.scene.shape.MoveTo; import javafx.scene.shape.Path; import javafx.scene.shape.PathElement; import javafx.scene.shape.Shape; import javafx.scene.shape.VLineTo; import javafx.scene.text.HitInfo; import javafx.stage.Window; import javafx.util.Duration; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.List; import com.sun.javafx.PlatformUtil; import javafx.css.converter.BooleanConverter; import javafx.css.converter.PaintConverter; import com.sun.javafx.scene.control.behavior.TextInputControlBehavior; import com.sun.javafx.tk.FontMetrics; import com.sun.javafx.tk.Toolkit; import static com.sun.javafx.PlatformUtil.isWindows; import java.security.AccessController; import java.security.PrivilegedAction; /** * Abstract base class for text input skins. * * @since 9 * @see TextFieldSkin * @see TextAreaSkin */ public abstract class TextInputControlSkin extends SkinBase { /************************************************************************** * * Static fields / blocks * **************************************************************************/ /** * Unit names for caret movement. * * @see #moveCaret(TextUnit, Direction, boolean) */ public static enum TextUnit { CHARACTER, WORD, LINE, PARAGRAPH, PAGE }; /** * Direction names for caret movement. * * @see #moveCaret(TextUnit, Direction, boolean) */ public static enum Direction { LEFT, RIGHT, UP, DOWN, BEGINNING, END }; static boolean preload = false; static { AccessController.doPrivileged((PrivilegedAction) () -> { String s = System.getProperty("com.sun.javafx.virtualKeyboard.preload"); if (s != null) { if (s.equalsIgnoreCase("PRERENDER")) { preload = true; } } return null; }); } /** * Specifies whether we ought to show handles. We should do it on touch platforms, but not * iOS (and maybe not Android either?) */ static final boolean SHOW_HANDLES = Properties.IS_TOUCH_SUPPORTED && !PlatformUtil.isIOS(); private final static boolean IS_FXVK_SUPPORTED = Platform.isSupported(ConditionalFeature.VIRTUAL_KEYBOARD); /************************************************************************** * * Private fields * **************************************************************************/ final ObservableObjectValue fontMetrics; private ObservableBooleanValue caretVisible; private CaretBlinking caretBlinking = new CaretBlinking(blinkProperty()); /** * A path, provided by the textNode, which represents the caret. * I assume this has to be updated whenever the caretPosition * changes. Perhaps more frequently (including text changes), * but I'm not sure. */ final Path caretPath = new Path(); StackPane caretHandle = null; StackPane selectionHandle1 = null; StackPane selectionHandle2 = null; // Start/Length of the text under input method composition private int imstart; private int imlength; // Holds concrete attributes for the composition runs private List imattrs = new java.util.ArrayList(); /************************************************************************** * * Constructors * **************************************************************************/ /** * Creates a new instance of TextInputControlSkin, although note that this * instance does not handle any behavior / input mappings - this needs to be * handled appropriately by subclasses. * * @param control The control that this skin should be installed onto. */ public TextInputControlSkin(final T control) { super(control); fontMetrics = new ObjectBinding() { { bind(control.fontProperty()); } @Override protected FontMetrics computeValue() { invalidateMetrics(); return Toolkit.getToolkit().getFontLoader().getFontMetrics(control.getFont()); } }; /** * The caret is visible when the text box is focused AND when the selection * is empty. If the selection is non empty or the text box is not focused * then we don't want to show the caret. Also, we show the caret while * performing some operations such as most key strokes. In that case we * simply toggle its opacity. *

*/ caretVisible = new BooleanBinding() { { bind(control.focusedProperty(), control.anchorProperty(), control.caretPositionProperty(), control.disabledProperty(), control.editableProperty(), displayCaret, blinkProperty());} @Override protected boolean computeValue() { // RT-10682: On Windows, we show the caret during selection, but on others we hide it return !blinkProperty().get() && displayCaret.get() && control.isFocused() && (isWindows() || (control.getCaretPosition() == control.getAnchor())) && !control.isDisabled() && control.isEditable(); } }; if (SHOW_HANDLES) { caretHandle = new StackPane(); selectionHandle1 = new StackPane(); selectionHandle2 = new StackPane(); caretHandle.setManaged(false); selectionHandle1.setManaged(false); selectionHandle2.setManaged(false); caretHandle.visibleProperty().bind(new BooleanBinding() { { bind(control.focusedProperty(), control.anchorProperty(), control.caretPositionProperty(), control.disabledProperty(), control.editableProperty(), control.lengthProperty(), displayCaret);} @Override protected boolean computeValue() { return (displayCaret.get() && control.isFocused() && control.getCaretPosition() == control.getAnchor() && !control.isDisabled() && control.isEditable() && control.getLength() > 0); } }); selectionHandle1.visibleProperty().bind(new BooleanBinding() { { bind(control.focusedProperty(), control.anchorProperty(), control.caretPositionProperty(), control.disabledProperty(), displayCaret);} @Override protected boolean computeValue() { return (displayCaret.get() && control.isFocused() && control.getCaretPosition() != control.getAnchor() && !control.isDisabled()); } }); selectionHandle2.visibleProperty().bind(new BooleanBinding() { { bind(control.focusedProperty(), control.anchorProperty(), control.caretPositionProperty(), control.disabledProperty(), displayCaret);} @Override protected boolean computeValue() { return (displayCaret.get() && control.isFocused() && control.getCaretPosition() != control.getAnchor() && !control.isDisabled()); } }); caretHandle.getStyleClass().setAll("caret-handle"); selectionHandle1.getStyleClass().setAll("selection-handle"); selectionHandle2.getStyleClass().setAll("selection-handle"); selectionHandle1.setId("selection-handle-1"); selectionHandle2.setId("selection-handle-2"); } if (IS_FXVK_SUPPORTED) { if (preload) { Scene scene = control.getScene(); if (scene != null) { Window window = scene.getWindow(); if (window != null) { FXVK.init(control); } } } control.focusedProperty().addListener(observable -> { if (FXVK.useFXVK()) { Scene scene = getSkinnable().getScene(); if (control.isEditable() && control.isFocused()) { FXVK.attach(control); } else if (scene == null || scene.getWindow() == null || !scene.getWindow().isFocused() || !(scene.getFocusOwner() instanceof TextInputControl && ((TextInputControl)scene.getFocusOwner()).isEditable())) { FXVK.detach(); } } }); } if (control.getOnInputMethodTextChanged() == null) { control.setOnInputMethodTextChanged(event -> { handleInputMethodEvent(event); }); } control.setInputMethodRequests(new ExtendedInputMethodRequests() { @Override public Point2D getTextLocation(int offset) { Scene scene = getSkinnable().getScene(); Window window = scene.getWindow(); // Don't use imstart here because it isn't initialized yet. Rectangle2D characterBounds = getCharacterBounds(control.getSelection().getStart() + offset); Point2D p = getSkinnable().localToScene(characterBounds.getMinX(), characterBounds.getMaxY()); Point2D location = new Point2D(window.getX() + scene.getX() + p.getX(), window.getY() + scene.getY() + p.getY()); return location; } @Override public int getLocationOffset(int x, int y) { return getInsertionPoint(x, y); } @Override public void cancelLatestCommittedText() { // TODO } @Override public String getSelectedText() { TextInputControl control = getSkinnable(); IndexRange selection = control.getSelection(); return control.getText(selection.getStart(), selection.getEnd()); } @Override public int getInsertPositionOffset() { int caretPosition = getSkinnable().getCaretPosition(); if (caretPosition < imstart) { return caretPosition; } else if (caretPosition < imstart + imlength) { return imstart; } else { return caretPosition - imlength; } } @Override public String getCommittedText(int begin, int end) { TextInputControl control = getSkinnable(); if (begin < imstart) { if (end <= imstart) { return control.getText(begin, end); } else { return control.getText(begin, imstart) + control.getText(imstart + imlength, end + imlength); } } else { return control.getText(begin + imlength, end + imlength); } } @Override public int getCommittedTextLength() { return getSkinnable().getText().length() - imlength; } }); } /************************************************************************** * * Properties * **************************************************************************/ // --- blink private BooleanProperty blink; private final void setBlink(boolean value) { blinkProperty().set(value); } private final boolean isBlink() { return blinkProperty().get(); } private final BooleanProperty blinkProperty() { if (blink == null) { blink = new SimpleBooleanProperty(this, "blink", true); } return blink; } // --- text fill /** * The fill to use for the text under normal conditions */ private final ObjectProperty textFill = new StyleableObjectProperty(Color.BLACK) { @Override protected void invalidated() { updateTextFill(); } @Override public Object getBean() { return TextInputControlSkin.this; } @Override public String getName() { return "textFill"; } @Override public CssMetaData getCssMetaData() { return StyleableProperties.TEXT_FILL; } }; /** * The fill {@code Paint} used for the foreground text color. * @param value the text fill */ protected final void setTextFill(Paint value) { textFill.set(value); } protected final Paint getTextFill() { return textFill.get(); } protected final ObjectProperty textFillProperty() { return textFill; } // --- prompt text fill private final ObjectProperty promptTextFill = new StyleableObjectProperty(Color.GRAY) { @Override public Object getBean() { return TextInputControlSkin.this; } @Override public String getName() { return "promptTextFill"; } @Override public CssMetaData getCssMetaData() { return StyleableProperties.PROMPT_TEXT_FILL; } }; /** * The fill {@code Paint} used for the foreground prompt text color. * @param value the prompt text fill */ protected final void setPromptTextFill(Paint value) { promptTextFill.set(value); } protected final Paint getPromptTextFill() { return promptTextFill.get(); } protected final ObjectProperty promptTextFillProperty() { return promptTextFill; } // --- hightlight fill /** * The fill to use for the text when highlighted. */ private final ObjectProperty highlightFill = new StyleableObjectProperty(Color.DODGERBLUE) { @Override protected void invalidated() { updateHighlightFill(); } @Override public Object getBean() { return TextInputControlSkin.this; } @Override public String getName() { return "highlightFill"; } @Override public CssMetaData getCssMetaData() { return StyleableProperties.HIGHLIGHT_FILL; } }; /** * The fill {@code Paint} used for the background of selected text. * @param value the highlight fill */ protected final void setHighlightFill(Paint value) { highlightFill.set(value); } protected final Paint getHighlightFill() { return highlightFill.get(); } protected final ObjectProperty highlightFillProperty() { return highlightFill; } // --- highlight text fill private final ObjectProperty highlightTextFill = new StyleableObjectProperty(Color.WHITE) { @Override protected void invalidated() { updateHighlightTextFill(); } @Override public Object getBean() { return TextInputControlSkin.this; } @Override public String getName() { return "highlightTextFill"; } @Override public CssMetaData getCssMetaData() { return StyleableProperties.HIGHLIGHT_TEXT_FILL; } }; /** * The fill {@code Paint} used for the foreground of selected text. * @param value the highlight text fill */ protected final void setHighlightTextFill(Paint value) { highlightTextFill.set(value); } protected final Paint getHighlightTextFill() { return highlightTextFill.get(); } protected final ObjectProperty highlightTextFillProperty() { return highlightTextFill; } // --- display caret private final BooleanProperty displayCaret = new StyleableBooleanProperty(true) { @Override public Object getBean() { return TextInputControlSkin.this; } @Override public String getName() { return "displayCaret"; } @Override public CssMetaData getCssMetaData() { return StyleableProperties.DISPLAY_CARET; } }; private final void setDisplayCaret(boolean value) { displayCaret.set(value); } private final boolean isDisplayCaret() { return displayCaret.get(); } private final BooleanProperty displayCaretProperty() { return displayCaret; } /** * Caret bias in the content. true means a bias towards forward character * (true=leading/false=trailing) */ private BooleanProperty forwardBias = new SimpleBooleanProperty(this, "forwardBias", true); protected final BooleanProperty forwardBiasProperty() { return forwardBias; } // Public for behavior public final void setForwardBias(boolean isLeading) { forwardBias.set(isLeading); } protected final boolean isForwardBias() { return forwardBias.get(); } /************************************************************************** * * Abstract API * **************************************************************************/ /** * @param start the start * @param end the end * @return the path elements describing the shape of the underline for the given range. */ protected abstract PathElement[] getUnderlineShape(int start, int end); /** * @param start the start * @param end the end * @return the path elements describing the bounding rectangles for the given range of text. */ protected abstract PathElement[] getRangeShape(int start, int end); /** * Adds highlight for composed text from Input Method. * @param nodes the list of nodes * @param start the start */ protected abstract void addHighlight(List nodes, int start); /** * Removes highlight for composed text from Input Method. * @param nodes the list of nodes */ protected abstract void removeHighlight(List nodes); // Public for behavior /** * Moves the caret by one of the given text unit, in the given * direction. Note that only certain combinations are valid, * depending on the implementing subclass. * * @param unit the unit of text to move by. * @param dir the direction of movement. * @param select whether to extends the selection to the new posititon. */ public abstract void moveCaret(TextUnit unit, Direction dir, boolean select); /************************************************************************** * * Public API * **************************************************************************/ // Public for behavior /** * Returns the position to be used for a context menu, based on the location * of the caret handle or selection handles. This is supported only on touch * displays and does not use the location of the mouse. * @return the position to be used for this context menu */ public Point2D getMenuPosition() { if (SHOW_HANDLES) { if (caretHandle.isVisible()) { return new Point2D(caretHandle.getLayoutX() + caretHandle.getWidth() / 2, caretHandle.getLayoutY()); } else if (selectionHandle1.isVisible() && selectionHandle2.isVisible()) { return new Point2D((selectionHandle1.getLayoutX() + selectionHandle1.getWidth() / 2 + selectionHandle2.getLayoutX() + selectionHandle2.getWidth() / 2) / 2, selectionHandle2.getLayoutY() + selectionHandle2.getHeight() / 2); } else { return null; } } else { throw new UnsupportedOperationException(); } } // For use with PasswordField in TextFieldSkin /** * This method may be overridden by subclasses to replace the displayed * characters without affecting the actual text content. This is used to * display bullet characters in PasswordField. * * @param txt the content that may need to be masked. * @return the replacement string. This may just be the input string, or may be a string of replacement characters with the same length as the input string. */ protected String maskText(String txt) { return txt; } /** * Returns the insertion point for a given location. * * @param x the x location * @param y the y location * @return the insertion point for a given location */ protected int getInsertionPoint(double x, double y) { return 0; } /** * Returns the bounds of the character at a given index. * * @param index the index * @return the bounds of the character at a given index */ public Rectangle2D getCharacterBounds(int index) { return null; } /** * Ensures that the character at a given index is visible. * * @param index the index */ protected void scrollCharacterToVisible(int index) {} /** * Invalidates cached min and pref sizes for the TextInputControl. */ protected void invalidateMetrics() { } /** * Called when textFill property changes. */ protected void updateTextFill() {}; /** * Called when highlightFill property changes. */ protected void updateHighlightFill() {}; /** * Called when highlightTextFill property changes. */ protected void updateHighlightTextFill() {}; protected void handleInputMethodEvent(InputMethodEvent event) { final TextInputControl textInput = getSkinnable(); if (textInput.isEditable() && !textInput.textProperty().isBound() && !textInput.isDisabled()) { // just replace the text on iOS if (PlatformUtil.isIOS()) { textInput.setText(event.getCommitted()); return; } // remove previous input method text (if any) or selected text if (imlength != 0) { removeHighlight(imattrs); imattrs.clear(); textInput.selectRange(imstart, imstart + imlength); } // Insert committed text if (event.getCommitted().length() != 0) { String committed = event.getCommitted(); textInput.replaceText(textInput.getSelection(), committed); } // Replace composed text imstart = textInput.getSelection().getStart(); StringBuilder composed = new StringBuilder(); for (InputMethodTextRun run : event.getComposed()) { composed.append(run.getText()); } textInput.replaceText(textInput.getSelection(), composed.toString()); imlength = composed.length(); if (imlength != 0) { int pos = imstart; for (InputMethodTextRun run : event.getComposed()) { int endPos = pos + run.getText().length(); createInputMethodAttributes(run.getHighlight(), pos, endPos); pos = endPos; } addHighlight(imattrs, imstart); // Set caret position in composed text int caretPos = event.getCaretPosition(); if (caretPos >= 0 && caretPos < imlength) { textInput.selectRange(imstart + caretPos, imstart + caretPos); } } } } // Public for behavior /** * Starts or stops caret blinking. The behavior classes use this to temporarily * pause blinking while user is typing or otherwise moving the caret. * * @param value whether caret should be blinking. */ public void setCaretAnimating(boolean value) { if (value) { caretBlinking.start(); } else { caretBlinking.stop(); blinkProperty().set(true); } } /************************************************************************** * * Private implementation * **************************************************************************/ TextInputControlBehavior getBehavior() { return null; } ObservableBooleanValue caretVisibleProperty() { return caretVisible; } boolean isRTL() { return (getSkinnable().getEffectiveNodeOrientation() == NodeOrientation.RIGHT_TO_LEFT); }; private void createInputMethodAttributes(InputMethodHighlight highlight, int start, int end) { double minX = 0f; double maxX = 0f; double minY = 0f; double maxY = 0f; PathElement elements[] = getUnderlineShape(start, end); for (int i = 0; i < elements.length; i++) { PathElement pe = elements[i]; if (pe instanceof MoveTo) { minX = maxX = ((MoveTo)pe).getX(); minY = maxY = ((MoveTo)pe).getY(); } else if (pe instanceof LineTo) { minX = (minX < ((LineTo)pe).getX() ? minX : ((LineTo)pe).getX()); maxX = (maxX > ((LineTo)pe).getX() ? maxX : ((LineTo)pe).getX()); minY = (minY < ((LineTo)pe).getY() ? minY : ((LineTo)pe).getY()); maxY = (maxY > ((LineTo)pe).getY() ? maxY : ((LineTo)pe).getY()); } else if (pe instanceof HLineTo) { minX = (minX < ((HLineTo)pe).getX() ? minX : ((HLineTo)pe).getX()); maxX = (maxX > ((HLineTo)pe).getX() ? maxX : ((HLineTo)pe).getX()); } else if (pe instanceof VLineTo) { minY = (minY < ((VLineTo)pe).getY() ? minY : ((VLineTo)pe).getY()); maxY = (maxY > ((VLineTo)pe).getY() ? maxY : ((VLineTo)pe).getY()); } // Don't assume that shapes are ended with ClosePath. if (pe instanceof ClosePath || i == elements.length - 1 || (i < elements.length - 1 && elements[i+1] instanceof MoveTo)) { // Now, create the attribute. Shape attr = null; if (highlight == InputMethodHighlight.SELECTED_RAW) { // blue background attr = new Path(); ((Path)attr).getElements().addAll(getRangeShape(start, end)); attr.setFill(Color.BLUE); attr.setOpacity(0.3f); } else if (highlight == InputMethodHighlight.UNSELECTED_RAW) { // dash underline. attr = new Line(minX + 2, maxY + 1, maxX - 2, maxY + 1); attr.setStroke(textFill.get()); attr.setStrokeWidth(maxY - minY); ObservableList dashArray = attr.getStrokeDashArray(); dashArray.add(Double.valueOf(2f)); dashArray.add(Double.valueOf(2f)); } else if (highlight == InputMethodHighlight.SELECTED_CONVERTED) { // thick underline. attr = new Line(minX + 2, maxY + 1, maxX - 2, maxY + 1); attr.setStroke(textFill.get()); attr.setStrokeWidth((maxY - minY) * 3); } else if (highlight == InputMethodHighlight.UNSELECTED_CONVERTED) { // single underline. attr = new Line(minX + 2, maxY + 1, maxX - 2, maxY + 1); attr.setStroke(textFill.get()); attr.setStrokeWidth(maxY - minY); } if (attr != null) { attr.setManaged(false); imattrs.add(attr); } } } } /************************************************************************** * * Support classes * **************************************************************************/ private static final class CaretBlinking { private final Timeline caretTimeline; private final WeakReference blinkPropertyRef; public CaretBlinking(final BooleanProperty blinkProperty) { blinkPropertyRef = new WeakReference<>(blinkProperty); caretTimeline = new Timeline(); caretTimeline.setCycleCount(Timeline.INDEFINITE); caretTimeline.getKeyFrames().addAll( new KeyFrame(Duration.ZERO, e -> setBlink(false)), new KeyFrame(Duration.seconds(.5), e -> setBlink(true)), new KeyFrame(Duration.seconds(1))); } public void start() { caretTimeline.play(); } public void stop() { caretTimeline.stop(); } private void setBlink(final boolean value) { final BooleanProperty blinkProperty = blinkPropertyRef.get(); if (blinkProperty == null) { caretTimeline.stop(); return; } blinkProperty.set(value); } } private static class StyleableProperties { private static final CssMetaData TEXT_FILL = new CssMetaData("-fx-text-fill", PaintConverter.getInstance(), Color.BLACK) { @Override public boolean isSettable(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return skin.textFill == null || !skin.textFill.isBound(); } @Override @SuppressWarnings("unchecked") public StyleableProperty getStyleableProperty(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return (StyleableProperty)skin.textFill; } }; private static final CssMetaData PROMPT_TEXT_FILL = new CssMetaData("-fx-prompt-text-fill", PaintConverter.getInstance(), Color.GRAY) { @Override public boolean isSettable(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return skin.promptTextFill == null || !skin.promptTextFill.isBound(); } @Override @SuppressWarnings("unchecked") public StyleableProperty getStyleableProperty(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return (StyleableProperty)skin.promptTextFill; } }; private static final CssMetaData HIGHLIGHT_FILL = new CssMetaData("-fx-highlight-fill", PaintConverter.getInstance(), Color.DODGERBLUE) { @Override public boolean isSettable(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return skin.highlightFill == null || !skin.highlightFill.isBound(); } @Override @SuppressWarnings("unchecked") public StyleableProperty getStyleableProperty(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return (StyleableProperty)skin.highlightFill; } }; private static final CssMetaData HIGHLIGHT_TEXT_FILL = new CssMetaData("-fx-highlight-text-fill", PaintConverter.getInstance(), Color.WHITE) { @Override public boolean isSettable(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return skin.highlightTextFill == null || !skin.highlightTextFill.isBound(); } @Override @SuppressWarnings("unchecked") public StyleableProperty getStyleableProperty(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return (StyleableProperty)skin.highlightTextFill; } }; private static final CssMetaData DISPLAY_CARET = new CssMetaData("-fx-display-caret", BooleanConverter.getInstance(), Boolean.TRUE) { @Override public boolean isSettable(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return skin.displayCaret == null || !skin.displayCaret.isBound(); } @Override @SuppressWarnings("unchecked") public StyleableProperty getStyleableProperty(TextInputControl n) { final TextInputControlSkin skin = (TextInputControlSkin) n.getSkin(); return (StyleableProperty)skin.displayCaret; } }; private static final List> STYLEABLES; static { List> styleables = new ArrayList>(SkinBase.getClassCssMetaData()); styleables.add(TEXT_FILL); styleables.add(PROMPT_TEXT_FILL); styleables.add(HIGHLIGHT_FILL); styleables.add(HIGHLIGHT_TEXT_FILL); styleables.add(DISPLAY_CARET); STYLEABLES = Collections.unmodifiableList(styleables); } } /** * Returns the CssMetaData associated with this class, which may include the * CssMetaData of its superclasses. * @return the CssMetaData associated with this class, which may include the * CssMetaData of its superclasses */ public static List> getClassCssMetaData() { return StyleableProperties.STYLEABLES; } /** * {@inheritDoc} */ @Override public List> getCssMetaData() { return getClassCssMetaData(); } @Override protected void executeAccessibleAction(AccessibleAction action, Object... parameters) { switch (action) { case SHOW_TEXT_RANGE: { Integer start = (Integer)parameters[0]; Integer end = (Integer)parameters[1]; if (start != null && end != null) { scrollCharacterToVisible(end); scrollCharacterToVisible(start); scrollCharacterToVisible(end); } break; } default: super.executeAccessibleAction(action, parameters); } } }