package bluej.stride.framedjava.slots;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
import javafx.beans.binding.DoubleExpression;
import javafx.beans.property.ObjectProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import bluej.stride.framedjava.slots.InfixStructured.CaretPosMap;
import bluej.stride.framedjava.slots.InfixStructured.IntCounter;
import bluej.stride.generic.Frame;
import bluej.stride.generic.Frame.View;
import bluej.stride.generic.InteractionManager;
import bluej.stride.slots.EditableSlot.MenuItems;
import bluej.utility.javafx.DelegableScalableTextField;
import bluej.utility.javafx.FXConsumer;
import bluej.utility.javafx.FXPlatformRunnable;
import bluej.utility.javafx.HangingFlowPane;
import bluej.utility.javafx.JavaFXUtil;
import bluej.utility.javafx.SharedTransition;
import threadchecker.OnThread;
import threadchecker.Tag;
| A single text field in an expression slot. This usually features
| one alphanumeric identifier or a blank. If you have say "getWorld().setX(10+3)"
|* then the fields will be square bracketed as follows:
* [getWorld]([])[].[setX]([10]+[3])[]
*
* This component encapsulates the actual GUI item rather than inheriting from it.
*/
// Package-visible
|
|class StructuredSlotField implements StructuredSlotComponent{
|
|/**
| The actual GUI component. Delegates most of its behaviour back to this class.
|
private final DelegableScalableTextField<StructuredSlotField> field;
| The immediate parent expression of this field. Is often not the
| same as the top-level expression of the whole slot. e.g.
| setFoo(10+3) -- the field for the 10 has as its parent the brackets,
| not the slot as a whole.
|
private final InfixStructured parent;
| Creates an StructuredSlotField with the given parent and content
| @param parent Parent of this field
| @param content Initial content of this field
| @param stringLiteral Whether we are the field directly inside a string literal.
| This affects some of the behaviour.
|
public StructuredSlotField(InfixStructured parent, String content, boolean stringLiteral)
{
this.parent = parent;
field = new DelegableScalableTextField<>(parent, this, content);
JavaFXUtil.addStyleClass(field, "expression-slot-field");
if (stringLiteral)
JavaFXUtil.addStyleClass(field, "expression-string-literal");
FXPlatformRunnable shrinkGrow = () -> {
boolean suggesting = parent.suggestingFor(StructuredSlotField.this);
if (field.isFocused() == false && !suggesting)
{
notifyLostFocus(null);
}
else
{
JavaFXUtil.setPseudoclass("bj-transparent", false, field);
}
};
field.focusedProperty().addListener(new ChangeListener<Boolean>() {
@Override
@OnThread(value = Tag.FXPlatform, ignoreParent = true)
public void changed(ObservableValue<? extends Boolean> observable, Boolean old, Boolean focused)
{
shrinkGrow.run();
parent.caretMoved();
if (focused)
{
parent.getSlot().notifyGainFocus(StructuredSlotField.this);
}
}
});
field.textProperty().addListener(new ChangeListener<String>() {
@Override
@OnThread(value = Tag.FXPlatform, ignoreParent = true)
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue)
{
shrinkGrow.run();
if (!stringLiteral)
{
updateBreaks();
}
}
});
field.promptTextProperty().addListener(new ChangeListener<String>() {
@Override
@OnThread(value = Tag.FXPlatform, ignoreParent = true)
public void changed(ObservableValue<? extends String> observable, String oldValue,
String newValue)
{
shrinkGrow.run();
if (!stringLiteral)
{
updateBreaks();
}
}
});
JavaFXUtil.initializeCustomHelp(parent.getEditor(), field, this::calculateTooltip, true);
JavaFXUtil.onceInScene(field, () -> {
shrinkGrow.run();
if (parent.getSlot() != null)
field.setContextMenu(MenuItems.makeContextMenu(parent.getSlot().getMenuItems(true)));
}
});
if (!stringLiteral)
updateBreaks();
}
| HangingFlowPane, which will always be our parent container, keeps track
| of which items it can break before. This method updates whether you can
| break before this field, following the rule that: you can break before any
| field that has non-empty text or non-empty prompt text.
|
| This method should not be called if we are a string literal, because
| an empty string literal field behaves differently to an empty expression field.
|
private void updateBreaks()
{
HangingFlowPane.setBreakBefore(field, !getText().isEmpty() || !field.getPromptText().isEmpty());
}
| Calculates the scene X position of the given location within the field
|
private double calculateSceneX(CaretPos pos)
{
return field.calculateSceneX(pos.index);
}
| Calculates the scene X position of the given Y offset with the field
|
private double calculateSceneY(double y)
{
return field.localToScene(new Point2D(0, y)).getY();
}
@Override
public TextOverlayPosition calculateOverlayPos(CaretPos pos)
{
return TextOverlayPosition.fromScene(calculateSceneX(pos),
calculateSceneY(0.0),
calculateSceneY(field.getBaselineOffset()),
calculateSceneY(field.getHeight()), this);
}
| Calculate the position of the end of the text field. Note that
| this may not be the same as calculateOverlayPos applied to
| the last position with a field. For example, if the field is empty
| but has prompt text, calculateOverlayPos would return the left-hand
| side of the field (being the position for 0, the last position in the field),
| whereas this method would return the visible right-hand edge.
|
public TextOverlayPosition calculateOverlayEnd()
{
return TextOverlayPosition.fromScene(field.localToScene(field.getBoundsInLocal()).getMaxX(),
calculateSceneY(0.0),
calculateSceneY(field.getBaselineOffset()),
calculateSceneY(field.getHeight()), this);
}
@Override
public void focusAtStart()
{
focusAt(0);
}
| Focus the field, and position cursor at the given position
|
private void focusAt(int i)
{
field.requestFocus();
field.positionCaret(i);
}
@Override
public void focusAtEnd()
{
focusAt(field.getLength());
}
@Override
public Node focusAtPos(CaretPos caretPos)
{
focusAt(caretPos.index);
return field;
}
@Override
public CaretPos getStartPos()
{
return new CaretPos(0, null);
}
@Override
public CaretPos getEndPos()
{
return new CaretPos(field.getLength(), null);
}
public boolean isEmpty()
{
return field.getText().equals("");
}
public void requestFocus()
{
field.requestFocus();
}
@Override
public PosAndDist getNearest(double sceneX, double sceneY, boolean allowDescend, boolean anchorInItem)
{
double topYDist = Math.abs(calculateSceneY(0.0) - sceneY);
double bottomYDist = Math.abs(calculateSceneY(field.getHeight() - 1.0) - sceneY);
PosAndDist nearest = new PosAndDist();
for (int j = 0; j <= field.getLength(); j++)
{
CaretPos pos = new CaretPos(j, null);
double xDist = calculateSceneX(pos) - sceneX;
double dist = Math.hypot(xDist, Math.min(topYDist, bottomYDist));
nearest = PosAndDist.nearest(nearest, new PosAndDist(pos, dist));
}
Bounds b = field.localToScene(field.getBoundsInLocal());
nearest = PosAndDist.nearest(nearest, new PosAndDist(new CaretPos(0, null), Math.hypot(b.getMinX() - sceneX, Math.min(topYDist, bottomYDist))));
nearest = PosAndDist.nearest(nearest, new PosAndDist(new CaretPos(field.getLength(), null), Math.hypot(b.getMaxX() - sceneX, Math.min(topYDist, bottomYDist))));
return nearest;
}
@Override
public CaretPos getSelectIntoPos(boolean atEnd)
{
return new CaretPos(atEnd ? field.getLength() : 0, null);
}
public String getText()
{
return field.getText();
}
public void setText(String s, StructuredSlot.ModificationToken token)
{
token.check();
field.setText(s);
}
@Override
public String getCopyText(CaretPos from, CaretPos to)
{
int start = from == null ? 0 : from.index;
int end = to == null ? field.getLength() : to.index;
return field.getText().substring(start, end);
}
@Override
public String getJavaCode()
{
return field.getText();
}
@Override
public CaretPos getCurrentPos()
{
if (field.isFocused())
{
return new CaretPos(field.getCaretPosition(), null);
}
return null;
}
public void setPromptText(String s)
{
field.setPromptText(s);
}
@Override
public ObservableList getComponents()
{
return FXCollections.observableArrayList(field);
}
@Override
public List mapCaretPosStringPos(IntCounter len, boolean javaString)
{
String text = getText();
List<CaretPosMap> r = Collections.singletonList(new CaretPosMap(null, len.counter, len.counter + text.length()));
len.counter += text.length();
return r;
}
@Override
public Region getNodeForPos(CaretPos pos)
{
return field;
}
@Override
public String testingGetState(CaretPos pos)
{
if (pos == null)
return "{" + field.getText() + "}";
else
{
return "{" + field.getText().substring(0, pos.index) + "$" + field.getText().substring(pos.index) + "}";
}
}
@Override
public boolean isFocused()
{
return field.isFocused();
}
@Override
public boolean isFieldAndEmpty()
{
return field.getText().isEmpty();
}
public ObjectProperty> onKeyPressedProperty()
{
return field.onKeyPressedProperty();
}
public DoubleExpression heightProperty()
{
return field.heightProperty();
}
@Override
public void insertSuggestion(CaretPos p, String name, char opening, List<String> params, StructuredSlot.ModificationToken token)
{
if (params != null)
throw new IllegalArgumentException();
setText(getText().substring(0, p.index) + name + getText().substring(p.index), token);
}
@OnThread(Tag.FXPlatform)
private void calculateTooltip(FXConsumer<String> tooltipConsumer)
{
if (!getText().equals("") && !isFocused())
tooltipConsumer.accept("");
else{ parent.calculateTooltipFor(this, tooltipConsumer);
}
}
@Override
public Stream getAllStartEndPositionsBetween(CaretPos start, CaretPos end)
{
if (start == null)
start = getStartPos();
if (end == null)
end = getEndPos();
return Stream.of(calculateOverlayPos(start), calculateOverlayPos(end));
}
@Override
public Stream> getAllExpressions()
{
return Stream.empty();
}
public void addEventHandler(EventType<MouseEvent> mouseEvent, EventHandler<? super MouseEvent> eventHandler)
{
field.addEventHandler(mouseEvent, eventHandler);
}
public ObservableStringValue textProperty()
{
return field.textProperty();
}
public void setPseudoclass(String name, boolean on)
{
JavaFXUtil.setPseudoclass(name, on, field);
}
@Override
public void setView(View oldView, View newView, SharedTransition animate)
{
field.setEditable(newView == View.NORMAL);
field.setDisable(newView != View.NORMAL);
if (newView == Frame.View.JAVA_PREVIEW)
{
animate.addOnStopped(() -> {
JavaFXUtil.setPseudoclass("bj-java-preview", newView == Frame.View.JAVA_PREVIEW, field);
});
}
else
{
JavaFXUtil.setPseudoclass("bj-java-preview", newView == Frame.View.JAVA_PREVIEW, field);
}
}
@OnThread(Tag.FXPlatform)
public void cut()
{
field.cut();
}
@OnThread(Tag.FXPlatform)
public void copy()
{
field.copy();
}
@OnThread(Tag.FXPlatform)
public void paste()
{
field.paste();
}
@Override
public boolean isAlmostBlank()
{
return isEmpty();
}
@Override
public void notifyLostFocus(StructuredSlotField except)
{
boolean collapsible = parent.isCollapsible(StructuredSlotField.this);
boolean empty = field.getText().isEmpty() && field.getPromptText().isEmpty();
if (empty && collapsible)
{
JavaFXUtil.setPseudoclass("bj-transparent", true, field);
}
else
{
JavaFXUtil.setPseudoclass("bj-transparent", collapsible || !field.getText().isEmpty(), field);
}
}
@Override
public void setEditable(boolean editable)
{
field.setDisable(!editable);
}
@OnThread(Tag.FXPlatform)
public void nextWord()
{
field.nextWord();
}
@OnThread(Tag.FXPlatform)
public void previousWord()
{
field.previousWord();
}
@Override
public boolean isNumericLiteral()
{
return getText().matches("\\A\\d*\\z");
}
@Override
public int calculateEffort()
{
return Math.min(3, field.getText().length());
}
@Override
public Stream makeDisplayClone(InteractionManager editor)
{
TextField f = new TextField();
f.textProperty().bind(field.textProperty());
f.prefWidthProperty().bind(field.prefWidthProperty());
JavaFXUtil.bindList(f.getStyleClass(), field.getStyleClass());
JavaFXUtil.bindPseudoclasses(f, field.getPseudoClassStates());
JavaFXUtil.setPseudoclass("bj-pinned", true, f);
f.styleProperty().bind(field.styleProperty().concat(editor.getFontCSS()));
return Stream.of(f);
}
}
. StructuredSlotField
. changed
. changed
. changed
. updateBreaks
. calculateSceneX
. calculateSceneY
. calculateOverlayPos
. calculateOverlayEnd
. focusAtStart
. focusAt
. focusAtEnd
. focusAtPos
. getStartPos
. getEndPos
. isEmpty
. requestFocus
. getNearest
. getSelectIntoPos
. getText
. setText
. getCopyText
. getJavaCode
. getCurrentPos
. setPromptText
. getComponents
. mapCaretPosStringPos
. getNodeForPos
. testingGetState
. isFocused
. isFieldAndEmpty
. onKeyPressedProperty
. heightProperty
. insertSuggestion
. calculateTooltip
. getAllStartEndPositionsBetween
. getAllExpressions
. addEventHandler
. textProperty
. setPseudoclass
. setView
. cut
. copy
. paste
. isAlmostBlank
. notifyLostFocus
. setEditable
. nextWord
. previousWord
. isNumericLiteral
. calculateEffort
. makeDisplayClone
629 neLoCode
+ 29 LoComm