package bluej.stride.generic;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import bluej.Config;
import bluej.stride.framedjava.slots.StructuredSlot;
import bluej.stride.generic.ExtensionDescription.ExtensionSource;
import javafx.beans.binding.When;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
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.Bounds;
import javafx.scene.Node;
import javafx.scene.SnapshotParameters;
import javafx.scene.effect.Effect;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.Border;
import javafx.scene.layout.BorderStroke;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import bluej.collect.StrideEditReason;
import bluej.stride.framedjava.elements.CodeElement;
import bluej.stride.framedjava.errors.CodeError;
import bluej.stride.framedjava.errors.ErrorShower;
import bluej.stride.framedjava.frames.BlankFrame;
import bluej.stride.framedjava.frames.CodeFrame;
import bluej.stride.framedjava.frames.FrameHelper;
import bluej.stride.operations.CopyFrameAsImageOperation;
import bluej.stride.operations.CopyFrameAsJavaOperation;
import bluej.stride.operations.CopyFrameAsStrideOperation;
import bluej.stride.operations.CutFrameOperation;
import bluej.stride.operations.DeleteFrameOperation;
import bluej.stride.operations.DisableFrameOperation;
import bluej.stride.operations.EnableFrameOperation;
import bluej.stride.operations.FrameOperation;
import bluej.stride.slots.EditableSlot;
import bluej.stride.slots.FocusParent;
import bluej.stride.slots.HeaderItem;
import bluej.stride.slots.SlotLabel;
import bluej.utility.Debug;
import bluej.utility.Utility;
import bluej.utility.javafx.BetterVBox;
import bluej.utility.javafx.FXRunnable;
import bluej.utility.javafx.JavaFXUtil;
import bluej.utility.javafx.SharedTransition;
import threadchecker.OnThread;
import threadchecker.Tag;
| The base frame from which specific frames are derived.
|
| Frame relates primarily to the GUI representation of a frame. For the semantic side, see
| the CodeElement class instead.
|
| Frames often implement the CodeFrame interface, from which you can generate a CodeElement
| (e.g. for saving and compiling). Most CodeElements are also frame factories (e.g. for loading).
|
| @author Fraser McKay
|
public abstract class Frame implements CursorFinder, FocusParent<FrameContentItem>, ErrorShower{
| The list of contents of a frame. The primary dimension of a frame is vertical:
| the FrameContentItem objects in this list are laid out vertically within the frame,
| with the first in the list shown at the top. FrameContentItem is typically either
| a FrameContentRow (a flow pane, e.g. for frame headers or single frame contents), or
| a FrameCanvas (e.g. body of a while frame), or documentation (e.g. top of method frame)
|
| FrameContentItem is a logical container, not a GUI item directly.
|
protected final ObservableList<FrameContentItem> contents = FXCollections.observableArrayList();
| All frames have a header row, so we include it in the base class. The header will
| also always (I *think*) be in the contents list above, but a reference is kept separately for convenience).
| header is never null.
|
protected final FrameContentRow header;
| The headerCaptionLabel is often, but not always, present in the header. In an IfFrame
| this is the label showing "if". Method frames don't have one. Again, it's kept as a
|* separate reference for convenience. It may be null.
*/
protected final SlotLabel headerCaptionLabel;
/**
* Property tracking where this frame is enabled or disabled (akin to commented out)
protected final BooleanProperty frameEnabledProperty = new SimpleBooleanProperty(true);
| The actual GUI control that contains the frame content (based on this.contents). Never null.
|
private final BetterVBox frameContents;
| When the frame is disabled, we keep track of whether this is the root (the highest level disabled frame),
| because only the root should display the blur effect. Without this tracking, all child frames get
| double-, triple-, etc- blurred.
|
private final BooleanProperty disabledRoot = new SimpleBooleanProperty(true);
private final String stylePrefix;
public final String getStylePrefix()
{
return stylePrefix;
}
@OnThread(Tag.FXPlatform)
public void lostFocus()
{
}
| Informs us Ctrl was held down when we were inserted via the keyboard.
| Just here so it can be overridden in child classes.
|
@OnThread(Tag.FXPlatform)
public void insertedWithCtrl()
{
}
| enum for keeping track of frame preview state
|
public static enum FramePreviewEnabled
{
PREVIEW_NONE, PREVIEW_ENABLED, PREVIEW_DISABLED;
}
| A frame can be enabled or disabled, which is tracked via frameEnabledProperty.
| But when the user hovers over enable or disable in the context menu, we also
| show a preview of the opposite state. This property keeps track of whether
| we are showing no preview (most common option), and thus display according to
| frameEnabledProperty, or whether we are showing a preview of the enabled or disabled
| state.
|
private final ObjectProperty<FramePreviewEnabled> framePreviewEnableProperty =
new SimpleObjectProperty<>(FramePreviewEnabled.PREVIEW_NONE);
| Tracks whether this frame is the source of a current drag operation. If so,
| it is displayed with a blur effect.
|
private final BooleanProperty frameDragSourceProperty = new SimpleBooleanProperty(false);
| A reference to the editor in which this frame lives. A frame object may never move editor;
| any operation that appears to do this actually creates a new copy of the frame.
|
private final InteractionManager editor;
| Keeps track of whether the frame is fresh. A fresh frame is one which has been inserted
| but the user has not yet left the frame since creation. Fresh frames are not checked
| for errors until they lose focus, to avoid red error underlines popping up as you
| enter new (and thus partially complete) code.
|
private final BooleanProperty fresh = new SimpleBooleanProperty(false);
| A list of all the *frame errors* which exist right now for this frame. A frame error
| is one that belongs to the frame, typically because there is no suitable slot to display
| it in instead. An example is if you put two return frames without a value. The second
| return frame should show an unreachable code error, but there's no sensible slot to show it
| on, so we attach it to the return frame. That goes in allFrameErrors. In contrast,
| a syntax error in an if condition is not a frame error; that gets attached to the
| expression slot for the condition.
|
| Only one frame error is shown at any given time; this is tracked in shownError.
|
private final ObservableList<CodeError> allFrameErrors = FXCollections.observableArrayList();
| ShownError is usually null, but if there are any *frame errors* (see allFrameErrors)
| then one of them is picked and shown as a red border around the frame. This property
| keeps track of which error was picked. shownError should be null if and only if allFrameErrors is empty.
|
private final ObjectProperty<CodeError> shownError = new SimpleObjectProperty<>(null);
| The parent canvas of this frame. It can be null, if the frame is not in a parent canvas.
| This canvas will change at most twice during the frame's lifetime:
| - Once, from null to a parent canvas when the frame gets inserted somewhere
| - Optionally, once more back to null when the frame is discarded.
| It should thus never move canvas in its lifetime (this is usually done by making a new
| copy), only be added and removed from one.
|
private FrameCanvas parentCanvas = null;
| Keep track of whether a frame has always been blank (or near-blank). Frames which have
| had no content inserted yet can be removed by pressing the escape key.
|
private boolean alwaysBeenBlank = true;
protected Map<String, BooleanProperty> modifiers = new HashMap<>();
| Creates a new frame.
|
| @param editor The editor that this frame belongs to
| @param caption The caption to use in the header label. If null, no header label is
| added to the frame
| @param stylePrefix The prefix to be added on to the CSS style class. If you pass, e.g.
| "if-", the frame will get the style classes "frame" and "if-frame".
|* May not be null.
*/
@OnThread(Tag.FX)
public Frame(final InteractionManager editor, String caption, String stylePrefix)
{
frameContents = new BetterVBox(200.0) {
|
|@OnThread(Tag.FX)
|
|@Override
|
|public double getBottomMarginFor(Node n)
|
|{
|
|return Frame.this.getBottomMarginFor(n);
|
|}
|
|@OnThread(Tag.FX)
|
|@Override
|
|public double getLeftMarginFor(Node n)
|
|{
|
|return Frame.this.getLeftMarginFor(n);
|
|}
|
|@OnThread(Tag.FX)
|
|@Override
|
|public double getRightMarginFor(Node n)
|
|{
|
|return Frame.this.getRightMarginFor(n);
|
|}
|
|};
|
|//Debug.time("&&&&&& Constructing frame");
if (stylePrefix == null)
throw new NullPointerException();
JavaFXUtil.addStyleClass(frameContents, "frame", stylePrefix + "frame");
this.stylePrefix = stylePrefix;
// When we are enabled/disabled, update our child slot states and trigger a compilation.
frameEnabledProperty.addListener((a, b, enabled) -> {
|
|getEditableSlotsDirect().forEach(e -> e.setEditable(enabled));
|
|editor.modifiedFrame(this, false);
|
|});
|
|//Debug.time("&&&&&& Binding effect");
// Here's the state diagram for when we are enabled/disabled, and the preview state, as to what
// effect we should show:
// ---------------------------------------------------
// | Actual | _PREVIEW_NONE | _DISABLED | _ENABLED |
|
|// ---------------------------------------------------
|
|// | Enabled | Enabled | Disabled | Enabled |
|
|// | Disabled | Disabled | Disabled | Enabled |
|
|// ---------------------------------------------------
|
|// Thus, our test for showing disabled effect is either PREVIEW_DISABLED, or
|
|// not enabled && FRAME_ENABLE_PREVIEW_NO_PREVIEW
|
|// I suspect this could be done better:
|
|frameContents.effectProperty().bind(
|
|new When(disabledRoot.and(frameEnabledProperty.not().and(framePreviewEnableProperty.isEqualTo(FramePreviewEnabled.PREVIEW_NONE)).or(framePreviewEnableProperty.isEqualTo(FramePreviewEnabled.PREVIEW_DISABLED))))
|
|.then(new When(frameDragSourceProperty )
|
|.then(FrameEffects.getDragSourceAndDisabledEffect())
|
|.otherwise(FrameEffects.getDisabledEffect()))
|
|.otherwise(new When(frameDragSourceProperty)
|
|.then(FrameEffects.getDragSourceEffect())
|
|.otherwise((Effect)null)));
|
|// We put some setup into the editor class because the setup requires a lot of access
|
|// to editor internals. Easier to pass the editor the frame than it is to expose all
|
|// the editor internals to the frame.
|
|this.editor = editor;
|
|if (editor != null) {
|
|editor.setupFrame(this);
|
|}
|
|//Debug.time("&&&&&& Making frame internals");
if (caption == null)
{
headerCaptionLabel = null;
}
else
{
headerCaptionLabel = new SlotLabel(caption, "caption", stylePrefix + "caption");
}
header = makeHeader(stylePrefix);
// Whenever the logical containers in contents change, we update the
// GUI elements in frameContents:
// Add listener before setAll call:
|
|contents.addListener((ListChangeListener<? super FrameContentItem>) c -> frameContents.getChildren().setAll(calculateContents(Utility.mapList(contents, FrameContentItem::getNode))));
|
|contents.setAll(header);
|
|// By default, the header row contains only the caption, if present:
|
|setHeaderRow();
|
|// In the case that someone tries to focus the frame directly, we pass the focus
|
|// on to the cursor after us. (Not sure if we need this any more?)
|
|getNode().focusedProperty().addListener( (observable, oldValue, newValue) -> {
|
|if (newValue) {
|
|getCursorAfter().requestFocus();
|
|}
|
|});
|
|// Whenever a *frame error* (not slot error) is added, we recalculate whether,
|
|// and which error to show.
|
|allFrameErrors.addListener((ListChangeListener<CodeError>)c -> {
|
|shownError.set(allFrameErrors.stream().min(CodeError::compareErrors).orElse(null));
|
|FXRunnable update = () -> JavaFXUtil.setPseudoclass("bj-frame-error", shownError.get() != null, frameContents);
if (isFresh())
// This may queue up a few of them, but it doesn't really matter:
onNonFresh(update);
else{ update.run();
}
|
|});
|
|//Debug.time("&&&&&& Constructed frame");
}
/**
* Initialise things which must be done on the actual FX thread, not just a loader thread.
* Will be overridden by subclasses.
*/
@OnThread(Tag.FXPlatform)
protected void initialiseFX()
|
|{
|
|}
|
|/**
| Helper method to take an image (screenshot) of the given list of frames,
| optionally with the given colour of border around the image.
|
public static Image takeShot(List<Frame> frames, Color border
| none if null
|
{
int totalHeight = 0;
int maxWidth = 0;
for (Frame f : frames) {
Bounds b = f.getNode().getBoundsInParent();
totalHeight += (int)Math.ceil(b.getHeight()) + FrameCursor.HIDE_HEIGHT;
maxWidth = Math.max(maxWidth, (int)Math.ceil(b.getWidth()));
}
totalHeight -= FrameCursor.HIDE_HEIGHT;
WritableImage collated;
int xOffset, yOffset;
if (border != null) {
collated = new WritableImage(maxWidth + 2, totalHeight + 2);
for (int x = 0; x < maxWidth + 2; x++) {
collated.getPixelWriter().setColor(x, 0, border);
collated.getPixelWriter().setColor(x, totalHeight + 1, border);
}
for (int y = 0; y < totalHeight + 2; y++) {
collated.getPixelWriter().setColor(0, y, border);
collated.getPixelWriter().setColor(maxWidth + 1, y, border);
}
xOffset = yOffset = 1;
}
else {
if (maxWidth * totalHeight < 1) {
return null;
}
collated = new WritableImage(maxWidth, totalHeight);
xOffset = yOffset = 0;
}
int y = 0;
for (Frame f : frames) {
Bounds b = f.getNode().getBoundsInParent();
WritableImage image = new WritableImage((int)Math.ceil(b.getWidth()), (int)Math.ceil(b.getHeight()));
SnapshotParameters p = new SnapshotParameters();
p.setFill(Color.TRANSPARENT);
boolean dr = f.disabledRoot.get();
f.disabledRoot.set(true);
JavaFXUtil.setPseudoclass("bj-hide-caret", true, f.getNode());
f.getNode().snapshot(p, image);
f.disabledRoot.set(dr);
JavaFXUtil.setPseudoclass("bj-hide-caret", false, f.getNode());
JavaFXUtil.blitImage(collated, xOffset, yOffset + y, image);
y += (int)Math.ceil(b.getHeight()) + FrameCursor.HIDE_HEIGHT;
}
return collated;
}
| Gets all RecallableFocus items within the frame, to unlimited depth.
| Used for undo; see RecallableFocus.
| @return A stream of all contained RecallableFocus items
|
protected Stream getFocusablesInclContained()
{
return Utility.concat(getEditableSlotsDirect(), getPersistentCanvases().flatMap(c -> c.getFocusableCursors().stream()),
getPersistentCanvases().flatMap(c -> c.getBlockContents().stream()).flatMap(Frame::getFocusablesInclContained));
}
protected double getRightMarginFor(Node n)
{
return n == header.getNode() ? 1.0 : 0.0;
}
protected double getLeftMarginFor(Node n)
{
return n == header.getNode() ? 1.0 : 0.0;
}
protected double getBottomMarginFor(Node n)
{
return 0.0;
}
| Makes the header row, using the given style prefix.
| Drawn out into its own method so that it can be overridden and customised in subclasses.
|
protected FrameContentRow makeHeader(String stylePrefix)
{
return new FrameContentRow(this, stylePrefix);
}
| Given the normal content for this frame, gives back a list of altered content.
| This is overridden by subclasses, for example, to add sidebar displays.
|
| Can be overridden in subclasses to add more content. By default just returns its
| list of nodes.
|
protected List extends Node> calculateContents(List<Node> normalContent)
{
return normalContent;
}
| Gets a list of available context operations for this frame.
|
| This is called when the context menu is about to be shown, so you can dynamically decide
| which operations to include based on the current state at the time of this call, rather
| than trying to do complex bindings to update the operations in future.
|
| By default this is cut, copy, disable/enable, and delete operations. Override this method in sub-classes
| if you want different behaviour (e.g. if the frame doesn't support cut or delete).
|
| Overridden by subclasses.
|
@OnThread(Tag.FXPlatform)
public List getContextOperations()
{
List<FrameOperation> ops = new ArrayList<FrameOperation>();
ops.add(new CutFrameOperation(editor));
ops.add(new CopyFrameAsStrideOperation(editor));
ops.add(new CopyFrameAsImageOperation(editor));
ops.add(new CopyFrameAsJavaOperation(editor));
ops.add(isFrameEnabled() ? new DisableFrameOperation(editor) : new EnableFrameOperation(editor));
ops.add(new DeleteFrameOperation(editor));
return ops;
}
| Gets the first cursor inside this frame (or null if none)
|
public final FrameCursor getFirstInternalCursor()
{
return getCanvases().map(FrameCanvas::getFirstCursor).findFirst().orElse(null);
}
| Gets the last cursor inside this frame (or null if none)
|
public final FrameCursor getLastInternalCursor()
{
return Utility.findLast(getCanvases().map(FrameCanvas::getLastCursor)).orElse(null);
}
| Gets the parent canvas of this frame (may be null)
|
public FrameCanvas getParentCanvas()
{
return parentCanvas;
}
| Sets the parent canvas of this frame. Should only ever change
| the parent once from null to non-null, and optionally once
| back to null.
|
public void setParentCanvas(FrameCanvas parentCanvas)
{
FrameCanvas oldCanvas = this.parentCanvas;
this.parentCanvas = parentCanvas;
if (oldCanvas != null)
oldCanvas.getBlockContents().forEach(f -> f.updateAppearance(f.getParentCanvas()));
if (parentCanvas != null)
{
parentCanvas.getBlockContents().forEach(f -> f.updateAppearance(f.getParentCanvas()));
Utility.iterableStream(getAllFrames()).forEach(f -> f.updateAppearance(f.getParentCanvas()));
}
}
| Gets the cursor after this frame in the parent canvas. Returns null if not in a parent canvas.
|
public final FrameCursor getCursorAfter()
{
return parentCanvas == null ? null : parentCanvas.getCursorAfter(this);
}
| Gets the cursor before this frame in the parent canvas. Returns null if not in a parent canvas.
|
public final FrameCursor getCursorBefore()
{
return parentCanvas == null ? null : parentCanvas.getCursorBefore(this);
}
| Adds the given CSS style class to this frame
|
protected final void addStyleClass(String styleClass)
{
JavaFXUtil.addStyleClass(frameContents, styleClass);
}
| Remove the given CSS style class to this frame. (Eventually we should remove this in favour of using pseudo-classes).
|
protected final void removeStyleClass(String styleClass)
{
JavaFXUtil.removeStyleClass(frameContents, styleClass);
}
| This is primarily intended for use in adding the item to its parent container
| and adding handlers. It should not be used to circumvent this interface to the display properties.
|
public final Node getNode()
{
return frameContents;
}
| This is primarily intended for use by sub-classes which need to access properties
| of the graphics item (e.g. layout properties)
|
protected final Region getRegion()
{
return frameContents;
}
| Method which can be over-ridden if the block is styled differently depending on its parent
|
public void updateAppearance(FrameCanvas parentCanvas)
{
disabledRoot.set(canHaveEnabledState(true));
}
| Sets the drag source effect (i.e. notifies us if we are the source of a drag)
|
public void setDragSourceEffect(boolean on)
{
frameDragSourceProperty.set(on);
}
| Returns whether the frame is currently enabled.
|
public boolean isFrameEnabled()
{
return frameEnabledProperty.get();
}
| Sets the frame enabled state, if possible. For example, you cannot enable
| the child of a disabled frame.
|
public void setFrameEnabled(boolean enabled)
{
if (!canHaveEnabledState(enabled))
return;
frameEnabledProperty.set(enabled);
if (this instanceof CodeFrame)
((CodeFrame<?>)this).setElementEnabled(enabled);
if (!enabled)
{
JavaFXUtil.runNowOrLater(() -> {
flagErrorsAsOld();
removeOldErrors();
});
}
getCanvases().forEach(canvas -> canvas.getBlocksSubtype(Frame.class).forEach(b -> b.setFrameEnabled(enabled)));
updateAppearance(getParentCanvas());
}
| Checks if a frame can have the given enabled state. This boils down to checking,
| when given true, that all parents of this frame are enabled.
|
public boolean canHaveEnabledState(boolean enabled)
{
if (enabled)
{
FrameCanvas canvas = getParentCanvas();
if (canvas != null)
{
Frame parent = canvas.getParent().getFrame();
if (parent != null)
{
return parent.isFrameEnabled() && parent.canHaveEnabledState(enabled);
}
}
}
return true;
}
| Sets the frame enable state.
|
public void setFrameEnablePreview(FramePreviewEnabled state)
{
framePreviewEnableProperty.set(state);
}
| Flags all errors as old: frame errors and slot errors, to unlimited depth
|
@OnThread(Tag.FXPlatform)
public final void flagErrorsAsOld()
{
allFrameErrors.forEach(CodeError::flagAsOld);
getEditableSlotsDirect().forEach(EditableSlot::flagErrorsAsOld);
getPossiblyHiddenSlotsDirect().forEach(EditableSlot::flagErrorsAsOld);
getCanvases().forEach(FrameHelper::flagErrorsAsOld);
}
| Removes all errors flagged as old: frame errors and slot errors, to unlimited depth
|
@OnThread(Tag.FXPlatform)
public final void removeOldErrors()
{
allFrameErrors.removeIf(CodeError::isFlaggedAsOld);
getEditableSlotsDirect().forEach(EditableSlot::removeOldErrors);
getPossiblyHiddenSlotsDirect().forEach(EditableSlot::removeOldErrors);
getCanvases().forEach(FrameHelper::removeOldErrors);
}
| Public method called while frame is being removed. Should remove any overlays, listeners, etc.
|
| This version is final, to make sure all sub-items are cleaned up. To provide cleanup specific
| to this kind of frame, override the cleanupFrame method.
|
public final void cleanup()
{
getEditableSlots().forEach(EditableSlot::cleanup);
getCanvases().forEach(FrameCanvas::cleanup);
cleanupFrame();
}
| Override this method to provide cleanup specific to that type of frame
|
protected void cleanupFrame()
{
}
| Gets a stream of all directly contained canvases.
|
public final Stream getCanvases()
{
return contents.stream().map(FrameContentItem::getCanvas).flatMap(Utility::streamOptional);
}
| Get only those canvases which are persistent (i.e. exclude generated items like inherited method canvas)
|
public Stream getPersistentCanvases()
{
return getCanvases();
}
public boolean isCollapsible()
{
return false;
}
public void setCollapsed(boolean collapse)
{
}
| When a frame loses focus, sometimes it checks for certain empty slots and removes them,
| e.g. the throws declaration at the end of a method header.
|
public void checkForEmptySlot()
{
}
| By default, no extensions: override to specify.
|
| @param innerCanvas The inner canvas which we are asking about extensions for.
| If we are not asking for inner extensions (e.g. instead before, or after)
| then will be null.
| @param cursorInCanvas The cursor position in the inner canvas. Will be non-null iff innerCanvas is non-null
|
public List getAvailableExtensions(FrameCanvas innerCanvas, FrameCursor cursorInCanvas)
{
if (innerCanvas != null)
return Collections.emptyList();
return Arrays.asList(new ExtensionDescription('\\', "Disable/Enable frames", () -> {
if (canHaveEnabledState(isFrameEnabled()))
{
setFrameEnabled(!isFrameEnabled());
}
}, false, ExtensionSource.BEFORE, ExtensionSource.AFTER));
}
@OnThread(Tag.FXPlatform)
public final boolean notifyKeyAfter(char c, RecallableFocus rc)
{
return notifyKey(c, rc, getAvailableExtensions(null, null), ExtensionSource.AFTER);
}
@OnThread(Tag.FXPlatform)
public final boolean notifyKeyBefore(char c, RecallableFocus rc)
{
return notifyKey(c, rc, getAvailableExtensions(null, null), ExtensionSource.BEFORE);
}
@OnThread(Tag.FXPlatform)
private final boolean notifyKey(char c, RecallableFocus rc, List<ExtensionDescription> extensions, ExtensionSource src)
{
List<ExtensionDescription> candidates = extensions.stream()
.filter(e -> e.getShortcutKey() == c && e.validFor(src))
.collect(Collectors.toList());
if (candidates.size() == 0) {
return false;
}
if (candidates.size() > 1) {
throw new IllegalStateException("Ambiguous " + src + " for: " + (int)c);
}
editor.beginRecordingState(rc);
candidates.get(0).activate();
editor.endRecordingState(rc);
return true;
}
| Checks whether this frame can be dragged. True by default. Some frames cannot
| be dragged, e.g. inherited frames or class frames.
|
public boolean canDrag()
{
return true;
}
| Notifies about a left-click. Returns true if we have consumed the click, false otherwise.
|
@OnThread(Tag.FXPlatform)
public final boolean leftClicked(double sceneX, double sceneY, boolean shiftDown)
{
editor.clickNearestCursor(sceneX, sceneY, shiftDown);
return true;
}
| Called automatically when first created and added to its parent; shows where to focus input after initial key-press
|
| Returns true if it successfully focused on something; false if there is nothing in the frame to focus on
|
public boolean focusWhenJustAdded()
{
EditableSlot s = getEditableSlotsDirect().findFirst().orElse(null);
if (s != null) {
s.requestFocus();
return true;
}
return false;
}
protected void addTopRight(Node n)
{
}
| Gets the header row (for use in subclasses)
| @return
|
protected final FrameContentRow getHeaderRow()
{
return header;
}
| Automatically prepends the caption to the given items, if there is one
|
protected final void setHeaderRow(HeaderItem... headerItems)
{
if (headerCaptionLabel == null)
header.setHeaderItems(Arrays.asList(headerItems));
else
{
ArrayList<HeaderItem> items = new ArrayList<>();
items.add(headerCaptionLabel);
items.addAll(Arrays.asList(headerItems));
header.setHeaderItems(items);
}
}
| Called when this frame should become focused, e.g. because the user has navigated to it
| using keys by pressing up from below this frame.
| @param up true if used up to get here, false if used right
| @return true if this frame can be focused and has been, false otherwise (e.g. frame is disabled, or cannot receive focus because it has no slots or content)
|
public final boolean focusFrameEnd(boolean up)
{
if (isFrameEnabled()) {
FrameContentItem last = contents.get(contents.size() - 1);
return up ? last.focusBottomEndFromNext() : last.focusRightEndFromNext();
}
return false;
}
| Called when this frame should become focused, e.g. because the user has navigated to it
| using keys by pressing right from above this frame.
|
public final boolean focusFrameStart()
{
if (isFrameEnabled()) {
FrameContentItem first = contents.get(0);
return first.focusLeftEndFromPrev();
}
return false;
}
| Accessor for the editor that this frame lives in (never changes for a given frame)
|
public InteractionManager getEditor()
{
return editor;
}
| Called when you have clicked on the frame in a stack trace or want to jump to definition.
|
public void show(ShowReason reason)
{
switch (reason) {
case EXCEPTION:
JavaFXUtil.setPseudoclass("bj-stack-highlight", true, getNode());
getParentCanvas().getCursorBefore(this).requestFocus();
editor.scrollTo(getNode(), -50.0);
editor.registerStackHighlight(this);
break;
case LINK_TARGET:
editor.scrollTo(getNode(), -50.0);
focusName();
break;
}
}
| Focuses the name of the frame, or something close to that concept.
|
public void focusName()
{
focusWhenJustAdded();
}
| Removes the highlight which was added when showing this frame as a stack trace item
|
public void removeStackHighlight()
{
JavaFXUtil.setPseudoclass("bj-stack-highlight", false, getNode());
}
| Gets all slots, from this frame and all frames contained in canvases, to unlimited depth
|
public final Stream getHeaderItems()
{
return contents.stream().flatMap(FrameContentItem::getHeaderItemsDeep);
}
| Gets all editable slots, from this frame and all frames contained in canvases, to unlimited depth
|
public final Stream getEditableSlots()
{
return getHeaderItems().map(HeaderItem::asEditable).filter(x -> x != null);
}
| Gets only those editable slots which are directly in this frame (not any in frames inside any canvases)
| @return
|
public final Stream getEditableSlotsDirect()
{
return contents.stream().flatMap(FrameContentItem::getHeaderItemsDirect).map(HeaderItem::asEditable).filter(x -> x != null);
}
| Gets only editable slots which are directly in this frame (not any in frames inside any canvases)
| but which may be currently hidden (e.g. type name in class-extends when
| focus is lost, or return value, etc)
|
public Stream getPossiblyHiddenSlotsDirect()
{
return Stream.empty();
}
@Override
public void focusUp(FrameContentItem src, boolean toEnd)
{
int index = contents.indexOf(src);
if (index == -1)
throw new IllegalStateException("Item not contained in frame");
if (index == 0)
focusPrevTarget();
else
if (!contents.get(index - 1).focusBottomEndFromNext())
focusUp(contents.get(index - 1), toEnd);
}
@Override
public void focusDown(FrameContentItem src)
{
int index = contents.indexOf(src);
if (index == -1)
throw new IllegalStateException("Item not contained in frame");
if (index == contents.size() - 1)
{
if (getCursorAfter() != null)
getCursorAfter().requestFocus();
}
else
if (!contents.get(index + 1).focusTopEndFromPrev())
focusDown(contents.get(index + 1));
}
@Override
public void focusEnter(FrameContentItem src)
{
focusDown(src);
}
@Override
public void focusLeft(FrameContentItem src)
{
int index = contents.indexOf(src);
if (index == -1)
throw new IllegalStateException("Item not contained in frame");
if (index == 0)
focusPrevTarget();
else
if (!contents.get(index - 1).focusRightEndFromNext())
focusLeft(contents.get(index - 1));
}
@Override
public void focusRight(FrameContentItem src)
{
int index = contents.indexOf(src);
if (index == -1)
throw new IllegalStateException("Item not contained in frame");
if (index == contents.size() - 1)
{
if (getCursorAfter() != null)
getCursorAfter().requestFocus();
}
else
if (!contents.get(index + 1).focusLeftEndFromPrev())
focusRight(contents.get(index + 1));
}
protected void focusPrevTarget()
{
FrameCursor cursorBefore = getCursorBefore();
if (cursorBefore != null) {
cursorBefore.requestFocus();
}
}
| Called when the user has pressed backspace at the start of a header item inside a frame content item.
| @param srcRow The row in the frame where backspace was pressed
| @param src The item in which backspace was pressed
| @return True if we deleted ourselves in response, otherwise false
|
@OnThread(Tag.FXPlatform)
public boolean backspaceAtStart(FrameContentItem srcRow, HeaderItem src)
{
if (contents.size() > 0 && (src == contents.get(0) || srcRow == contents.get(0)))
{
if (isAlmostBlank())
{
FrameCanvas parentCanvas = getParentCanvas();
FrameCursor cursorBefore = getCursorBefore();
parentCanvas.removeBlock(this);
cursorBefore.requestFocus();
return true;
}
}
return false;
}
| Called when the user has pressed delete at the end of a header item inside a frame content item.
| @param srcRow The row in the frame where delete was pressed
| @param src The item in which delete was pressed
| @return True if we deleted ourselves in response, otherwise false
|
@OnThread(Tag.FXPlatform)
public boolean deleteAtEnd(FrameContentItem srcRow, HeaderItem src)
{
return false;
}
| Pulls up a frames contents. For example, where you delete an if but want to leave the content,
| we call this method to pull up the content of each subcanvas to replace the if.
| By default, we pull up each frame canvas's contents, with a blank frame between each.
|
| This method does not delete ourselves, just does the pull-up.
|
public void pullUpContents()
{
editor.modifiedFrame(this, false);
getCursorBefore().insertFramesAfter(Utility.<Frame>concat(getCanvases().<List<Frame>>map(canvas -> {
List<Frame> contents = new ArrayList<>(canvas.getBlockContents());
contents.forEach(c -> canvas.removeBlock(c));
return contents;
}).collect(Utility.<List<Frame>>intersperse(() -> Arrays.<Frame>asList(new BlankFrame(editor)))).toArray(new List[0])));
}
| Gets all frames contained within, to unlimited depth
|
public final Stream getAllFrames()
{
return Stream.concat(Stream.of(this), getCanvases().flatMap(c -> c.getBlocksSubtype(Frame.class).stream()).flatMap(Frame::getAllFrames));
}
| Sets this frame to be fresh
|
@OnThread(Tag.FXPlatform)
public void markFresh()
{
fresh.set(true);
}
| Sets this frame to be non-fresh
|
@OnThread(Tag.FXPlatform)
public void markNonFresh()
{
if (!isShowingSuggestions())
fresh.set(false);
}
@OnThread(Tag.FXPlatform)
protected boolean isShowingSuggestions()
{
return getEditableSlots().anyMatch(s -> s instanceof StructuredSlot &&
((StructuredSlot<?,?,?>)s).isShowingSuggestions());
}
| Checks wheter this frame is fresh
|
public boolean isFresh()
{
return fresh.get();
}
| Only valid when called on a fresh frame. In the future, if/when this frame becomes
| non-fresh, the given action will be executed.
|
public void onNonFresh(FXRunnable action)
{
if (!isFresh()) {
throw new IllegalStateException("Calling onNonFresh when we are already non-fresh; state cannot go back to fresh");
}
JavaFXUtil.addSelfRemovingListener(fresh, b -> action.run());
}
| Read-only interface to the fresh property.
|
public ObservableBooleanValue freshProperty()
{
return fresh;
}
| Gets a list of variables declared within this frame, that are in scope for
| frames after this one in the same canvas
|
public List getDeclaredVariablesAfter()
{
return Collections.emptyList();
}
| Gets a list of variables declared within this frame, that are in scope for
| the given canvas
|
public List getDeclaredVariablesWithin(FrameCanvas c)
{
return Collections.emptyList();
}
| Sets the given view.
| @param oldView the view transferring from
| @param newView the new view selected
| @param animation A class to hook into to synchronise animations
|
@OnThread(Tag.FXPlatform)
public void setView(View oldView, View newView, SharedTransition animation)
{
setViewNoOverride(oldView, newView, animation);
}
protected final void setViewNoOverride(View oldView, View newView, SharedTransition animation)
{
final Background start = frameContents.getBackground();
final Border startBorder = frameContents.getBorder();
JavaFXUtil.setPseudoclass("bj-java-preview", newView == View.JAVA_PREVIEW, frameContents);
frameContents.applyCss();
final Background end = frameContents.getBackground();
final Border endBorder = frameContents.getBorder();
boolean animatingBackground = false;
if (start != null && end != null && start.getImages().isEmpty() && end.getImages().isEmpty() && start.getFills().size() == 1 && end.getFills().size() == 1)
{
final BackgroundFill startFill = start.getFills().get(0);
final BackgroundFill endFill = end.getFills().get(0);
if (startFill.getFill() instanceof Color && endFill.getFill() instanceof Color && !(startFill.getFill().equals(endFill.getFill())))
{
final Color startColor = (Color) startFill.getFill();
final Color endColor = (Color) endFill.getFill();
animatingBackground = true;
JavaFXUtil.addChangeListener(animation.getProgress(), t -> {
Color c = startColor.interpolate(endColor, t.doubleValue());
frameContents.setBackground(new Background(new BackgroundFill(c, endFill.getRadii(), endFill.getInsets())));
});
}
}
boolean animatingBorder = false;
if (startBorder != null && endBorder != null && startBorder.getImages().isEmpty() && endBorder.getImages().isEmpty() && startBorder.getStrokes().size() == 1 && endBorder.getStrokes().size() == 1)
{
final BorderStroke startStroke = startBorder.getStrokes().get(0);
final BorderStroke endStroke = endBorder.getStrokes().get(0);
if (startStroke.isStrokeUniform() && endStroke.isStrokeUniform())
{
final Paint startStrokePaint = startStroke.getTopStroke();
final Paint endStrokePaint = endStroke.getTopStroke();
if (startStrokePaint instanceof Color && endStrokePaint instanceof Color && !startStrokePaint.equals(endStrokePaint))
{
final Color startColor = (Color)startStrokePaint;
final Color endColor = (Color)endStrokePaint;
animatingBorder = true;
JavaFXUtil.addChangeListener(animation.getProgress(), t -> {
Color c = startColor.interpolate(endColor, t.doubleValue());
frameContents.setBorder(new Border(new BorderStroke(c, startStroke.getTopStyle(), startStroke.getRadii(), startStroke.getWidths())));
});
}
}
}
if (animatingBackground || animatingBorder)
animation.addOnStopped(() -> frameContents.applyCss());
if (!isFrameEnabled())
{
if (newView == View.JAVA_PREVIEW)
{
frameContents.opacityProperty().bind(animation.getOppositeProgress());
animation.addOnStopped(() -> {
frameContents.opacityProperty().unbind();
frameContents.setVisible(false);
});
}
else
{
frameContents.setVisible(true);
frameContents.opacityProperty().bind(animation.getProgress());
animation.addOnStopped(() -> frameContents.opacityProperty().unbind());
}
}
contents.forEach(i -> i.setView(oldView, newView, animation));
}
| Gets the frame error, if any, which is currently being shown.
|
public Stream getCurrentErrors()
{
return shownError.get() == null ? Stream.empty() : Stream.of(shownError.get());
}
| Adds the given frame error to this frame. Does not necessarily show it (depends
| on which other errors are present on this frame)
|
@OnThread(Tag.FXPlatform)
public void addError(CodeError err)
{
allFrameErrors.add(err);
err.bindFresh(fresh, editor);
}
@Override
public void focusAndPositionAtError(CodeError err)
{
getCursorBefore().requestFocus();
}
@Override
public Node getRelevantNodeForError(CodeError err)
{
if (getCursorBefore() == null)
return null;
else{ return getCursorBefore().getNode();
}
}
| Callback to be called once the frame is compiled. Put here so subclasses can
| override it.
|
@OnThread(Tag.FXPlatform)
public void compiled()
{
}
public EditableSlot getErrorShowRedirect()
{
return getEditableSlotsDirect().findFirst().orElse(null);
}
| Returns true if the frame is about as blank as when it was first created. This typically means that
| all the slots are near blank, and the canvases only have blank frames, if any
|
public boolean isAlmostBlank()
{
return getEditableSlotsDirect().allMatch(EditableSlot::isAlmostBlank) && getCanvases().allMatch(FrameCanvas::isAlmostBlank);
}
public void trackBlank()
{
alwaysBeenBlank = alwaysBeenBlank && isAlmostBlank();
}
| Called when escape has been pressed within the frame.
| @param srcRow The row in which escape was pressed
| @param src The item in that row in which escape was pressed
|
@OnThread(Tag.FXPlatform)
public void escape(FrameContentItem srcRow, HeaderItem src)
{
if (alwaysBeenBlank && isFresh())
{
FrameCanvas parentCanvas = getParentCanvas();
FrameCursor cursorBefore = getCursorBefore();
editor.recordEdits(StrideEditReason.FLUSH);
parentCanvas.removeBlock(this);
editor.recordEdits(StrideEditReason.ESCAPE_FRESH);
cursorBefore.requestFocus();
}
}
protected Pane getSidebarContainer()
{
return frameContents;
}
public FrameCursor findCursor(double sceneX, double sceneY, FrameCursor prevCursor, FrameCursor nextCursor, List<Frame> exclude, boolean isDrag, boolean canDescend)
{
List<FrameCanvas> canvases = getCanvases().filter(canvas -> canvas.getShowingProperty().get()).collect(Collectors.toList());
final FrameCanvas lastCanvas = canvases.get(canvases.size() - 1);
double sceneMaxY = lastCanvas.getSceneBounds().getMaxY();
Bounds headerRowBounds = getHeaderRow().getSceneBounds();
if (prevCursor != null && sceneY <= (headerRowBounds.getMinY() + headerRowBounds.getMaxY()) / 2) {
return prevCursor;
}
for (int canvasIndex = 0; canvasIndex < canvases.size(); canvasIndex++)
{
Bounds canvasBounds = canvases.get(canvasIndex).getContentSceneBounds();
double nextY;
if (canvasIndex == canvases.size() - 1) {
nextY = sceneMaxY;
}
else {
nextY = canvases.get(canvasIndex + 1).getSceneBounds().getMinY();
}
if (sceneY <= (canvasBounds.getMaxY() + nextY) / 2)
{
boolean isClickOnMargin = sceneX >= headerRowBounds.getMinX() && sceneX < canvasBounds.getMinX()
&& !isDrag;
final FrameCursor cursor = canvases.get(canvasIndex).findClosestCursor(sceneX, sceneY, exclude, isDrag, canDescend && !isClickOnMargin);
if (cursor != null)
return cursor;
}
}
if (nextCursor != null) {
return nextCursor;
}
else {
if (prevCursor != null) {
return prevCursor;
}
return lastCanvas.findClosestCursor(sceneX, sceneY, exclude, isDrag, canDescend);
}
}
| Gets the lowest scene Y coordinate inside this block, for the purposes of picking a cursor
|
public double lowestCursorY()
{
FrameCanvas lastCanvas = Utility.findLast(getCanvases()).orElse(null);
return lastCanvas.getSceneBounds().getMaxY();
}
| Try to restore this frame's contents to match the given CodeElement. In practice,
| this will usually succeed if and only if CodeElement is the same type of frame as this one.
| @return True if successfully restored, false if not
|
public boolean tryRestoreTo(CodeElement codeElement)
{
return false;
}
| Returns true for all frames with effective code. I.e only false in frames such as BlankFrame and CommentFrame.
|
| @return True except for non-effective frames (codewise)
|
public boolean isEffectiveFrame()
{
return true;
}
public void setModifier(String name, boolean value)
{
if (modifiers.containsKey(name)) {
modifiers.get(name).set(value);
}
else {
Debug.reportError("No such modifier: " + name + " in Frame: " + this);
}
}
public BooleanProperty getModifier(String name)
{
return modifiers.get(name);
}
public static enum ShowReason { EXCEPTION, LINK_TARGET
}
| Different view modes in Stride editor.
|
public enum View {
NORMAL("normal"),
JAVA_PREVIEW("java_preview"),
BIRDSEYE_NODOC("birdseye_nodoc"),
BIRDSEYE_DOC("birdseye_doc");
private final String text;
View(String text)
{
this.text = text;
}
public String getText()
{
return text;
}
public boolean isBirdseye()
{
return this == BIRDSEYE_DOC || this == BIRDSEYE_NODOC;
}
}
| Tracks the cause of a view mode change, i.e. what user interaction actually
| caused the change. A mouse click, a shortcut key, etc.
|
public enum ViewChangeReason
{
MOUSE_CLICKED("mouse_clicked"),
KEY_PRESSED_ENTER("key_pressed_enter"),
KEY_PRESSED_ESCAPE("key_pressed_escape"),
MENU_OR_SHORTCUT("menu_or_shortcut");
private final String text;
ViewChangeReason(String text)
{
this.text = text;
}
public String getText()
{
return text;
}
}
protected static Node getHeaderNodeOf(Frame f)
{
return f.header.getNode();
}
| When we are deciding whether to display a message about a frame deletion that the user may
| want to undo, we only want to show the message for "large" frames. So we don't show it
|* for say a single assignment frame, or perhaps even an if frame with a single method call,
* but we do show it for a method or other container with several inner frames. To measure
| this, we use the idea of "effort": how many keystrokes (roughly) it would take to recreate
|* a particular piece of code.
*
* This method performs the calculation of effort. See comments inside for how it works
*
* @return The effort required to (re-)create this frame and all inner frames.
public int calculateEffort()
{
int effort = 1;
effort += getEditableSlotsDirect().mapToInt(EditableSlot::calculateEffort).sum();
effort += getAllFrames().filter(f -> f != this).mapToInt(Frame::calculateEffort).sum();
return effort;
}
| Called when the editor font size has changed to redraw any overlays
|
@OnThread(Tag.FXPlatform)
public void fontSizeChanged()
{
header.fontSizeChanged();
}
}
. getStylePrefix
. lostFocus
. insertedWithCtrl
. takeShot
. getFocusablesInclContained
. getRightMarginFor
. getLeftMarginFor
. getBottomMarginFor
. makeHeader
. calculateContents
. getContextOperations
. getFirstInternalCursor
. getLastInternalCursor
. getParentCanvas
. setParentCanvas
. getCursorAfter
. getCursorBefore
. addStyleClass
. removeStyleClass
. getNode
. getRegion
. updateAppearance
. setDragSourceEffect
. isFrameEnabled
. setFrameEnabled
. canHaveEnabledState
. setFrameEnablePreview
. flagErrorsAsOld
. removeOldErrors
. cleanup
. cleanupFrame
. getCanvases
. getPersistentCanvases
. isCollapsible
. setCollapsed
. checkForEmptySlot
. getAvailableExtensions
. notifyKeyAfter
. notifyKeyBefore
. notifyKey
. canDrag
. leftClicked
. focusWhenJustAdded
. addTopRight
. getHeaderRow
. setHeaderRow
. focusFrameEnd
. focusFrameStart
. getEditor
. show
. focusName
. removeStackHighlight
. getHeaderItems
. getEditableSlots
. getEditableSlotsDirect
. getPossiblyHiddenSlotsDirect
. focusUp
. focusDown
. focusEnter
. focusLeft
. focusRight
. focusPrevTarget
. backspaceAtStart
. deleteAtEnd
. pullUpContents
. getAllFrames
. markFresh
. markNonFresh
. isShowingSuggestions
. isFresh
. onNonFresh
. freshProperty
. getDeclaredVariablesAfter
. getDeclaredVariablesWithin
. setView
. setViewNoOverride
. getCurrentErrors
. addError
. focusAndPositionAtError
. getRelevantNodeForError
. compiled
. getErrorShowRedirect
. isAlmostBlank
. trackBlank
. escape
. getSidebarContainer
. findCursor
. lowestCursorY
. tryRestoreTo
. isEffectiveFrame
. setModifier
. getModifier
. getText
. isBirdseye
. getText
. getHeaderNodeOf
. calculateEffort
. fontSizeChanged
1469 neLoCode
+ 262 LoComm