diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/InlineCssTextField.java b/richtextfx/src/main/java/org/fxmisc/richtext/InlineCssTextField.java new file mode 100644 index 000000000..c27369148 --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/InlineCssTextField.java @@ -0,0 +1,23 @@ +package org.fxmisc.richtext; + +import org.fxmisc.richtext.model.SimpleEditableStyledDocument; + +import javafx.scene.text.TextFlow; + +/** + * A TextField that uses inline CSS, i.e. setStyle(String), to define the styles of text segments. + *

Use CSS Style Class ".styled-text-field" for styling the control. + * @author Jurgen + */ +public class InlineCssTextField extends StyledTextField +{ + public InlineCssTextField() { + super( "", TextFlow::setStyle, "", TextExt::setStyle, new SimpleEditableStyledDocument<>("", "") ); + } + + public InlineCssTextField( String text ) { + this(); replaceText( text ); + getUndoManager().forgetHistory(); + getUndoManager().mark(); + } +} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/StyleClassedTextField.java b/richtextfx/src/main/java/org/fxmisc/richtext/StyleClassedTextField.java new file mode 100644 index 000000000..6876beff4 --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/StyleClassedTextField.java @@ -0,0 +1,58 @@ +package org.fxmisc.richtext; + +import java.util.Collection; +import java.util.Collections; + +import org.fxmisc.richtext.model.SimpleEditableStyledDocument; + +/** + * A TextField that uses style classes, i.e. getStyleClass().add(String), to define the styles of text segments. + *

Use CSS Style Class ".styled-text-field" for styling the control. + * @author Jurgen + */ +public class StyleClassedTextField extends StyledTextField, Collection> +{ + public StyleClassedTextField() { + super( + Collections.emptyList(), + (paragraph, styleClasses) -> paragraph.getStyleClass().addAll(styleClasses), + Collections.emptyList(), + (text, styleClasses) -> text.getStyleClass().addAll(styleClasses), + new SimpleEditableStyledDocument<>( Collections.emptyList(), Collections.emptyList() ) + ); + } + + public StyleClassedTextField( String text ) { + this(); replaceText( text ); + getUndoManager().forgetHistory(); + getUndoManager().mark(); + } + + /** + * Convenient method to append text together with a single style class. + */ + public void append( String text, String styleClass ) { + insert( getLength(), text, styleClass ); + } + + /** + * Convenient method to insert text together with a single style class. + */ + public void insert( int position, String text, String styleClass ) { + replace( position, position, text, Collections.singleton( styleClass ) ); + } + + /** + * Convenient method to replace text together with a single style class. + */ + public void replace( int start, int end, String text, String styleClass ) { + replace( start, end, text, Collections.singleton( styleClass ) ); + } + + /** + * Convenient method to assign a single style class. + */ + public void setStyleClass( int from, int to, String styleClass ) { + setStyle( from, to, Collections.singletonList( styleClass ) ); + } +} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextField.java b/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextField.java new file mode 100644 index 000000000..df05da18b --- /dev/null +++ b/richtextfx/src/main/java/org/fxmisc/richtext/StyledTextField.java @@ -0,0 +1,158 @@ +package org.fxmisc.richtext; + +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.beans.NamedArg; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.ObjectPropertyBase; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +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.text.TextFlow; + +public class StyledTextField extends StyledTextArea +{ + private final Pattern VERTICAL_WHITESPACE = Pattern.compile( "\\v" ); + private final static String STYLE_SHEET; + private final static double HEIGHT; + static { + 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. + TextField tf = new TextField( "GetHeight" ); + new Scene(tf); tf.applyCss(); tf.layout(); + HEIGHT = tf.getHeight(); + } + + private boolean selectAll = true; + + + 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; + }); + } + + /* + * 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 + } + + + public void setText( String text ) + { + replaceText( text ); + } + + /** + * 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); } + + @Override + public void replaceText( int start, int end, String text ) + { + super.replaceText( start, end, VERTICAL_WHITESPACE.matcher( text ).replaceAll( " " ) ); + } +} diff --git a/richtextfx/src/main/resources/org/fxmisc/richtext/styled-text-field-caspian.css b/richtextfx/src/main/resources/org/fxmisc/richtext/styled-text-field-caspian.css new file mode 100644 index 000000000..9ddd8ce69 --- /dev/null +++ b/richtextfx/src/main/resources/org/fxmisc/richtext/styled-text-field-caspian.css @@ -0,0 +1,23 @@ +.styled-text-field +{ + -fx-cursor: text; + -fx-text-fill: -fx-text-inner-color; + -fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background; + -fx-background-insets: 0, 1, 2; + -fx-background-radius: 3, 2, 2; + -fx-padding: 3 5 4 5; +} +.styled-text-field:focused +{ + -fx-background-color: -fx-focus-color, -fx-text-box-border, -fx-control-inner-background; + -fx-background-insets: -0.4, 1, 2; + -fx-background-radius: 3.4, 2, 2; +} +.styled-text-field:disabled +{ + -fx-opacity: -fx-disabled-opacity; +} +.styled-text-field .main-selection +{ + -fx-fill: #0093ff; +} diff --git a/richtextfx/src/main/resources/org/fxmisc/richtext/styled-text-field-modena.css b/richtextfx/src/main/resources/org/fxmisc/richtext/styled-text-field-modena.css new file mode 100644 index 000000000..62b070a88 --- /dev/null +++ b/richtextfx/src/main/resources/org/fxmisc/richtext/styled-text-field-modena.css @@ -0,0 +1,25 @@ +.styled-text-field +{ + -fx-cursor: text; + -fx-text-fill: -fx-text-inner-color; + -fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border), + linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background); + -fx-background-insets: 0, 1; + -fx-background-radius: 3, 2; + -fx-padding: 4 7 4 7; +} +.styled-text-field:focused +{ + -fx-background-color: -fx-focus-color, -fx-control-inner-background, -fx-faint-focus-color, + linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background); + -fx-background-insets: -0.2, 1, -1.4, 3; + -fx-background-radius: 3, 2, 4, 0; +} +.styled-text-field:disabled +{ + -fx-opacity: 0.4; +} +.styled-text-field .main-selection +{ + -fx-fill: #0096C9; +}