package bluej.utility.javafx;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.ListChangeListener;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextArea;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.VBox;
import bluej.Config;
import bluej.stride.generic.InteractionManager;
| This is a class to make sure a TextArea always resizes to fit its content.
|
| In JavaFX, a TextArea always has a ScrollPane surrounding its content; this class
| tries to make it as if the ScrollPane doesn't exist, and the TextArea is always
| just the right size to fit its content.
|
public class ScrollFreeTextArea
{
private static final Scene scene = new Scene(new VBox(), 4000, 4000);
private static Scene calculationAidScene = null;
private static TextArea calculationAid = null;
private static String calculationAidFontCSS = null;
private static List<String> calculationAidStyleClass = null;
private final SimpleDoubleProperty scale = new SimpleDoubleProperty(1.0);
private final TextArea textArea;
private boolean initialised = false;
private double blankHeight;
private double suggestedOneLineHeight;
public ScrollFreeTextArea(InteractionManager editor)
{
this.textArea = new TextArea();
TextArea offScreen = new TextArea();
SimpleDoubleProperty contentHeight = new SimpleDoubleProperty();
@Override
public void changed(ObservableValue<?> observable, Object oldValue, Object newValue)
{
if (textArea.getScene() != null && textArea.getSkin() != null && !initialised)
{
initialised = true;
textArea.sceneProperty().removeListener(this);
textArea.skinProperty().removeListener(this);
ScrollFreeTextArea.this.recalculateOneTwoLineHeights(editor.getFontCSS().get());
textArea.getStyleClass().addListener((ListChangeListener<String>) c -> ScrollFreeTextArea.this.recalculateOneTwoLineHeights(editor.getFontCSS().get()));
textArea.snapshot(null, null);
ScrollPane p = ((ScrollPane) textArea.lookup(".scroll-pane"));
p.setFitToHeight(true);
p.setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
contentHeight.bind(new DoubleBinding()
{
private boolean accessedInternals = false;
private Region content;
@Override
protected double computeValue()
{
scene.setRoot(new Pane(offScreen));
offScreen.applyCss();
scene.getRoot().layout();
if (!accessedInternals)
{
((ScrollPane) offScreen.lookup(".scroll-pane")).setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
((ScrollPane) offScreen.lookup(".scroll-pane")).setFitToWidth(true);
content = (Region) offScreen.lookup(".content");
accessedInternals = true;
}
double prev = content.getPrefHeight();
content.setPrefHeight(Region.USE_COMPUTED_SIZE);
content.prefHeight(content.getWidth() + 1.0);
double r = content.prefHeight(content.getWidth());
content.setPrefHeight(r);
scene.setRoot(new VBox());
if (r <= blankHeight)
return scale.get() * (suggestedOneLineHeight + textArea.getPadding().getTop() + textArea.getPadding().getBottom());
else{ return scale.get() * (r + textArea.getPadding().getTop() + textArea.getPadding().getBottom());
}
}
{
bind(scale);
bind(textArea.widthProperty());
bind(textProperty());
bind(offScreen.styleProperty());
bind(textArea.paddingProperty());
}
});
}
}
};
textArea.sceneProperty().addListener(listener);
textArea.skinProperty().addListener(listener);
textArea.setWrapText(true);
offScreen.setWrapText(true);
textArea.setPrefRowCount(0);
textArea.setMinHeight(0);
textArea.prefHeightProperty().bind(contentHeight);
JavaFXUtil.bindList(offScreen.getStyleClass(), textArea.getStyleClass());
offScreen.textProperty().bind(textArea.textProperty());
offScreen.minWidthProperty().bind(offScreen.prefWidthProperty());
offScreen.prefWidthProperty().bind(textArea.widthProperty());
offScreen.maxWidthProperty().bind(offScreen.prefWidthProperty());
offScreen.setPrefRowCount(0);
offScreen.setMinHeight(0);
offScreen.styleProperty().bind(editor.getFontCSS());
JavaFXUtil.addChangeListener(editor.getFontCSS(), this::recalculateOneTwoLineHeights);
JavaFXUtil.addChangeListener(textArea.widthProperty(), w -> {
Platform.runLater(textArea::requestLayout);
});
}
private void recalculateOneTwoLineHeights(String fontSize)
{
blankHeight = calculateHeight(fontSize, "X");
double twoLine = calculateHeight(fontSize, "X\nX");
double threeLine = calculateHeight(fontSize, "X\nX\nX");
double extraLine = threeLine - twoLine;
suggestedOneLineHeight = twoLine - extraLine + 2
| fudge factor
|
}
private double calculateHeight(String fontCSS, String text)
{
if (calculationAid == null || calculationAidScene == null)
{
calculationAid = new TextArea();
calculationAid.setWrapText(true);
calculationAid.setPrefRowCount(0);
calculationAid.setMinHeight(0);
calculationAidScene = new Scene(new Pane(calculationAid), 4000, 4000);
Config.addEditorStylesheets(calculationAidScene);
}
if (!fontCSS.equals(calculationAidFontCSS))
{
calculationAid.setStyle(fontCSS);
calculationAidFontCSS = fontCSS;
calculationAid.applyCss();
}
if (!calculationAid.getStyleClass().equals(calculationAidStyleClass))
{
calculationAid.getStyleClass().setAll(textArea.getStyleClass());
calculationAidStyleClass = new ArrayList<>(textArea.getStyleClass());
calculationAid.applyCss();
}
calculationAid.setText(text);
calculationAid.layout();
((ScrollPane) calculationAid.lookup(".scroll-pane")).setVbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
((ScrollPane) calculationAid.lookup(".scroll-pane")).setFitToWidth(true);
Region content = (Region) calculationAid.lookup(".content");
double r = content.prefHeight(1000);
return r;
}
public void setPromptText(String s)
{
textArea.setPromptText(s);
}
public String getText()
{
return textArea.getText();
}
public void setText(String value)
{
textArea.setText(value);
}
public StringProperty textProperty()
{
return textArea.textProperty();
}
public StringProperty promptTextProperty()
{
return textArea.promptTextProperty();
}
public ReadOnlyIntegerProperty caretPositionProperty()
{
return textArea.caretPositionProperty();
}
public void positionCaret(int pos)
{
textArea.positionCaret(pos);
}
public int getLength()
{
return textArea.getLength();
}
public int getCaretPosition()
{
return textArea.getCaretPosition();
}
public void selectAll()
{
textArea.selectAll();
}
public boolean isDisable()
{
return textArea.isDisable();
}
public void setDisable(boolean value)
{
textArea.setDisable(value);
}
public boolean isFocused()
{
return textArea.isFocused();
}
public ObservableBooleanValue focusedProperty()
{
return textArea.focusedProperty();
}
protected void setFocusTraversable(boolean on)
{
textArea.setFocusTraversable(on);
}
protected void addTextStyleClasses(String... styleClasses)
{
JavaFXUtil.addStyleClass(textArea, styleClasses);
}
public Node getNode()
{
return textArea;
}
public ReadOnlyDoubleProperty heightProperty()
{
return textArea.heightProperty();
}
public void setPseudoclass(String name, boolean on)
{
JavaFXUtil.setPseudoclass(name, on, textArea);
}
public <T extends Event> void addEventFilter(EventType<T> eventType, EventHandler<? super T> eventFilter)
{
textArea.addEventFilter(eventType, eventFilter);
}
public void requestFocus()
{
textArea.requestFocus();
}
public void bindPrefMaxWidth(DoubleBinding amount)
{
textArea.maxWidthProperty().bind(amount);
textArea.prefWidthProperty().bind(textArea.maxWidthProperty());
}
public void insertAtCaret(String s)
{
textArea.insertText(textArea.getCaretPosition(), s);
}
public void shrinkToNothingUsing(SharedTransition animate)
{
scale.unbind();
scale.bind(animate.getOppositeProgress());
animate.addOnStopped(scale::unbind);
animate.addOnStopped(() -> textArea.setVisible(false));
}
public void growFromNothingUsing(SharedTransition animate)
{
textArea.setVisible(true);
scale.unbind();
scale.bind(animate.getProgress());
animate.addOnStopped(scale::unbind);
}
public Bounds getSceneBounds()
{
return textArea.localToScene(textArea.getBoundsInLocal());
}
static {
Config.addEditorStylesheets(scene);
}
}
top,
use,
map,
class ScrollFreeTextArea
. ScrollFreeTextArea
. changed
. computeValue
. recalculateOneTwoLineHeights
. calculateHeight
. setPromptText
. getText
. setText
. textProperty
. promptTextProperty
. caretPositionProperty
. positionCaret
. getLength
. getCaretPosition
. selectAll
. isDisable
. setDisable
. isFocused
. focusedProperty
. setFocusTraversable
. addTextStyleClasses
. getNode
. heightProperty
. setPseudoclass
. addEventFilter
. requestFocus
. bindPrefMaxWidth
. insertAtCaret
. shrinkToNothingUsing
. growFromNothingUsing
. getSceneBounds
406 neLoCode
+ 5 LoComm