package bluej.stride.slots;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import bluej.collect.StrideEditReason;
import bluej.editor.stride.FrameCatalogue;
import bluej.stride.framedjava.ast.links.PossibleLink;
import javafx.application.Platform;
import javafx.beans.binding.BooleanBinding;
import javafx.beans.binding.BooleanExpression;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.geometry.BoundingBox;
import javafx.scene.Node;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TextField;
import javafx.scene.input.Clipboard;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.Region;
import javafx.util.Duration;
import bluej.Config;
import bluej.stride.framedjava.ast.TextSlotFragment;
import bluej.stride.framedjava.elements.CodeElement;
import bluej.stride.framedjava.errors.CodeError;
import bluej.stride.framedjava.errors.ErrorAndFixDisplay;
import bluej.stride.framedjava.errors.ErrorAndFixDisplay.ErrorFixListener;
import bluej.stride.framedjava.frames.CodeFrame;
import bluej.stride.framedjava.slots.TextOverlayPosition;
import bluej.stride.generic.Frame;
import bluej.stride.generic.Frame.View;
import bluej.stride.generic.FrameContentRow;
import bluej.stride.generic.InteractionManager;
import bluej.stride.slots.SuggestionList.SuggestionListListener;
import bluej.utility.javafx.AnnotatableTextField;
import bluej.utility.javafx.ErrorUnderlineCanvas;
import bluej.utility.javafx.FXPlatformConsumer;
import bluej.utility.javafx.JavaFXUtil;
import bluej.utility.javafx.SharedTransition;
import threadchecker.OnThread;
import threadchecker.Tag;


| A slot which handles single-field text input, for example variable name definition. | | The class is abstract, with some abstract methods to do with specific context behaviour that | are implemented by slim subclasses, but the vast majority of the functionality lies in this class. | public abstract class TextSlot<SLOT_FRAGMENT extends TextSlotFragment> implements EditableSlot, ErrorFixListener, CopyableHeaderItem{ private final List<SlotValueListener> listeners = new ArrayList<SlotValueListener>(); protected final InteractionManager editor; protected final Frame frameParent; private final CodeFrame<? extends CodeElement> codeFrameParent; private final FrameContentRow row; private final SlotTextField field; private final ObjectProperty<SuggestionList> suggestionDisplayProperty = new SimpleObjectProperty<>(); private final CompletionCalculator completionCalculator; private final SimpleDoubleProperty suggestionXOffset = new SimpleDoubleProperty(); private SLOT_FRAGMENT slotElement; private ErrorAndFixDisplay errorAndFixDisplay; @OnThread(Tag.FXPlatform) private final List<CodeError> allErrors = new ArrayList<>(); @OnThread(Tag.FXPlatform) private final List<CodeError> shownErrors = new ArrayList<>(); private final List<Underline> underlines = new ArrayList<>(); private final ObservableList<String> recentValues = FXCollections.observableArrayList(); private CodeError hoverErrorCurrentlyShown; private final BooleanBinding effectivelyFocusedProperty;
| Creates a text slot. Will be called from subclasses only | | @param editor The editor in which we lie | @param frameParent The frame in which we lie | @param codeFrameParent Ditto, but typed as CodeFrame | @param row The row in which we lie | @param completionCalculator The completion calculator to be used for auto-completion. Null iff auto-completion should be disabled | @param stylePrefix The prefix to use for CSS style classes | @param hints Hints to show in the cheat sheet when this slot is focused. | protected TextSlot(InteractionManager editor, Frame frameParent, CodeFrame<? extends CodeElement> codeFrameParent, FrameContentRow row, CompletionCalculator completionCalculator, String stylePrefix, List<FrameCatalogue.Hint> hints) { this.editor = editor; this.completionCalculator = completionCalculator; this.frameParent = frameParent; this.codeFrameParent = codeFrameParent; if (frameParent != codeFrameParent) throw new IllegalArgumentException("frameParent and codeFrameParent are not same object"); this.row = row; field = new SlotTextField(stylePrefix, row.getOverlay()); editor.setupFocusableSlotComponent(this, field.getFocusableNode(), completionCalculator != null, row::getExtensions, hints); listeners.add((slot, oldValue, newValue, parent) -> { if (newValue.contains(";")) return false; else{ return true; } }); effectivelyFocusedProperty = field.focusedProperty().or(suggestionDisplayProperty.isNotNull()); }
| A property reflecting whether the field is "effectively focused" |* * "Effectively focused" means that either the field has actual JavaFX GUI * focus, or code completion is showing for this slot, meaning it doesn't * have GUI focus, but for our purposes it is logically the focus owner * within the editor. public ObservableBooleanValue effectivelyFocusedProperty() { return effectivelyFocusedProperty; }
| The class dealing with the actual GUI component | public class SlotTextField extends AnnotatableTextField { private String lastBeforePrefix; private String valueOnGain;
| Constructor. | @param stylePrefix The prefix to use for CSS style classes | @param overlay The overlay on which to draw errors, underlines, etc | private SlotTextField(String stylePrefix, ErrorUnderlineCanvas overlay) { super(overlay); addStyleClasses("text-slot", stylePrefix + "text-slot"); prefWidthProperty().set(10); SuggestionListListener suggestionListener = new SuggestionListListener() { @Override @OnThread(Tag.FXPlatform) public void suggestionListChoiceClicked(SuggestionList suggestionList, int highlighted) { executeSuggestion(suggestionList, highlighted); row.focusRight(TextSlot.this); } @Override @OnThread(Tag.FXPlatform) public Response suggestionListKeyTyped(SuggestionList suggestionList, KeyEvent event, int highlighted) { if (event.getCharacter().equals(" ") && completeIfPossible(suggestionList, highlighted)) { row.focusRight(TextSlot.this); return Response.DISMISS; } else if (!event.getCharacter().equals("\b")) injectEvent(event); return Response.CONTINUE; } @OnThread(Tag.FXPlatform) private boolean completeIfPossible(SuggestionList suggestionList, int highlighted) { if (highlighted != -1) { return executeSuggestion(suggestionList, highlighted); } else if (suggestionDisplayProperty.get().eligibleCount() == 1 && getText().length() > 0) { return executeSuggestion(suggestionList, suggestionDisplayProperty.get().getFirstEligible()); } return false; } @Override @OnThread(Tag.FXPlatform) public Response suggestionListKeyPressed(SuggestionList suggestionList, KeyEvent event, int highlighted) { switch (event.getCode()) { case BACK_SPACE: backspace(); return Response.CONTINUE; case LEFT: if (getCaretPosition() == 0) { row.focusLeft(TextSlot.this); return Response.DISMISS; } else { positionCaret(getCaretPosition() - 1); return Response.DISMISS; } case RIGHT: Optional<String> common = suggestionDisplayProperty.get().getLongestCommonPrefix(); if (common.isPresent()) { boolean single = suggestionDisplayProperty.get().eligibleCount() == 1; field.replaceText(getStartOfCurWord(), field.getCaretPosition(), common.get()); if (single) return Response.DISMISS; } else { row.focusRight(TextSlot.this); return Response.DISMISS; } break; case ENTER: if (executeSuggestion(suggestionList, highlighted)) { row.focusRight(TextSlot.this); return Response.DISMISS; } break; case ESCAPE: setTransparent(false); return Response.DISMISS; case TAB: if (event.isShiftDown()) row.focusLeft(TextSlot.this); else { row.focusRight(TextSlot.this); completeIfPossible(suggestionList, highlighted); } return Response.DISMISS; } return Response.CONTINUE; } @Override public void hidden() { suggestionDisplayProperty.set(null); setFakeCaretShowing(false); } }; this.onKeyPressedProperty().set(event -> { if (event.isShiftDown() && event.isControlDown() && event.getCharacter().length() > 0 && event.getCode() != KeyCode.CONTROL && event.getCode() != KeyCode.SHIFT) { row.notifyModifiedPress(event.getCode()); event.consume(); return; } switch (event.getCode()) { case UP: if (errorAndFixDisplay != null && errorAndFixDisplay.hasFixes() && errorAndFixDisplay.isShowing()) { errorAndFixDisplay.up(); } else { row.focusUp(TextSlot.this, false); } event.consume(); break; case DOWN: if (errorAndFixDisplay != null && errorAndFixDisplay.hasFixes() && errorAndFixDisplay.isShowing()) { errorAndFixDisplay.down(); } else { row.focusDown(TextSlot.this); } event.consume(); break; case LEFT: if (getSelection().getStart() == 0) { row.focusLeft(TextSlot.this); event.consume(); } break; case RIGHT: if (getSelection().getEnd() == getLength()) { row.focusRight(TextSlot.this); event.consume(); } break; case ENTER: if (errorAndFixDisplay != null) { errorAndFixDisplay.executeSelected(); } else { row.focusEnter(TextSlot.this); } event.consume(); break; case BACK_SPACE: if (getCaretPosition() == 0 && !hasSelection()) { for (SlotValueListener listener : listeners) { listener.backSpacePressedAtStart(TextSlot.this); } event.consume(); } break; case DELETE: if (getCaretPosition() == getLength() && anchorProperty().get() == getCaretPosition()) { for (SlotValueListener listener : listeners) { listener.deletePressedAtEnd(TextSlot.this); } event.consume(); } break; case SPACE: if (event.isControlDown()) { showSuggestionDisplay(suggestionListener); event.consume(); } break; case ESCAPE: row.escape(TextSlot.this); break; default: break; } }); JavaFXUtil.addFocusListener(getFocusableNode(), newValue -> { if (newValue) { valueOnGain = getText(); editor.beginRecordingState(TextSlot.this); setTransparent(false); Platform.runLater(this::deselect); showErrorAtCaret(getCaretPosition()); } else { setTransparent(!getText().isEmpty() && suggestionDisplayProperty.get() == null); editor.endRecordingState(TextSlot.this); if (errorAndFixDisplay != null) { errorAndFixDisplay.hide(); errorAndFixDisplay = null; } if (!getText().equals(valueOnGain)) { recentValues.removeAll(getText()); recentValues.removeAll(valueOnGain); recentValues.add(0, valueOnGain); while (recentValues.size() > 3) recentValues.remove(3); } valueChangedLostFocus(valueOnGain, getText()); } } }); this.textProperty().addListener((observable, oldValue, newValue) -> { slotElement = null; if (!isFocused() && suggestionDisplayProperty.get() == null) { if (newValue.length() > 0) { setTransparent(true); } } boolean allowed = true; for (SlotValueListener listener : listeners) { boolean listenerAllow = listener.valueChanged(TextSlot.this, oldValue, newValue, row); allowed = allowed && listenerAllow; } if (!allowed) { setText(oldValue); } else { JavaFXUtil.runPlatformLater(() -> { if (suggestionDisplayProperty.get() != null) { String beforeNewPrefix = getText().substring(0, getStartOfCurWord()); if (!beforeNewPrefix.equals(lastBeforePrefix)) { if (!beforeNewPrefix.endsWith("(")) showSuggestionDisplay(suggestionListener); } else { updateSuggestions(true); } } }); editor.modifiedFrame(frameParent, false); } }); minWidthProperty().bind(new DoubleBinding() {{ super.bind(textProperty()); super.bind(promptTextProperty()); super.bind(fontProperty()); } private String lastText; private double monospaceWidth; @Override protected double computeValue() { String effectiveText = textProperty().get().length() > 0 ? textProperty().get() : promptTextProperty().get(); return Math.max(10, 5 + measureString(effectiveText, true)); } }); prefWidthProperty().bind(minWidthProperty()); caretPositionProperty().addListener( (observable, oldValue, newVal) -> { if (isFocused()) JavaFXUtil.runNowOrLater(() -> showErrorAtCaret(newVal.intValue())); }); JavaFXUtil.onceInScene(getNode(), () -> setContextMenu(MenuItems.makeContextMenu(getMenuItems(true)))); } public final int getCaretPosition() { return caretPositionProperty().get(); } protected void setTransparent(boolean transparent) { field.setPseudoclass("bj-transparent", transparent); } public String getCurWord() { return getText().substring(getStartOfCurWord(), getCaretPosition()); } @OnThread(Tag.FXPlatform) private void updateSuggestions(boolean initialState) { String prefix = getCurWord(); suggestionDisplayProperty.get().calculateEligible(prefix, true, initialState); suggestionDisplayProperty.get().updateVisual(prefix); lastBeforePrefix = getText().substring(0, getStartOfCurWord()); } @OnThread(Tag.FXPlatform) private void showSuggestionDisplay(SuggestionListListener listener) { if (completionCalculator == null) return; suggestionXOffset.set(calculateCaretPosition(getStartOfCurWord())); FXPlatformConsumer<SuggestionList> handler = s -> { suggestionDisplayProperty.set(s); updateSuggestions(true); suggestionDisplayProperty.get().highlightFirstEligible(); suggestionDisplayProperty.get().show(field.getNode(), new BoundingBox(suggestionXOffset.get(), 0, 0, field.heightProperty().get())); field.setFakeCaretShowing(true); }; editor.afterRegenerateAndReparse(() -> { final int stringPos = field.getCaretPosition(); completionCalculator.withCalculatedSuggestionList(getSlotElement().getPosInSourceDoc(stringPos), codeFrameParent.getCode(), listener, suggList -> { editor.recordCodeCompletionStarted(getSlotElement(), stringPos, getCurWord(), suggList.getRecordingId()); handler.accept(suggList); }); }); } @Override protected double calculateCaretPosition(int beforeIndex) { return super.calculateCaretPosition(beforeIndex); } public TextOverlayPosition getOverlayLocation(int caretPos) { double x; if (caretPos == Integer.MAX_VALUE) x = widthProperty().get(); else { caretPos = Math.max(0, Math.min(caretPos, getLength())); x = calculateCaretPosition(caretPos); } return TextOverlayPosition.nodeToOverlay(field.getNode(), x, 0, getBaseline(), field.heightProperty().get()); } } public Region getNode() { return field.getNode(); } public void addValueListener(SlotValueListener listener) { listeners.add(listener); } @Override public Frame getParentFrame() { return frameParent; } public final String getText() { return field.textProperty().get(); } @Override public final SLOT_FRAGMENT getSlotElement() { if (slotElement == null) slotElement = createFragment(getText()); return slotElement; } @Override public void focusAndPositionAtError(CodeError err) { requestFocus(); field.positionCaret(err.getStartPosition()); } public final void setPromptText(String arg0) { field.promptTextProperty().set(arg0); } public final void setText(String arg0) { field.textProperty().set(arg0); } public void setText(SLOT_FRAGMENT f) { field.textProperty().set(f.getContent()); f.registerSlot(this); } public final ReadOnlyStringProperty textProperty() { return field.textProperty(); } @Override public void requestFocus(Focus on) { field.requestFocus(); if (null != on) switch (on) { case LEFT: field.positionCaret(0); break; case RIGHT: field.positionCaret(field.getLength()); break; case SELECT_ALL: field.selectAll(); break; default: } } @OnThread(Tag.FXPlatform) @Override public void addError(CodeError err) { allErrors.add(err); err.bindFresh(getFreshExtra(err).or(getParentFrame().freshProperty()), editor); recalculateShownErrors(); } protected BooleanExpression getFreshExtra(CodeError err) { return new ReadOnlyBooleanWrapper(false); } @Override @OnThread(Tag.FXPlatform) public void flagErrorsAsOld() { allErrors.forEach(CodeError::flagAsOld); } @Override @OnThread(Tag.FXPlatform) public void removeOldErrors() { allErrors.removeIf(CodeError::isFlaggedAsOld); recalculateShownErrors(); } @OnThread(Tag.FXPlatform) private void recalculateShownErrors() { shownErrors.clear(); List<CodeError> sortedErrors = allErrors.stream() .sorted((a, b) -> CodeError.compareErrors(a, b)).collect(Collectors.toList()); for (CodeError e : sortedErrors) { if (shownErrors.stream().allMatch(shown -> !shown.overlaps(e))) { shownErrors.add(e); e.setShowingIndicator(true); } else { e.setShowingIndicator(false); } } field.clearErrorMarkers(this); shownErrors.forEach(e -> field.drawErrorMarker(this, e.getStartPosition(), e.getEndPosition(), e.isJavaPos(), b -> showErrorHover(b ? e : null), e.visibleProperty())); if (field.isFocused()) showErrorAtCaret(field.getCaretPosition()); } @OnThread(Tag.FXPlatform) private void showErrorHover(CodeError error) { if (errorAndFixDisplay != null) { if (error != null && errorAndFixDisplay.getError().equals(error)){ hoverErrorCurrentlyShown = error; return; } final int caretPosition = field.getCaretPosition(); Optional<CodeError> errorAtCaret = shownErrors.stream() .filter(e -> e.getStartPosition() <= caretPosition && caretPosition <= e.getEndPosition()) .findFirst(); if (error == null && field.isFocused() && errorAtCaret.isPresent() && errorAtCaret.get().equals(errorAndFixDisplay.getError())){ hoverErrorCurrentlyShown = null; return; } errorAndFixDisplay.hide(); errorAndFixDisplay = null; } if (error != null && error.visibleProperty().get()) { hoverErrorCurrentlyShown = error; errorAndFixDisplay = new ErrorAndFixDisplay(editor, error, this); errorAndFixDisplay.showBelow(field.getNode(), Duration.ZERO); } } @OnThread(Tag.FXPlatform) private void showErrorAtCaret(int caretPosition) { Optional<CodeError> errorAtCaret = shownErrors.stream() .filter(e -> e.getStartPosition() <= caretPosition && caretPosition <= e.getEndPosition()) .findFirst(); if (errorAtCaret.isPresent() && errorAndFixDisplay != null && errorAndFixDisplay.getError().equals(errorAtCaret.get())) { return; } if (errorAndFixDisplay != null) { errorAndFixDisplay.hide(); errorAndFixDisplay = null; } if (errorAtCaret.isPresent() && errorAtCaret.get().visibleProperty().get()) { errorAndFixDisplay = new ErrorAndFixDisplay(editor, errorAtCaret.get(), this); errorAndFixDisplay.showBelow(field.getNode()); } } public int getStartOfCurWord() { for (int i = Math.min(field.getCaretPosition(), getText().length()) - 1; i >= 0; i--) { if (!Character.isJavaIdentifierPart(getText().charAt(i))) { return i + 1; } } return 0; } @Override public void cleanup() { if (editor.getCodeOverlayPane() != null) { if (errorAndFixDisplay != null) { final ErrorAndFixDisplay errorAndFixDisplayToHide = this.errorAndFixDisplay; JavaFXUtil.runNowOrLater(() -> errorAndFixDisplayToHide.hide()); this.errorAndFixDisplay = null; } } JavaFXUtil.runNowOrLater(() -> field.clearErrorMarkers(this)); } public void replace(int startPosInSlot, int endPosInSlot, String replacement) { String before = getText().substring(0, startPosInSlot); String after = getText().substring(endPosInSlot); setText(before + replacement + after); field.positionCaret(before.length() + replacement.length()); } @Override @OnThread(Tag.FXPlatform) public void fixedError(CodeError err) { allErrors.remove(err); recalculateShownErrors(); } @OnThread(Tag.FXPlatform) private boolean executeSuggestion(SuggestionList suggestionList, int highlighted) { final int position = getStartOfCurWord(); String word = field.getCurWord(); final boolean success = field.executeCompletion(completionCalculator, highlighted, position); if (success) { editor.recordCodeCompletionEnded(getSlotElement(), position, word, getText(), suggestionList.getRecordingId()); } return success; } public boolean isEmpty() { return field.textProperty().get().isEmpty(); } public void addFocusListener(Frame frame) { field.focusedProperty().addListener( (observable, oldValue, newValue) -> { if (!newValue) { frame.checkForEmptySlot(); } }); } @Override public boolean isFocused() { return field.isFocused(); } @Override public int getFocusInfo() { return field.getCaretPosition(); } @Override public Node recallFocus(int info) { requestFocus(Focus.LEFT); field.positionCaret(info); return field.getNode(); } @OnThread(Tag.FXPlatform) public Stream getCurrentErrors() { return shownErrors.stream(); } @OnThread(Tag.FXPlatform) public void addUnderline(Underline u) { underlines.add(u); drawUnderlines(); } @OnThread(Tag.FXPlatform) public void removeAllUnderlines() { underlines.clear(); drawUnderlines(); } @OnThread(Tag.FXPlatform) private void drawUnderlines() { field.clearUnderlines(); underlines.forEach(u -> field.drawUnderline(this, u.getStartPosition(), u.getEndPosition(), u.getOnClick())); } @Override public void saved() { } @Override public ObservableList getComponents() { return FXCollections.observableArrayList(field.getNode()); } @Override public TextOverlayPosition getOverlayLocation(int caretPos, boolean javaPos) { return field.getOverlayLocation(caretPos); } public abstract List findLinks(); public void lostFocus() { field.setTransparent(!getText().isEmpty()); } protected abstract SLOT_FRAGMENT createFragment(String content);
| Called when the slot has lost focus, and the value has changed since focus was gained. | | Allows us to perform actions like pop-up prompts or renaming the compilation unit. | @OnThread(Tag.FXPlatform) public abstract void valueChangedLostFocus(String oldValue, String newValue); @Override public void setView(View oldView, View newView, SharedTransition animate) { field.editableProperty().set(newView == View.NORMAL); field.disableProperty().set(newView != View.NORMAL); if (newView == Frame.View.JAVA_PREVIEW) { animate.addOnStopped(() -> { JavaFXUtil.setPseudoclass("bj-java-preview", newView == Frame.View.JAVA_PREVIEW, field.getFocusableNode()); }); } else { JavaFXUtil.setPseudoclass("bj-java-preview", newView == Frame.View.JAVA_PREVIEW, field.getFocusableNode()); } } protected Map getExtraContextMenuItems() { return Collections.emptyMap(); } @Override public final Map getMenuItems(boolean contextMenu) { Map<TopLevelMenu, MenuItems> itemMap = new HashMap<>(getExtraContextMenuItems()); final ObservableList<SortedMenuItem> menuItems = FXCollections.observableArrayList(); if (contextMenu) { menuItems.add(getRecentValuesMenu()); } final MenuItem cutItem = JavaFXUtil.makeMenuItem(Config.getString("frame.slot.cut"), field::cut, new KeyCodeCombination(KeyCode.X, KeyCodeCombination.SHORTCUT_DOWN)); final MenuItem copyItem = JavaFXUtil.makeMenuItem(Config.getString("frame.slot.copy"), field::copy, new KeyCodeCombination(KeyCode.C, KeyCodeCombination.SHORTCUT_DOWN)); final MenuItem pasteItem = JavaFXUtil.makeMenuItem(Config.getString("frame.slot.paste"), field::paste, Config.isMacOS() ? null : new KeyCodeCombination(KeyCode.V, KeyCodeCombination.SHORTCUT_DOWN)); menuItems.addAll( MenuItemOrder.CUT.item(cutItem), MenuItemOrder.COPY.item(copyItem), MenuItemOrder.PASTE.item(pasteItem)); itemMap.put(TopLevelMenu.EDIT, MenuItems.concat( new MenuItems(menuItems) { @Override @OnThread(Tag.FXPlatform) public void onShowing() { if (hoverErrorCurrentlyShown != null ){ errorAndFixDisplay.hide(); } boolean selectionPresent = field.hasSelection(); cutItem.setDisable(!selectionPresent); copyItem.setDisable(!selectionPresent); pasteItem.setDisable(!Clipboard.getSystemClipboard().hasString()); } }, itemMap.get(TopLevelMenu.EDIT) )); return itemMap; } private SortedMenuItem getRecentValuesMenu() { final Menu recent = new Menu(Config.getString("frame.slot.recent")); recent.setDisable(true); recentValues.addListener((ListChangeListener)c -> { recent.getItems().clear(); if (recentValues.isEmpty()) { recent.setDisable(true); } else { recent.setDisable(false); recentValues.forEach(v -> { MenuItem item = new MenuItem(v); item.setOnAction(e -> { editor.recordEdits(StrideEditReason.FLUSH); setText(v); editor.recordEdits(StrideEditReason.UNDO_LOCAL); }); recent.getItems().add(item); }); } }); return MenuItemOrder.RECENT_VALUES.item(recent); } @Override public boolean isAlmostBlank() { return getText().isEmpty(); } @Override public boolean isEditable() { return !field.disableProperty().get(); } @Override public void setEditable(boolean editable) { field.disableProperty().set(!editable); } @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); } @Override public int calculateEffort() { return Math.min(4, getText().length()); } }

.   TextSlot
.   effectivelyFocusedProperty

top, use, map, class SlotTextField

.   SlotTextField
.   suggestionListChoiceClicked
.   suggestionListKeyTyped
.   completeIfPossible
.   suggestionListKeyPressed
.   hidden
.   computeValue
.   getCaretPosition
.   setTransparent
.   getCurWord
.   updateSuggestions
.   showSuggestionDisplay
.   calculateCaretPosition
.   getOverlayLocation
.   getNode
.   addValueListener
.   getParentFrame
.   getText
.   getSlotElement
.   focusAndPositionAtError
.   setPromptText
.   setText
.   setText
.   textProperty
.   requestFocus
.   addError
.   getFreshExtra
.   flagErrorsAsOld
.   removeOldErrors
.   recalculateShownErrors
.   showErrorHover
.   showErrorAtCaret
.   getStartOfCurWord
.   cleanup
.   replace
.   fixedError
.   executeSuggestion
.   isEmpty
.   addFocusListener
.   isFocused
.   getFocusInfo
.   recallFocus
.   getCurrentErrors
.   addUnderline
.   removeAllUnderlines
.   drawUnderlines
.   saved
.   getComponents
.   getOverlayLocation
.   findLinks
.   lostFocus
.   createFragment
.   valueChangedLostFocus
.   setView
.   getExtraContextMenuItems
.   getMenuItems
.   onShowing
.   getRecentValuesMenu
.   isAlmostBlank
.   isEditable
.   setEditable
.   makeDisplayClone
.   calculateEffort




1308 neLoCode + 18 LoComm