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