Skip to content

Commit 63cda11

Browse files
authored
Added styled text fields (#894)
1 parent fa2074b commit 63cda11

5 files changed

Lines changed: 287 additions & 0 deletions

File tree

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.fxmisc.richtext;
2+
3+
import org.fxmisc.richtext.model.SimpleEditableStyledDocument;
4+
5+
import javafx.scene.text.TextFlow;
6+
7+
/**
8+
* A TextField that uses inline CSS, i.e. <code>setStyle(String)</code>, to define the styles of text segments.
9+
* <p>Use CSS Style Class ".styled-text-field" for styling the control.
10+
* @author Jurgen
11+
*/
12+
public class InlineCssTextField extends StyledTextField<String,String>
13+
{
14+
public InlineCssTextField() {
15+
super( "", TextFlow::setStyle, "", TextExt::setStyle, new SimpleEditableStyledDocument<>("", "") );
16+
}
17+
18+
public InlineCssTextField( String text ) {
19+
this(); replaceText( text );
20+
getUndoManager().forgetHistory();
21+
getUndoManager().mark();
22+
}
23+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package org.fxmisc.richtext;
2+
3+
import java.util.Collection;
4+
import java.util.Collections;
5+
6+
import org.fxmisc.richtext.model.SimpleEditableStyledDocument;
7+
8+
/**
9+
* A TextField that uses style classes, i.e. <code>getStyleClass().add(String)</code>, to define the styles of text segments.
10+
* <p>Use CSS Style Class ".styled-text-field" for styling the control.
11+
* @author Jurgen
12+
*/
13+
public class StyleClassedTextField extends StyledTextField<Collection<String>, Collection<String>>
14+
{
15+
public StyleClassedTextField() {
16+
super(
17+
Collections.<String>emptyList(),
18+
(paragraph, styleClasses) -> paragraph.getStyleClass().addAll(styleClasses),
19+
Collections.<String>emptyList(),
20+
(text, styleClasses) -> text.getStyleClass().addAll(styleClasses),
21+
new SimpleEditableStyledDocument<>( Collections.<String>emptyList(), Collections.<String>emptyList() )
22+
);
23+
}
24+
25+
public StyleClassedTextField( String text ) {
26+
this(); replaceText( text );
27+
getUndoManager().forgetHistory();
28+
getUndoManager().mark();
29+
}
30+
31+
/**
32+
* Convenient method to append text together with a single style class.
33+
*/
34+
public void append( String text, String styleClass ) {
35+
insert( getLength(), text, styleClass );
36+
}
37+
38+
/**
39+
* Convenient method to insert text together with a single style class.
40+
*/
41+
public void insert( int position, String text, String styleClass ) {
42+
replace( position, position, text, Collections.singleton( styleClass ) );
43+
}
44+
45+
/**
46+
* Convenient method to replace text together with a single style class.
47+
*/
48+
public void replace( int start, int end, String text, String styleClass ) {
49+
replace( start, end, text, Collections.singleton( styleClass ) );
50+
}
51+
52+
/**
53+
* Convenient method to assign a single style class.
54+
*/
55+
public void setStyleClass( int from, int to, String styleClass ) {
56+
setStyle( from, to, Collections.singletonList( styleClass ) );
57+
}
58+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package org.fxmisc.richtext;
2+
3+
import java.util.List;
4+
import java.util.function.BiConsumer;
5+
import java.util.regex.Pattern;
6+
7+
import org.fxmisc.richtext.model.EditableStyledDocument;
8+
9+
import javafx.application.Application;
10+
import javafx.beans.NamedArg;
11+
import javafx.beans.property.ObjectProperty;
12+
import javafx.beans.property.ObjectPropertyBase;
13+
import javafx.event.ActionEvent;
14+
import javafx.event.EventHandler;
15+
import javafx.scene.AccessibleRole;
16+
import javafx.scene.Group;
17+
import javafx.scene.Node;
18+
import javafx.scene.Parent;
19+
import javafx.scene.Scene;
20+
import javafx.scene.control.TextField;
21+
import javafx.scene.input.KeyCode;
22+
import javafx.scene.input.KeyEvent;
23+
import javafx.scene.input.MouseEvent;
24+
import javafx.scene.layout.Pane;
25+
import javafx.scene.text.TextFlow;
26+
27+
public class StyledTextField<PS, S> extends StyledTextArea
28+
{
29+
private final Pattern VERTICAL_WHITESPACE = Pattern.compile( "\\v" );
30+
private final static String STYLE_SHEET;
31+
private final static double HEIGHT;
32+
static {
33+
String globalCSS = System.getProperty( "javafx.userAgentStylesheetUrl" ); // JavaFX preference!
34+
if ( globalCSS == null ) globalCSS = Application.getUserAgentStylesheet();
35+
if ( globalCSS == null ) globalCSS = Application.STYLESHEET_MODENA;
36+
globalCSS = "styled-text-field-"+ globalCSS.toLowerCase() +".css";
37+
STYLE_SHEET = StyledTextField.class.getResource( globalCSS ).toExternalForm();
38+
39+
// Ugly hack to get a TextFields default height :(
40+
// as it differs between Caspian, Modena, etc.
41+
TextField tf = new TextField( "GetHeight" );
42+
new Scene(tf); tf.applyCss(); tf.layout();
43+
HEIGHT = tf.getHeight();
44+
}
45+
46+
private boolean selectAll = true;
47+
48+
49+
public StyledTextField(@NamedArg("initialParagraphStyle") PS initialParagraphStyle,
50+
@NamedArg("applyParagraphStyle") BiConsumer<TextFlow, PS> applyParagraphStyle,
51+
@NamedArg("initialTextStyle") S initialTextStyle,
52+
@NamedArg("applyStyle") BiConsumer<? super TextExt, S> applyStyle,
53+
@NamedArg("document") EditableStyledDocument<PS, String, S> document)
54+
{
55+
super( initialParagraphStyle, applyParagraphStyle, initialTextStyle, applyStyle, document, true );
56+
57+
getStylesheets().add( STYLE_SHEET );
58+
getStyleClass().setAll( "styled-text-field" );
59+
60+
setAccessibleRole( AccessibleRole.TEXT_FIELD );
61+
setPrefSize( 135, HEIGHT );
62+
63+
addEventFilter( KeyEvent.KEY_PRESSED, KE -> {
64+
if ( KE.getCode() == KeyCode.ENTER ) {
65+
fireEvent( new ActionEvent( this, null ) );
66+
KE.consume();
67+
}
68+
else if ( KE.getCode() == KeyCode.TAB ) {
69+
traverse( this.getParent(), this, KE.isShiftDown() ? -1 : +1 );
70+
KE.consume();
71+
}
72+
});
73+
74+
addEventFilter( MouseEvent.MOUSE_PRESSED, ME -> selectAll = isFocused() );
75+
76+
focusedProperty().addListener( (ob,was,focused) -> {
77+
if ( ! was && focused && selectAll ) {
78+
selectRange( getLength(), 0 );
79+
}
80+
else if ( ! focused && was ) {
81+
moveTo( 0 ); requestFollowCaret();
82+
}
83+
selectAll = true;
84+
});
85+
}
86+
87+
/*
88+
* There's no public API to move the focus forward or backward
89+
* without explicitly knowing the node. So here's a basic local
90+
* implementation to accomplish that.
91+
*/
92+
private Node traverse( Parent p, Node from, int dir )
93+
{
94+
if ( p == null ) return null;
95+
96+
List<Node> nodeList = p.getChildrenUnmodifiable();
97+
int len = nodeList.size();
98+
int neighbor = -1;
99+
100+
if ( from != null ) while ( ++neighbor < len && nodeList.get(neighbor) != from );
101+
else if ( dir == 1 ) neighbor = -1;
102+
else neighbor = len;
103+
104+
for ( neighbor += dir; neighbor > -1 && neighbor < len; neighbor += dir ) {
105+
106+
Node target = nodeList.get( neighbor );
107+
108+
if ( target instanceof Pane || target instanceof Group ) {
109+
target = traverse( (Parent) target, null, dir ); // down
110+
if ( target != null ) return target;
111+
}
112+
else if ( target.isVisible() && ! target.isDisabled() && target.isFocusTraversable() ) {
113+
target.requestFocus();
114+
return target;
115+
}
116+
}
117+
118+
return traverse( p.getParent(), p, dir ); // up
119+
}
120+
121+
122+
public void setText( String text )
123+
{
124+
replaceText( text );
125+
}
126+
127+
/**
128+
* The action handler associated with this text field, or
129+
* {@code null} if no action handler is assigned.
130+
*
131+
* The action handler is normally called when the user types the ENTER key.
132+
*/
133+
private ObjectProperty<EventHandler<ActionEvent>> onAction = new ObjectPropertyBase<EventHandler<ActionEvent>>() {
134+
@Override
135+
protected void invalidated() {
136+
setEventHandler(ActionEvent.ACTION, get());
137+
}
138+
139+
@Override
140+
public Object getBean() {
141+
return StyledTextField.this;
142+
}
143+
144+
@Override
145+
public String getName() {
146+
return "onAction";
147+
}
148+
};
149+
public final ObjectProperty<EventHandler<ActionEvent>> onActionProperty() { return onAction; }
150+
public final EventHandler<ActionEvent> getOnAction() { return onActionProperty().get(); }
151+
public final void setOnAction(EventHandler<ActionEvent> value) { onActionProperty().set(value); }
152+
153+
@Override
154+
public void replaceText( int start, int end, String text )
155+
{
156+
super.replaceText( start, end, VERTICAL_WHITESPACE.matcher( text ).replaceAll( " " ) );
157+
}
158+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.styled-text-field
2+
{
3+
-fx-cursor: text;
4+
-fx-text-fill: -fx-text-inner-color;
5+
-fx-background-color: -fx-shadow-highlight-color, -fx-text-box-border, -fx-control-inner-background;
6+
-fx-background-insets: 0, 1, 2;
7+
-fx-background-radius: 3, 2, 2;
8+
-fx-padding: 3 5 4 5;
9+
}
10+
.styled-text-field:focused
11+
{
12+
-fx-background-color: -fx-focus-color, -fx-text-box-border, -fx-control-inner-background;
13+
-fx-background-insets: -0.4, 1, 2;
14+
-fx-background-radius: 3.4, 2, 2;
15+
}
16+
.styled-text-field:disabled
17+
{
18+
-fx-opacity: -fx-disabled-opacity;
19+
}
20+
.styled-text-field .main-selection
21+
{
22+
-fx-fill: #0093ff;
23+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
.styled-text-field
2+
{
3+
-fx-cursor: text;
4+
-fx-text-fill: -fx-text-inner-color;
5+
-fx-background-color: linear-gradient(to bottom, derive(-fx-text-box-border, -10%), -fx-text-box-border),
6+
linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background);
7+
-fx-background-insets: 0, 1;
8+
-fx-background-radius: 3, 2;
9+
-fx-padding: 4 7 4 7;
10+
}
11+
.styled-text-field:focused
12+
{
13+
-fx-background-color: -fx-focus-color, -fx-control-inner-background, -fx-faint-focus-color,
14+
linear-gradient(from 0px 0px to 0px 5px, derive(-fx-control-inner-background, -9%), -fx-control-inner-background);
15+
-fx-background-insets: -0.2, 1, -1.4, 3;
16+
-fx-background-radius: 3, 2, 4, 0;
17+
}
18+
.styled-text-field:disabled
19+
{
20+
-fx-opacity: 0.4;
21+
}
22+
.styled-text-field .main-selection
23+
{
24+
-fx-fill: #0096C9;
25+
}

0 commit comments

Comments
 (0)