package bluej.stride.slots;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Stream;
import bluej.stride.framedjava.slots.TypeSlot;
import bluej.stride.generic.InteractionManager;
import bluej.utility.javafx.FXRunnable;
import javafx.application.Platform;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import bluej.stride.generic.Frame;
import bluej.utility.Utility;
import bluej.utility.javafx.binding.DeepListBinding;
| A class which manages a list of comma-separated type slots, e.g. an implements
| list or a throws declaration.
|
| This class holds a list of TypeSlot, and handles the use of backspace, delete,
| and typing commas to shrink/extend the list.
|
public class TypeList
{
| The list of header items to show; this will be a list of type slots with
| comma slot labels inbetween, and prefixLabel on the front.
|
private final ObservableList<HeaderItem> headerItems = FXCollections.observableArrayList();
| The list of type slots currently in this type list
|
protected final ObservableList<TypeSlot> typeSlots = FXCollections.observableArrayList();
| A piece of code to generate a new type slot when we need one. Supplied
| by our creator (easier to pass lambda than have to subclass TypeList every time).
|
private final Supplier<TypeSlot> slotGenerator;
| The frame which we are contained in.
|
private final Frame parentFrame;
| A piece of code to focus on the item after this type list. Called
| when the user deletes all items in the type list, and we need somewhere
| else to focus.
|
private final FXRunnable focusOnNext;
| A reference to the editor, used to trigger recompile when the list changes.
|
private InteractionManager editor;
| A property keeping track of whether any of the slots in this type list are currently focused.
|
private final BooleanProperty focusedProperty = new SimpleBooleanProperty(false);
| Constructor. Protected access because we expect to be subclassed for use anyway.
|
| @param label The label to display before the list, e.g. "implements". Must not be null.
|* @param parentFrame The frame this type list is contained in
* @param slotGenerator A piece of code to generate a new TypeSlot for this list when needed
* @param focusOnNext An action to focus on the item after this list, for when the user
| blanks the type list. (Note that at the moment, this means when the user
| blanks the list using backspace, we focus the item after rather than the
| item before, which is slightly odd. But type lists are rarely used.)
| @param editor A reference to the editor, used to notify about recompiles.
|
protected TypeList(String label, Frame parentFrame, Supplier<TypeSlot> slotGenerator, FXRunnable focusOnNext, InteractionManager editor)
{
this.parentFrame = parentFrame;
this.slotGenerator = slotGenerator;
this.focusOnNext = focusOnNext;
this.editor = editor;
final SlotLabel prefixLabel = new SlotLabel(label);
new DeepListBinding<HeaderItem>(headerItems) {
@Override
protected Stream> getListenTargets()
{
return Stream.of(typeSlots);
}
@Override
protected Stream calculateValues()
{
if (typeSlots.isEmpty())
return Stream.empty();
ArrayList<HeaderItem> commas = new ArrayList<>();
for (int i = 0; i < typeSlots.size() - 1; i++)
commas.add(new SlotLabel(", "));
return Utility.concat(Stream.of(prefixLabel), Utility.interleave(typeSlots.stream().map(h -> (HeaderItem)h), commas.stream()));
}
}.startListening();
final ChangeListener<Boolean> focusListener = (a, b, newVal) -> updateFocusedProperty();
typeSlots.addListener((ListChangeListener<? super TypeSlot>) change -> {
while (change.next())
{
if (change.wasAdded())
{
change.getAddedSubList().forEach(slot -> slot.effectivelyFocusedProperty().addListener(focusListener));
}
if (change.wasRemoved())
{
change.getRemoved().forEach(slot -> slot.effectivelyFocusedProperty().removeListener(focusListener));
}
}
updateFocusedProperty();
});
}
private void updateFocusedProperty()
{
focusedProperty.set(typeSlots.stream().anyMatch(slot -> slot.effectivelyFocusedProperty().getValue()));
}
public ObservableList getHeaderItems()
{
return headerItems;
}
| Add a new type slot before the given index (0 <= index <= typeSlots.size())
| @return The new type slot, which will just have been added to the typeSlots list.
|
private TypeSlot addTypeSlot(int index)
{
final TypeSlot slot = slotGenerator.get();
slot.addBackspaceAtStartListener(() -> backSpacePressedAtStart(slot));
slot.addDeleteAtEndListener(() -> deletePressedAtEnd(slot));
slot.onTopLevelComma((before, after) -> {
TypeSlot newSlot = addTypeSlot(typeSlots.indexOf(slot) + 1);
slot.setText(before);
newSlot.setText(after);
newSlot.requestFocus(Focus.LEFT);
});
slot.addFocusListener(parentFrame);
slot.addClosingChar(' ');
typeSlots.add(index, slot);
return slot;
}
private boolean backSpacePressedAtStart(TypeSlot slot)
{
int index = typeSlots.indexOf(slot);
String remainder = delete((TypeSlot)slot);
if (index - 1 >= 0 && index - 1 < typeSlots.size())
{
TypeSlot prev = typeSlots.get(index - 1);
prev.setText(prev.getText() + remainder);
prev.requestFocus();
prev.recallFocus(prev.getText().length() - remainder.length());
return true;
}
else
{
focusOnNext.run();
return true;
}
}
private boolean deletePressedAtEnd(TypeSlot slot)
{
int index = typeSlots.indexOf(slot);
if (index < typeSlots.size() - 1) {
String remainder = delete(typeSlots.get(index + 1));
String prev = typeSlots.get(index).getText();
typeSlots.get(index).setText(prev + remainder);
typeSlots.get(index).recallFocus(prev.length());
return false;
}
else {
delete((TypeSlot) slot);
focusOnNext.run();
return true;
}
}
private String delete(TypeSlot slot)
{
slot.cleanup();
typeSlots.remove(slot);
editor.modifiedFrame(parentFrame, false);
return slot.getText();
}
public void addTypeSlotAtEnd(String content, boolean requestFocus)
{
TypeSlot slot = addTypeSlot(typeSlots.size());
slot.setText(content);
if (requestFocus)
slot.requestFocus(Focus.LEFT);
}
public void setTypes(List<String> types)
{
while (typeSlots.size() > 0)
{
delete(typeSlots.get(typeSlots.size() - 1));
}
types.forEach(t -> addTypeSlotAtEnd(t, false));
}
public Stream getTypeSlots()
{
return typeSlots.stream();
}
public void ensureAtLeastOneSlot()
{
if (typeSlots.isEmpty())
addTypeSlotAtEnd("", false);
}
public void clearIfSingleEmpty()
{
if (typeSlots.size() == 1 && typeSlots.get(0).isEmpty())
delete(typeSlots.get(0));
}
public ReadOnlyBooleanProperty focusedProperty()
{
return focusedProperty;
}
public void removeIndex(int index)
{
if (index >= 0 && index < typeSlots.size())
delete(typeSlots.get(index));
}
}
top,
use,
map,
class TypeList
. TypeList
. getListenTargets
. calculateValues
. updateFocusedProperty
. getHeaderItems
. addTypeSlot
. backSpacePressedAtStart
. deletePressedAtEnd
. delete
. addTypeSlotAtEnd
. setTypes
. getTypeSlots
. ensureAtLeastOneSlot
. clearIfSingleEmpty
. focusedProperty
. removeIndex
276 neLoCode
+ 23 LoComm