package bluej.stride.generic;

import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import bluej.stride.generic.ExtensionDescription.ExtensionSource;
import bluej.utility.Debug;
import javafx.beans.binding.DoubleExpression;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ObservableBooleanValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.util.Duration;

import bluej.stride.slots.CopyableHeaderItem;
import bluej.stride.slots.EditableSlot;
import bluej.stride.slots.Focus;
import bluej.stride.slots.HeaderItem;
import bluej.stride.slots.SlotParent;
import bluej.utility.Utility;
import bluej.utility.javafx.ErrorUnderlineCanvas;
import bluej.utility.javafx.HangingFlowPane;
import bluej.utility.javafx.JavaFXUtil;
import bluej.utility.javafx.SharedTransition;
import bluej.utility.javafx.binding.ConcatListBinding;
import bluej.utility.javafx.binding.ConcatMapListBinding;
import threadchecker.OnThread;
import threadchecker.Tag;


| A frame content item with a stack pane containing an overlay and a flow pane. | | A frame content row is used for the content of most frames (e.g. frame headers). | Essentially, anything except documentation pane and frame canvases sits within | a frame content row. All HeaderItem and EditableSlot things sit inside the | HangingFlowPane in a FrameContentRow. | public class FrameContentRow implements FrameContentItem, SlotParent<HeaderItem>{
| The frame that this FrameContentRow lives in | private final Frame parentFrame;
| The flow pane containing all the non-overlay content. Its content | is taken from headerRowComponents | private final HangingFlowPane headerRow = new HangingFlowPane();
| An overlay which spans the whole FrameContentRow, for showing red | error underlines. | private final ErrorUnderlineCanvas headerOverlay;
| A list bound to the header items which will then be used as the contents | of the headerRow. The headerRow contains lots of Nodes. Each HeaderItem | may produce many Nodes (e.g. an expression slot is one HeaderItem but usually | has many Nodes, e.g. a text field, an operator label, another text field.) | private final ObservableList<HeaderItem> headerRowComponents = FXCollections.observableArrayList();
| The stack pane which contains the headerRow (underneath) and headerOverlay (on top). | private final StackPane stackPane;
| Keeps track of whether the mouse is currently hovering over this FrameContentRow or not | private final BooleanProperty mouseHovering = new SimpleBooleanProperty(false);
| Quick constructor to create a FrameContentRow with the given content. | Has "anon-" style prefix. |*/ public FrameContentRow(Frame parentFrame, HeaderItem... items) { this(parentFrame, "anon-"); headerRowComponents.setAll(items); } /** * Standard constructor for FrameContentRow. Creates an empty one, with the * given CSS style class prefix. * * @param parentFrame The parent of this FrameContentRow | @param stylePrefix The style-class prefix. | public FrameContentRow(Frame parentFrame, String stylePrefix) { this.parentFrame = parentFrame; stackPane = new StackPane(); headerOverlay = new ErrorUnderlineCanvas(stackPane); stackPane.getChildren().addAll(headerRow, headerOverlay.getNode()); headerRow.setMinWidth(200.0); headerRow.getStyleClass().addAll("header-row", stylePrefix + "header-row"); headerRow.setAlignment(Pos.CENTER_LEFT); ConcatMapListBinding.bind(headerRow.getChildren(), headerRowComponents, HeaderItem::getComponents); StackPane.setMargin(headerRow, new Insets(0, 6, 0, 6)); stackPane.addEventFilter(MouseEvent.MOUSE_ENTERED, e -> mouseHovering.set(true)); stackPane.addEventFilter(MouseEvent.MOUSE_EXITED, e -> mouseHovering.set(false)); }
| Sets the margin for the headerRow flow pane within the stack pane. | public void setMargin(Insets insets) { StackPane.setMargin(headerRow, insets); } @Override public Stream getHeaderItemsDeep() { return getHeaderItemsDirect(); } @Override public Stream getHeaderItemsDirect() { return headerRowComponents.stream(); }
| Gets all the HeaderItems contained within this FrameContentRow which are | instances of EditableSlot (as determined by HeaderItem::asEditable). | public Stream getSlotsDirect() { return getHeaderItemsDirect().map(HeaderItem::asEditable).filter(x -> x != null); }
| Gets the overlay which spans this FrameContentRow (and thus also spans all contained | HeaderItems). | public ErrorUnderlineCanvas getOverlay() { return headerOverlay; }
| Sets the HeaderItems which are to be contained within this FrameContentRow, replacing | all previous content. | public void setHeaderItems(List<HeaderItem> headerItems) { headerRowComponents.setAll(headerItems); }
| Binds the contents of this FrameContentRow to the concatenation of the given | list of lists of HeaderItems. | public void bindContentsConcat(ObservableList<ObservableList<? extends HeaderItem>> src) { ConcatListBinding.bind(headerRowComponents, src); } @Override public Bounds getSceneBounds() { return stackPane.localToScene(stackPane.getBoundsInLocal()); } @Override public void focusLeft(HeaderItem src) { int index = headerRowComponents.indexOf(src); if (index < 0) { throw new IllegalStateException("Child slot not found in slot parent"); } EditableSlot s = prevFocusableBefore(index); if (s != null) { s.requestFocus(Focus.RIGHT); } else { parentFrame.focusLeft(this); } } @Override public void focusRight(HeaderItem src) { int index = headerRowComponents.indexOf(src); if (index < 0) { throw new IllegalStateException("Child slot not found in slot parent"); } EditableSlot s = nextFocusableAfter(index); if (s != null) { s.requestFocus(Focus.LEFT); } else { parentFrame.focusRight(this); } }
| Gets the next editable slot after the given position (exclusive), or null if there is none. | private EditableSlot nextFocusableAfter(int curSlot) { for (int i = curSlot + 1; i < headerRowComponents.size(); i++) { EditableSlot s = headerRowComponents.get(i).asEditable(); if (s != null && s.isEditable()) { return s; } } return null; }
| Gets the previous editable slot before the given position (exclusive), or null if there is none. | private EditableSlot prevFocusableBefore(int curSlot) { for (int i = curSlot - 1; i >= 0; i--) { EditableSlot s = headerRowComponents.get(i).asEditable(); if (s != null && s.isEditable()) { return s; } } return null; } @Override public void focusEnter(HeaderItem src) { parentFrame.focusEnter(this); } @Override @OnThread(Tag.FXPlatform) public void escape(HeaderItem src) { parentFrame.escape(this, src); } @Override public void focusDown(HeaderItem src) { parentFrame.focusDown(this); } @Override public void focusUp(HeaderItem src, boolean cursorToEnd) { parentFrame.focusUp(this, cursorToEnd); } @Override @OnThread(Tag.FXPlatform) public boolean backspaceAtStart(HeaderItem src) { return parentFrame.backspaceAtStart(this, src); } @Override @OnThread(Tag.FXPlatform) public boolean deleteAtEnd(HeaderItem src) { return parentFrame.deleteAtEnd(this, src); } @Override public void setView(Frame.View oldView, Frame.View newView, SharedTransition animation) { getHeaderItemsDirect().forEach(item -> item.setView(oldView, newView, animation)); animation.addOnStopped(() -> JavaFXUtil.runAfter(Duration.millis(100), headerOverlay::redraw)); } @Override public boolean focusBottomEndFromNext() { return focusLeftEndFromPrev(); } @Override public boolean focusLeftEndFromPrev() { Optional<EditableSlot> last = getSlotsDirect().filter(EditableSlot::isEditable).findFirst(); if (last.isPresent()) { last.get().requestFocus(Focus.LEFT); return true; } else{ return false; } } @Override public boolean focusRightEndFromNext() { Optional<EditableSlot> last = Utility.findLast(getSlotsDirect().filter(EditableSlot::isEditable)); if (last.isPresent()) { last.get().requestFocus(Focus.RIGHT); return true; } else{ return false; } } @Override public boolean focusTopEndFromPrev() { return focusLeftEndFromPrev(); } @Override public Optional getCanvas() { return Optional.empty(); } public final DoubleExpression flowPaneWidth() { return headerRow.widthProperty(); } @Override public Region getNode() { return stackPane; } public void setSnapToPixel(boolean b) { stackPane.setSnapToPixel(b); headerRow.setSnapToPixel(b); }
| Adds the given overlay to the stack pane, in front of the error underline canvas. | public void addOverlay(Node item) { stackPane.getChildren().add(item); }
| Gets the X coordinate (in scene coordinates) of the first Node in the flow pane. | public double getLeftFirstItem() { Node n = headerRow.getChildren().stream().findFirst().get(); return n.localToScene(n.getBoundsInLocal()).getMinX(); } public DoubleExpression flowPaneHeight() { return headerRow.heightProperty(); } public void applyCss() { headerRow.applyCss(); }
| Makes a display clone of this FrameContentRow. This is used for making a copy of | method headers for displaying the pinned method header. | @return A StackPane containing a display-identical (but immutable) copy of this FrameContentRow. | public StackPane makeDisplayClone(InteractionManager editor) { HangingFlowPane hfpCopy = new HangingFlowPane(); hfpCopy.getChildren().setAll(headerRowComponents.stream().flatMap(c -> ((CopyableHeaderItem)c).makeDisplayClone(editor) ).collect(Collectors.toList())); hfpCopy.prefWidthProperty().bind(headerRow.widthProperty()); hfpCopy.alignmentProperty().bind(headerRow.alignmentProperty()); hfpCopy.hangingIndentProperty().bind(headerRow.hangingIndentProperty()); JavaFXUtil.bindList(hfpCopy.getStyleClass(), headerRow.getStyleClass().filtered(c -> !c.equals("header-row"))); StackPane.setMargin(hfpCopy, new Insets(0, 6, 0, 6)); StackPane paneCopy = new StackPane(hfpCopy); return paneCopy; } public void setVisible(boolean visible) { stackPane.setVisible(visible); stackPane.setManaged(visible); } public ObservableBooleanValue mouseHoveringProperty() { return mouseHovering; } public final List getExtensions() { if (parentFrame.getHeaderRow() == this) return parentFrame.getAvailableExtensions(null, null); else{ return Collections.emptyList(); } } @OnThread(Tag.FXPlatform) public void notifyModifiedPress(KeyCode c) { List<ExtensionDescription> possibles = getExtensions().stream().filter(ext -> ext.validFor(ExtensionSource.MODIFIER) && ("" + ext.getShortcutKey()).equals(c.getName().toLowerCase())).collect(Collectors.toList()); if (possibles.size() == 1) { possibles.get(0).activate(); } else if (possibles.size() > 1) { Debug.message("Ambiguous alt keypress for " + parentFrame.getClass() + " for " + c); } }
| Called when the editor font size has changed, to redraw the overlay | @OnThread(Tag.FXPlatform) public void fontSizeChanged() { headerOverlay.redraw(); } }

.   FrameContentRow
.   setMargin
.   getHeaderItemsDeep
.   getHeaderItemsDirect
.   getSlotsDirect
.   getOverlay
.   setHeaderItems
.   bindContentsConcat
.   getSceneBounds
.   focusLeft
.   focusRight
.   nextFocusableAfter
.   prevFocusableBefore
.   focusEnter
.   escape
.   focusDown
.   focusUp
.   backspaceAtStart
.   deleteAtEnd
.   setView
.   focusBottomEndFromNext
.   focusLeftEndFromPrev
.   focusRightEndFromNext
.   focusTopEndFromPrev
.   getCanvas
.   flowPaneWidth
.   getNode
.   setSnapToPixel
.   addOverlay
.   getLeftFirstItem
.   flowPaneHeight
.   applyCss
.   makeDisplayClone
.   setVisible
.   mouseHoveringProperty
.   getExtensions
.   notifyModifiedPress
.   fontSizeChanged




470 neLoCode + 36 LoComm