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 extends PossibleLink> 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