Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

8335547: Support multi-line prompt text for TextArea #1716

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -314,22 +314,15 @@ public String getName() {
* @defaultValue An empty String
* @since JavaFX 2.2
*/
private StringProperty promptText = new SimpleStringProperty(this, "promptText", "") {
@Override protected void invalidated() {
// Strip out newlines
String txt = get();
if (txt != null && txt.contains("\n")) {
txt = txt.replace("\n", "");
if (isBound()) {
unbind();
}
set(txt);
}
private StringProperty promptText;
public final StringProperty promptTextProperty() {
if (promptText == null) {
promptText = new SimpleStringProperty(this, "promptText", "");
}
};
public final StringProperty promptTextProperty() { return promptText; }
public final String getPromptText() { return promptText.get(); }
public final void setPromptText(String value) { promptText.set(value); }
return promptText;
}
public final String getPromptText() { return promptText == null ? "" : promptText.get(); }
public final void setPromptText(String value) { promptTextProperty().set(value); }


/**
Original file line number Diff line number Diff line change
@@ -26,10 +26,7 @@
package javafx.scene.control.skin;

import java.util.List;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.binding.ObjectBinding;
import javafx.beans.binding.StringBinding;
import javafx.beans.binding.*;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.value.ObservableBooleanValue;
@@ -144,7 +141,6 @@ public class TextFieldSkin extends TextInputControlSkin<TextField> {
*/
public TextFieldSkin(final TextField control) {
super(control);

// install default input map for the text field control
this.behavior = (control instanceof PasswordField)
? new PasswordFieldBehavior((PasswordField)control)
@@ -734,6 +730,13 @@ private void createPromptNode() {
promptNode.fontProperty().bind(getSkinnable().fontProperty());

promptNode.textProperty().bind(getSkinnable().promptTextProperty());
promptNode.textProperty().bind(Bindings.createStringBinding(() -> {
String s = getSkinnable().getPromptText();
if (s == null) {
return "";
}
return s.replace("\n", "");
}, getSkinnable().promptTextProperty()));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two minor problems:

  1. missing { } after if. (I would highly recommend always using { })
  2. when s is null you call replace() unnecessarily

suggestion:

            String s = getSkinnable().getPromptText();
            if (s == null) {
                return "";
            }
            return s.replace("\n", "");

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should consider using a fluent binding here, which is a more modern solution compared to the Bindings class. It is also simpler because you don't need to check for null:

promptNode.textProperty().bind(getSkinnable().promptTextProperty().map(s -> s.replace("\n", "")));

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered the suggestion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I will push the new changes thanks for the suggestion @mstr2

promptNode.fillProperty().bind(promptTextFillProperty());
updateSelection();
}
Original file line number Diff line number Diff line change
@@ -25,13 +25,10 @@

package test.javafx.scene.control;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.*;
import static test.com.sun.javafx.scene.control.infrastructure.ControlTestUtils.assertStyleClassContains;

import com.sun.javafx.tk.Toolkit;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleBooleanProperty;
@@ -41,6 +38,11 @@
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextInputControlShim;
import javafx.scene.control.skin.TextAreaSkin;
import javafx.scene.control.skin.TextInputSkinShim;
import javafx.scene.layout.StackPane;
import javafx.scene.text.Text;
import javafx.stage.Stage;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -53,6 +55,10 @@ public class TextAreaTest {
private TextArea txtArea;//Empty string
private TextArea dummyTxtArea;//With string value

private Scene scene;
private Stage stage;
private StackPane root;

@BeforeEach
public void setup() {
txtArea = new TextArea();
@@ -62,9 +68,21 @@ public void setup() {

@AfterEach
public void cleanup() {
if (stage != null) {
stage.hide();
}
removeUncaughtExceptionHandler();
}

private void initStage() {
//This step is not needed (Just to make sure StubToolkit is loaded into VM)
Toolkit tk = Toolkit.getToolkit();
root = new StackPane();
scene = new Scene(root);
stage = new Stage();
stage.setScene(scene);
}

private void setUncaughtExceptionHandler() {
Thread.currentThread().setUncaughtExceptionHandler((thread, throwable) -> {
if (throwable instanceof RuntimeException) {
@@ -246,6 +264,42 @@ private void removeUncaughtExceptionHandler() {
assertFalse(txtArea.isWrapText());
}

@Test
public void testPromptTextWithBindingWithLineBreaks() {
initStage();
txtArea.setSkin(new TextAreaSkin(txtArea));
String promptWithLineBreaks = "Prompt\nwith\nLineBreaks";
StringProperty promptProperty = new SimpleStringProperty(promptWithLineBreaks);
txtArea.promptTextProperty().bind(promptProperty);
root.getChildren().add(txtArea);
Text promptNode = TextInputSkinShim.getPromptNode(txtArea);
assertEquals(promptWithLineBreaks, promptNode.getText());
txtArea.promptTextProperty().unbind();
}

@Test
public void testPromptTextInTextArea() {
initStage();
txtArea.setSkin(new TextAreaSkin(txtArea));
String promptWithLineBreaks = "Prompt\nwith\nLineBreaks";
txtArea.setPromptText(promptWithLineBreaks);
root.getChildren().add(txtArea);
Text promptNode = TextInputSkinShim.getPromptNode(txtArea);
assertEquals(promptWithLineBreaks, promptNode.getText());
}

@Test
public void testPromptTextWithNullValue() {
initStage();
txtArea.setSkin(new TextAreaSkin(txtArea));
String promptWithNull = null;
StringProperty promptPropertyNull = new SimpleStringProperty(promptWithNull);
txtArea.promptTextProperty().bind(promptPropertyNull);
root.getChildren().add(txtArea);
Text promptNode = TextInputSkinShim.getPromptNode(txtArea);
assertNull(promptNode);
}

/*********************************************************************
* Miscellaneous Tests *
********************************************************************/
Original file line number Diff line number Diff line change
@@ -29,29 +29,18 @@
import java.util.List;

import com.sun.javafx.tk.Toolkit;
import javafx.scene.text.Text;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.Arguments;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.params.provider.Arguments;
import static javafx.scene.input.KeyCode.*;
import static javafx.scene.input.KeyEvent.*;
import static java.util.stream.Collectors.*;
@@ -258,6 +247,59 @@ private void removeUncaughtExceptionHandler() {
txtField.onActionProperty().bind(op);
assertEquals(ev, op.getValue());
}

@Test
public void testPromptTextWithBindingWithLineBreaks() {
initStage();
txtField.setSkin(new TextFieldSkin(txtField));
String promptWithLineBreaks = "Prompt\nwith\nLineBreaks";
StringProperty promptProperty = new SimpleStringProperty(promptWithLineBreaks);
txtField.promptTextProperty().bind(promptProperty);
root.getChildren().add(txtField);
Text promptNode = TextInputSkinShim.getPromptNode(txtField);
assertEquals(promptWithLineBreaks.replace("\n",""), promptNode.getText());
txtField.promptTextProperty().unbind();
}

@Test
public void testPromptTextWithBindingWithoutLineBreaks() {
initStage();
txtField.setSkin(new TextFieldSkin(txtField));
String promptWithoutLineBreaks = "Prompt without LineBreaks";
StringProperty promptProperty = new SimpleStringProperty(promptWithoutLineBreaks);
txtField.promptTextProperty().bind(promptProperty);
root.getChildren().add(txtField);
Text promptNode = TextInputSkinShim.getPromptNode(txtField);
assertEquals(promptWithoutLineBreaks, promptNode.getText());
txtField.promptTextProperty().unbind();
}

@Test
public void testPromptTextWhenSettingValueWithLineBreaks() {
initStage();
txtField.setSkin(new TextFieldSkin(txtField));
String promptWithoutLineBreaks = "Prompt without LineBreaks";
String promptWithLineBreaks = "Prompt\nwith\nLineBreaks";
txtField.setPromptText(promptWithoutLineBreaks);
root.getChildren().add(txtField);
Text promptNode = TextInputSkinShim.getPromptNode(txtField);
assertEquals(promptWithoutLineBreaks, promptNode.getText());
txtField.setPromptText(promptWithLineBreaks);
assertEquals(promptWithLineBreaks.replace("\n",""), promptNode.getText());
}

@Test
public void testPromptTextWithNullValue() {
initStage();
txtField.setSkin(new TextFieldSkin(txtField));
String promptWithNull = null;
StringProperty promptPropertyNull = new SimpleStringProperty(promptWithNull);
txtField.promptTextProperty().bind(promptPropertyNull);
root.getChildren().add(txtField);
Text promptNode = TextInputSkinShim.getPromptNode(txtField);
assertNull(promptNode);
}

/*********************************************************************
* Miscellaneous Tests *
********************************************************************/
Original file line number Diff line number Diff line change
@@ -107,39 +107,6 @@ public void textDefaultsToEmptyString(Class<?> type) {
assertEquals("", textInput.getText());
}

@ParameterizedTest
@MethodSource("parameters")
public void bindPromptTextWithoutLineBreaks(Class<?> type) {
setup(type);
String promptWithoutLinebreaks = "Prompt without\tlinebreaks";
StringProperty promptProperty = new SimpleStringProperty(promptWithoutLinebreaks);
textInput.promptTextProperty().bind(promptProperty);
assertEquals(promptWithoutLinebreaks, textInput.getPromptText());
textInput.promptTextProperty().unbind();
}

@ParameterizedTest
@MethodSource("parameters")
public void bindPromptTextWithLineBreaks(Class<?> type) {
setup(type);
String promptWithLinebreaks = "Prompt\nwith\nLineBreaks\nand\nmixed\tcharacters \uD83C\uDF0D";
StringProperty promptProperty = new SimpleStringProperty(promptWithLinebreaks);
textInput.promptTextProperty().bind(promptProperty);
String expectedPromptWithoutLineBreaks = promptWithLinebreaks.replace("\n", "");
assertEquals(expectedPromptWithoutLineBreaks, textInput.getPromptText());
textInput.promptTextProperty().unbind();
}

@ParameterizedTest
@MethodSource("parameters")
public void bindPromptTextWithNull(Class<?> type) {
setup(type);
StringProperty promptPropertyNull = new SimpleStringProperty(null);
textInput.promptTextProperty().bind(promptPropertyNull);
assertNull(textInput.getPromptText());
textInput.promptTextProperty().unbind();
}

@ParameterizedTest
@MethodSource("parameters")
public void editableDefaultsToTrue(Class<?> type) {