All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.fxmisc.richtext.StyledTextField Maven / Gradle / Ivy

package org.fxmisc.richtext;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;

import org.fxmisc.richtext.model.EditableStyledDocument;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.NamedArg;
import javafx.beans.binding.Bindings;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.css.CssMetaData;
import javafx.css.StyleConverter;
import javafx.css.Styleable;
import javafx.css.StyleableObjectProperty;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.AccessibleRole;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.scene.text.TextFlow;

/**
 * A text field whose segment generic has been specified to be a {@link String}. How the text
 * will be styled is not yet specified in this class, but use {@link StyleClassedTextField} for a style class
 * approach to styling the text and {@link InlineCssTextField} for an inline css approach to styling the text.
 *
 * 

Use CSS Style Class ".styled-text-field" for styling the control.

* * @param type of paragraph style * @param type of style that can be applied to text. * * @author Jurgen */ public abstract class StyledTextField extends StyledTextArea { private final static List> CSS_META_DATA_LIST; private final static CssMetaData TEXT_ALIGNMENT = new CustomCssMetaData<>( "-fx-alignment", (StyleConverter) StyleConverter.getEnumConverter(TextAlignment.class), TextAlignment.LEFT, s -> (StyleableObjectProperty) s.alignmentProperty() ); private final static CssMetaData PROMPT_TEXT_FILL = new CustomCssMetaData<>( "-fx-prompt-text-fill", (StyleConverter) StyleConverter.getPaintConverter(), Color.GRAY, s -> (StyleableObjectProperty) s.promptTextFillProperty() ); private final static Pattern VERTICAL_WHITESPACE = Pattern.compile( "\\v+" ); private final static String STYLE_SHEET; private static double HEIGHT = 24; static { List> styleables = new ArrayList<>(GenericStyledArea.getClassCssMetaData()); styleables.add( PROMPT_TEXT_FILL ); styleables.add( TEXT_ALIGNMENT ); CSS_META_DATA_LIST = Collections.unmodifiableList(styleables); String globalCSS = System.getProperty( "javafx.userAgentStylesheetUrl" ); // JavaFX preference! if ( globalCSS == null ) globalCSS = Application.getUserAgentStylesheet(); if ( globalCSS == null ) globalCSS = Application.STYLESHEET_MODENA; globalCSS = "styled-text-field-"+ globalCSS.toLowerCase() +".css"; STYLE_SHEET = StyledTextField.class.getResource( globalCSS ).toExternalForm(); // Ugly hack to get a TextFields default height :( // as it differs between Caspian, Modena, etc. Runnable snapshot = () -> { TextField tf = new TextField( "GetHeight" ); new Scene(tf).snapshot( null ); HEIGHT = tf.getHeight(); }; if ( Platform.isFxApplicationThread() ) snapshot.run(); else Platform.runLater( snapshot ); } private boolean selectAll = true; private StyleableObjectProperty textAlignment; private StyleableObjectProperty promptFillProp; public StyledTextField(@NamedArg("initialParagraphStyle") PS initialParagraphStyle, @NamedArg("applyParagraphStyle") BiConsumer applyParagraphStyle, @NamedArg("initialTextStyle") S initialTextStyle, @NamedArg("applyStyle") BiConsumer applyStyle, @NamedArg("document") EditableStyledDocument document) { super( initialParagraphStyle, applyParagraphStyle, initialTextStyle, applyStyle, document, true ); getStylesheets().add( STYLE_SHEET ); getStyleClass().setAll( "styled-text-field" ); setAccessibleRole( AccessibleRole.TEXT_FIELD ); setPrefSize( 135, HEIGHT ); addEventFilter( KeyEvent.KEY_PRESSED, KE -> { if ( KE.getCode() == KeyCode.ENTER ) { fireEvent( new ActionEvent( this, null ) ); KE.consume(); } else if ( KE.getCode() == KeyCode.TAB ) { traverse( this.getParent(), this, KE.isShiftDown() ? -1 : +1 ); KE.consume(); } }); addEventFilter( MouseEvent.MOUSE_PRESSED, ME -> selectAll = isFocused() ); focusedProperty().addListener( (ob,was,focused) -> { if ( ! was && focused && selectAll ) { selectRange( getLength(), 0 ); } else if ( ! focused && was ) { moveTo( 0 ); requestFollowCaret(); } selectAll = true; }); super.setWrapText( false ); wrapTextProperty().addListener( (ob,ov,wrap) -> { if ( wrap ) { // veto any changes wrapTextProperty().unbind(); super.setWrapText(false); } }); } /* * There's no public API to move the focus forward or backward * without explicitly knowing the node. So here's a basic local * implementation to accomplish that. */ private Node traverse( Parent p, Node from, int dir ) { if ( p == null ) return null; List nodeList = p.getChildrenUnmodifiable(); int len = nodeList.size(); int neighbor = -1; if ( from != null ) while ( ++neighbor < len && nodeList.get(neighbor) != from ); else if ( dir == 1 ) neighbor = -1; else neighbor = len; for ( neighbor += dir; neighbor > -1 && neighbor < len; neighbor += dir ) { Node target = nodeList.get( neighbor ); if ( target instanceof Pane || target instanceof Group ) { target = traverse( (Parent) target, null, dir ); // down if ( target != null ) return target; } else if ( target.isVisible() && ! target.isDisabled() && target.isFocusTraversable() ) { target.requestFocus(); return target; } } return traverse( p.getParent(), p, dir ); // up } /** * Specifies how the text should be aligned when there is empty space within the TextField. * To configure via CSS use {@code -fx-alignment:} and values from {@link javafx.scene.text.TextAlignment}. */ public final ObjectProperty alignmentProperty() { if (textAlignment == null) { textAlignment = new CustomStyleableProperty<>( TextAlignment.LEFT, "textAlignment", this, TEXT_ALIGNMENT ); textAlignment.addListener( (ob,ov,alignment) -> changeAlignment( alignment ) ); } return textAlignment; } public final TextAlignment getAlignment() { return textAlignment == null ? TextAlignment.LEFT : textAlignment.getValue(); } public final void setAlignment( TextAlignment value ) { alignmentProperty().setValue( value ); } protected abstract void changeAlignment( TextAlignment txtAlign ); /** * The action handler associated with this text field, or {@code null} if no action handler is assigned. * The action handler is normally called when the user types the ENTER key. */ private ObjectProperty> onAction = new ObjectPropertyBase>() { @Override protected void invalidated() { setEventHandler(ActionEvent.ACTION, get()); } @Override public Object getBean() { return StyledTextField.this; } @Override public String getName() { return "onAction"; } }; public final ObjectProperty> onActionProperty() { return onAction; } public final EventHandler getOnAction() { return onActionProperty().get(); } public final void setOnAction(EventHandler value) { onActionProperty().set(value); } /** * The prompt text to display or null if no prompt text is to be displayed. *

The Text will be aligned according to the text fields alignment setting and have a default * text fill of GRAY unless you have changed it by any means, e.g. with CSS "-fx-prompt-text-fill" */ public final void setPromptText( Text value ) { placeholderProperty().set( value ); } public final ObjectProperty promptTextProperty() { return placeholderProperty(); } public final Text getPromptText() { return getPlaceholder() instanceof Text ? (Text) getPlaceholder() : null; } /** setPlaceholder is not supported by StyledTextField, use setPromptText instead */ @Override public void setPlaceholder( Node value, Pos where ) { throw new UnsupportedOperationException("Use setPromptText instead"); } @Override protected void configurePlaceholder( Node placeholder ) { placeholder.layoutYProperty().bind( Bindings.createDoubleBinding( () -> (getHeight() - placeholder.getLayoutBounds().getHeight()) / 2 + Math.abs( placeholder.getLayoutBounds().getMinY() ), heightProperty(), placeholder.layoutBoundsProperty() ) ); placeholder.layoutXProperty().bind( Bindings.createDoubleBinding( () -> calcHorizontalPos(), widthProperty(), placeholder.layoutBoundsProperty(), paddingProperty(), alignmentProperty() ) ); if ( placeholder instanceof Text && ((Text) placeholder).getFill() == Color.BLACK ) { ((Text) placeholder).fillProperty().bind( promptTextFillProperty() ); } } private final ObjectProperty promptTextFillProperty() { if ( promptFillProp == null ) { promptFillProp = new CustomStyleableProperty<>( Color.GRAY, "promptFill", this, PROMPT_TEXT_FILL ); } return promptFillProp; } private double calcHorizontalPos() { double leftPad = getPadding().getLeft(); double rightPad = getPadding().getRight(); double promptWidth = getPlaceholder().getLayoutBounds().getWidth(); TextAlignment alignment = getAlignment(); double alignmentPadding = leftPad; if ( alignment == TextAlignment.RIGHT ) alignmentPadding = rightPad; else if ( alignment == TextAlignment.CENTER ) alignmentPadding = 0; if ( promptWidth < (getWidth() - alignmentPadding) ) setClip( null ); else setClip( new Rectangle( getWidth(), getHeight() ) ); switch ( alignment ) { case CENTER : return (getWidth() - promptWidth) / 2; case RIGHT : return getWidth() - rightPad - promptWidth; default : return leftPad; } } @Override public void replaceText( int start, int end, String text ) { super.replaceText( start, end, VERTICAL_WHITESPACE.matcher( text ).replaceAll( " " ) ); } public void setText( String text ) { replaceText( text ); } /** This is a no op for text fields and therefore marked as deprecated. */ @Override @Deprecated public void setWrapText( boolean value ) {} /** This always returns false for styled text fields. */ @Override public boolean isWrapText() { return false; } @Override public List> getCssMetaData() { return CSS_META_DATA_LIST; } public static List> getClassCssMetaData() { return CSS_META_DATA_LIST; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy