package bluej.utility.javafx;
import javafx.animation.Timeline;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyDoubleProperty;
import javafx.beans.property.ReadOnlyIntegerProperty;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.ObservableList;
import javafx.collections.ObservableSet;
import javafx.css.PseudoClass;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.IndexRange;
import javafx.scene.control.Tooltip;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.text.Font;
import javafx.util.Duration;
import bluej.stride.slots.CompletionCalculator;
import bluej.stride.slots.EditableSlot;
import bluej.utility.Utility;
import bluej.utility.javafx.ErrorUnderlineCanvas.UnderlineInfo;
import threadchecker.OnThread;
import threadchecker.Tag;
| Uses ScalableHeightTextField because we need it in ImportDialog
|
public class AnnotatableTextField
{
private final ScalableHeightTextField field;
private final StackPane pane;
private final ErrorUnderlineCanvas errorMarker;
private final BooleanProperty fakeCaretShowing = new SimpleBooleanProperty(false);
public AnnotatableTextField(String str, ErrorUnderlineCanvas overlay, boolean startHidden)
{
field = new ScalableHeightTextField(str, startHidden);
field.focusedProperty().addListener((a, b, newVal) -> {
JavaFXUtil.setPseudoclass("bj-empty", newVal.booleanValue() == false && field.getLength() == 0, field);
});
if (overlay != null)
{
errorMarker = overlay;
pane = new StackPane(field);
}
else
{
pane = new StackPane();
errorMarker = new ErrorUnderlineCanvas(pane);
pane.getChildren().addAll(field, errorMarker.getNode());
}
field.setOnMouseMoved(e -> {
JavaFXUtil.setPseudoclass("bj-hyperlink", errorMarker.linkFromX(e.getSceneX()) != null, field);
});
field.setOnMouseClicked(e -> {
Utility.ifNotNull(errorMarker.linkFromX(e.getSceneX()), FXPlatformRunnable::run);
});
errorMarker.addExtraRedraw(g -> {
if (fakeCaretShowing.get())
{
double x = errorMarker.sceneToLocal(field.localToScene(calculateCaretPosition(field.getCaretPosition()), 0)).getX();
Paint p = g.getStroke();
g.setStroke(Color.BLACK);
g.strokeLine(x, 0, x, field.getHeight());
g.setStroke(p);
}
});
JavaFXUtil.addChangeListener(fakeCaretShowing, c -> JavaFXUtil.runNowOrLater(() -> errorMarker.redraw()));
JavaFXUtil.addChangeListener(field.caretPositionProperty(), p -> { if (fakeCaretShowing.get()) JavaFXUtil.runNowOrLater(() -> errorMarker.redraw());
});
}
public AnnotatableTextField(ErrorUnderlineCanvas overlay)
{
this("", overlay);
}
public AnnotatableTextField(String str, ErrorUnderlineCanvas overlay)
{
this(str, overlay, false);
}
public Region getNode()
{
return pane;
}
public Node getFocusableNode()
{
return field;
}
public final StringProperty textProperty()
{
return field.textProperty();
}
public final ReadOnlyDoubleProperty widthProperty()
{
return field.widthProperty();
}
public final ReadOnlyDoubleProperty heightProperty()
{
return field.heightProperty();
}
public final ReadOnlyBooleanProperty focusedProperty()
{
return field.focusedProperty();
}
public void positionCaret(int pos)
{
field.positionCaret(pos);
}
public final StringProperty promptTextProperty()
{
return field.promptTextProperty();
}
public void replaceText(int start, int end, String text)
{
field.replaceText(start, end, text);
}
public final boolean isFocused()
{
return field.isFocused();
}
public void requestFocus()
{
field.requestFocus();
}
public final int getLength()
{
return field.getLength();
}
public final ReadOnlyIntegerProperty caretPositionProperty()
{
return field.caretPositionProperty();
}
public final DoubleProperty minWidthProperty()
{
return field.minWidthProperty();
}
public final ReadOnlyObjectProperty selectionProperty()
{
return field.selectionProperty();
}
public final ReadOnlyIntegerProperty anchorProperty()
{
return field.anchorProperty();
}
public void end()
{
field.end();
}
public void deselect()
{
field.deselect();
}
public final DoubleProperty prefWidthProperty()
{
return field.prefWidthProperty();
}
public final ObjectProperty fontProperty()
{
return field.fontProperty();
}
public double measureString(String str, boolean includeInsets)
{
return JavaFXUtil.measureString(field, str, includeInsets, includeInsets);
}
public final IndexRange getSelection()
{
return field.getSelection();
}
public final ObjectProperty> onKeyPressedProperty()
{
return field.onKeyPressedProperty();
}
public final BooleanProperty editableProperty()
{
return field.editableProperty();
}
public final BooleanProperty disableProperty()
{
return field.disableProperty();
}
public Timeline getGrowToFullHeightTimeline(Duration dur)
{
return field.getGrowToFullHeightTimeline(dur);
}
public Timeline getShrinkToNothingTimeline(Duration dur)
{
return field.getShrinkToNothingTimeline(dur);
}
@OnThread(Tag.FXPlatform)
public boolean executeCompletion(CompletionCalculator cc, int highlighted, int startOfCurWord)
{
return cc.execute(field, highlighted, startOfCurWord);
}
| Calculates the position of the caret when it is before the given index
| in the text field, so passing 0 (before first char) will generally return
| 0 (far left of field), passing 1 will return X position at end of first char
| (before second char) and so on.
|
| Ideally this would be calculated using model-to-view or font metrics or similar,
| but since JavaFX doesn't seem to support these yet, we use the hack of
| adjusting the text content of a hidden text field and measuring its width.
|
| @param beforeIndex Character position, will calculate caret position before (to left) of char
| @return The X coordinate (0 being far left) of the caret, our best guess.
|
protected double calculateCaretPosition(int beforeIndex)
{
if (beforeIndex == Integer.MAX_VALUE)
return field.getWidth();
double paddingLeft = field.getPadding().getLeft();
double borderLeft = 0;
if (field.getBorder() != null && field.getBorder().getInsets() != null)
borderLeft = field.getBorder().getInsets().getLeft();
int index = Math.min(beforeIndex, field.getText().length());
return paddingLeft + borderLeft + measureString(field.getText().substring(0, index), false) + 1
| fudge factor
|
}
protected double getBaseline()
{
double height = field.getHeight() - 3 - field.getPadding().getBottom();
if (field.getBorder() != null && field.getBorder().getInsets() != null)
height -= field.getBorder().getInsets().getBottom();
return height;
}
public final ObjectProperty tooltipProperty()
{
return field.tooltipProperty();
}
@OnThread(Tag.FXPlatform)
public void clearUnderlines()
{
errorMarker.clearUnderlines();
field.setCursor(null);
}
@OnThread(Tag.FXPlatform)
public void drawUnderline(UnderlineInfo s, int startPosition, int endPosition, FXPlatformRunnable onClick)
{
errorMarker.addUnderline(s, startPosition, endPosition, onClick);
}
protected void addStyleClasses(String... styleClasses)
{
JavaFXUtil.addStyleClass(field, styleClasses);
}
protected void setPseudoclass(String pseudoClass, boolean on)
{
JavaFXUtil.setPseudoclass(pseudoClass, on, field);
}
@OnThread(Tag.FXPlatform)
public void drawErrorMarker(EditableSlot s, int startPos, int endPos, boolean javaPos, FXPlatformConsumer<Boolean> onHover, ObservableBooleanValue visible)
{
if ((startPos == 0 && endPos == 0) || getLength() == 0)
errorMarker.addErrorMarker(s, 0, Integer.MAX_VALUE, false, onHover, visible);
else{ errorMarker.addErrorMarker(s, startPos, endPos, javaPos, onHover, visible);
}
}
@OnThread(Tag.FXPlatform)
public void clearErrorMarkers(EditableSlot s)
{
errorMarker.clearErrorMarkers(s);
}
public void selectAll()
{
field.selectAll();
}
public void cut()
{
field.cut();
}
public void copy()
{
field.copy();
}
public void paste()
{
field.paste();
}
@OnThread(Tag.FXPlatform)
public void backspace()
{
field.deletePreviousChar();
}
public void setContextMenu(ContextMenu menu)
{
field.setContextMenu(menu);
}
public void injectEvent(KeyEvent e)
{
field.fireEvent(e.copyFor(null, field));
}
public void setFakeCaretShowing(boolean showing)
{
fakeCaretShowing.set(showing);
}
public Font getFont()
{
return field.getFont();
}
public double measureString(String text, Font font)
{
return JavaFXUtil.measureString(field, text, font, true, true);
}
public boolean hasSelection()
{
return field.getAnchor() != field.getCaretPosition();
}
public ObservableList getStyleClass()
{
return field.getStyleClass();
}
public ObservableSet getPseudoClassStates()
{
return field.getPseudoClassStates();
}
public StringProperty styleProperty()
{
return field.styleProperty();
}
}
top,
use,
map,
class AnnotatableTextField
. AnnotatableTextField
. AnnotatableTextField
. AnnotatableTextField
. getNode
. getFocusableNode
. textProperty
. widthProperty
. heightProperty
. focusedProperty
. positionCaret
. promptTextProperty
. replaceText
. isFocused
. requestFocus
. getLength
. caretPositionProperty
. minWidthProperty
. selectionProperty
. anchorProperty
. end
. deselect
. prefWidthProperty
. fontProperty
. measureString
. getSelection
. onKeyPressedProperty
. editableProperty
. disableProperty
. getGrowToFullHeightTimeline
. getShrinkToNothingTimeline
. executeCompletion
. calculateCaretPosition
. getBaseline
. tooltipProperty
. clearUnderlines
. drawUnderline
. addStyleClasses
. setPseudoclass
. drawErrorMarker
. clearErrorMarkers
. selectAll
. cut
. copy
. paste
. backspace
. setContextMenu
. injectEvent
. setFakeCaretShowing
. getFont
. measureString
. hasSelection
. getStyleClass
. getPseudoClassStates
. styleProperty
463 neLoCode
+ 11 LoComm