package bluej.editor.stride;
import bluej.Config;
import bluej.collect.StrideEditReason;
import bluej.compiler.CompileReason;
import bluej.compiler.CompileType;
import bluej.debugger.DebuggerThread;
import bluej.editor.stride.ErrorOverviewBar.ErrorInfo;
import bluej.editor.stride.ErrorOverviewBar.ErrorState;
import bluej.editor.stride.FXTabbedEditor.CodeCompletionState;
import bluej.editor.stride.FrameCatalogue.Hint;
import bluej.parser.AssistContent;
import bluej.parser.AssistContent.CompletionKind;
import bluej.parser.AssistContent.ParamInfo;
import bluej.parser.ConstructorCompletion;
import bluej.parser.PrimitiveTypeCompletion;
import bluej.parser.entity.EntityResolver;
import bluej.parser.entity.PackageOrClass;
import bluej.pkgmgr.Project;
import bluej.pkgmgr.target.ClassTarget;
import bluej.pkgmgr.target.Target;
import bluej.prefmgr.PrefMgr;
import bluej.stride.framedjava.ast.ASTUtility;
import bluej.stride.framedjava.ast.HighlightedBreakpoint;
import bluej.stride.framedjava.ast.JavaFragment.PosInSourceDoc;
import bluej.stride.framedjava.ast.SlotFragment;
import bluej.stride.framedjava.ast.links.PossibleKnownMethodLink;
import bluej.stride.framedjava.ast.links.PossibleLink;
import bluej.stride.framedjava.ast.links.PossibleMethodUseLink;
import bluej.stride.framedjava.ast.links.PossibleTypeLink;
import bluej.stride.framedjava.ast.links.PossibleVarLink;
import bluej.stride.framedjava.elements.CallElement;
import bluej.stride.framedjava.elements.ClassElement;
import bluej.stride.framedjava.elements.CodeElement;
import bluej.stride.framedjava.elements.LocatableElement.LocationMap;
import bluej.stride.framedjava.elements.MethodWithBodyElement;
import bluej.stride.framedjava.elements.NormalMethodElement;
import bluej.stride.framedjava.elements.TopLevelCodeElement;
import bluej.stride.framedjava.errors.CodeError;
import bluej.stride.framedjava.errors.ErrorAndFixDisplay;
import bluej.stride.framedjava.frames.*;
import bluej.stride.framedjava.slots.ExpressionSlot;
import bluej.stride.generic.*;
import bluej.stride.generic.Frame.ShowReason;
import bluej.stride.generic.Frame.View;
import bluej.stride.operations.UndoRedoManager;
import bluej.stride.slots.EditableSlot;
import bluej.stride.slots.LinkedIdentifier;
import bluej.stride.slots.SuggestionList;
import bluej.stride.slots.SuggestionList.SuggestionListParent;
import bluej.stride.slots.SuggestionList.SuggestionShown;
import bluej.utility.BackgroundConsumer;
import bluej.utility.Debug;
import bluej.utility.Utility;
import bluej.utility.javafx.CircleCountdown;
import bluej.utility.javafx.FXPlatformConsumer;
import bluej.utility.javafx.FXPlatformRunnable;
import bluej.utility.javafx.FXRunnable;
import bluej.utility.javafx.FXSupplier;
import bluej.utility.javafx.JavaFXUtil;
import bluej.utility.javafx.SharedTransition;
import bluej.utility.javafx.binding.ViewportHeightBinding;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Platform;
import javafx.beans.Observable;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleExpression;
import javafx.beans.binding.StringBinding;
import javafx.beans.binding.StringExpression;
import javafx.beans.property.*;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableBooleanValue;
import javafx.beans.value.ObservableStringValue;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.event.Event;
import javafx.geometry.Bounds;
import javafx.geometry.Point2D;
import javafx.geometry.Pos;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.ScrollPane;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.ScrollEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.scene.text.TextFlow;
import javafx.stage.Stage;
import javafx.util.Duration;
import javafx.util.Pair;
import threadchecker.OnThread;
import threadchecker.Tag;
import java.io.File;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
| The big central editor class for the frame editor. The frames analogue of MoeEditor.
|
| This class contains all the coordinating functionality for each frame editor. It is exposed to sub-elements
| (frames, slots, etc) via the InteractionManager interface, so that class is a good place
| to understand the public interface of this class.
|
@OnThread(Tag.FX)
public class FrameEditorTab
extends FXTab implements InteractionManager, SuggestionListParent{
@OnThread(Tag.Any)
private final static List<Future<List<AssistContentThreadSafe>>> popularImports = new ArrayList<>();
@OnThread(Tag.Any)
private final static List<Future<List<AssistContentThreadSafe>>> rarerImports = new ArrayList<>();
@OnThread(Tag.Any) private static Future<List<AssistContentThreadSafe>> javaLangImports;
@OnThread(Tag.FX) private static List<AssistContentThreadSafe> prims;
private final SimpleObjectProperty<CursorOrSlot> focusedItem = new SimpleObjectProperty<>(null);
private final TopLevelCodeElement initialSource;
private final StringProperty nameProperty = new SimpleStringProperty();
@OnThread(Tag.Any)
private final ReadWriteLock importedTypesLock = new ReentrantReadWriteLock();
@OnThread(Tag.Any)
private final List<Future<List<AssistContentThreadSafe>>> importedTypes;
private final FrameSelection selection = new FrameSelection(this);
private final FrameEditor editor;
private final UndoRedoManager undoRedoManager;
private final ObjectProperty<View> viewProperty = new SimpleObjectProperty<>(View.NORMAL);
private final EntityResolver projectResolver;
private final FrameMenuManager menuManager;
private WindowOverlayPane windowOverlayPane;
private CodeOverlayPane codeOverlayPane;
private VBox bannerPane;
private boolean undoBannerShowing = false;
private Observable observableScroll;
private ViewportHeightBinding viewportHeight;
private boolean manualScrolledSinceLastFocusChange = false;
private CursorOrSlot focusOwnerDuringLastManualScroll = null;
@OnThread(Tag.FX)
private final Property<TopLevelFrame<? extends TopLevelCodeElement>> topLevelFrameProperty = new SimpleObjectProperty<>();
private ContextMenu menu;
private HBox controlPanel;
private FrameCursor dragTarget;
private BorderPaneWithHighlightColor contentRoot;
private StackPane scrollAndOverlays;
private StackPane scrollContent;
private final ObjectProperty<FXTabbedEditor> parent = new SimpleObjectProperty<>();
private final Project project;
private ScrollPane scroll;
private boolean selectingByDrag;
private BirdseyeManager birdseyeManager;
private Rectangle birdseyeSelection;
private Pane birdseyeSelectionPane;
private Node birdseyeDefaultFocusAfter;
private FXRunnable birdseyeDefaultRequestFocusAfter;
private Iterator<CodeError> errors;
private SimpleBooleanProperty initialised = new SimpleBooleanProperty(false);
private boolean startedInitialising;
private Frame stackHighlight;
private EditableSlot showingUnderlinesFor = null;
private ErrorOverviewBar errorOverviewBar;
@OnThread(Tag.FX)
private boolean loading = false;
private boolean animatingScroll = false;
private boolean anyButtonsPressed = false;
private SharedTransition viewChange;
private ErrorAndFixDisplay cursorErrorDisplay;
private boolean inScrollTo = false;
private Canvas execHistoryCanvas;
private final Set<Node> execNodesListenedTo = new HashSet<>();
private final SimpleBooleanProperty debugVarVisibleProperty = new SimpleBooleanProperty(false);
private List<HighlightedBreakpoint> latestExecHistory;
private StringBinding strideFontSizeAsString;
private StringExpression strideFontCSS;
private final SimpleObjectProperty<Image> imageProperty = new SimpleObjectProperty<>(null);
@OnThread(Tag.FXPlatform)
public FrameEditorTab(Project project, EntityResolver resolver, FrameEditor editor, TopLevelCodeElement initialSource)
{
super(true);
this.project = project;
this.projectResolver = resolver;
this.importedTypes = new ArrayList<>();
this.editor = editor;
this.initialSource = initialSource;
this.undoRedoManager = new UndoRedoManager(new FrameState(initialSource));
this.menuManager = new FrameMenuManager(this);
if (javaLangImports == null)
javaLangImports = importsUpdated("java.lang.*");
if (popularImports.isEmpty())
{
popularImports.addAll(Arrays.asList(
"java.io.*",
"java.math.*",
"java.time.*",
"java.util.*",
"java.util.function.*",
"java.util.stream.*",
Config.isGreenfoot() ? "greenfoot.*" : null
).stream().filter(i -> i != null).map(this::importsUpdated).collect(Collectors.toList()));
rarerImports.addAll(Arrays.asList(
Config.isGreenfoot() ? null : "java.awt.*",
Config.isGreenfoot() ? null : "java.awt.event.*",
"java.net.*",
"java.text.*",
"java.util.concurrent.*",
Config.isGreenfoot() ? null : "javafx.application.*",
Config.isGreenfoot() ? null : "javafx.beans.*",
Config.isGreenfoot() ? null : "javafx.beans.property.*",
Config.isGreenfoot() ? null : "javafx.collections.*",
Config.isGreenfoot() ? null : "javafx.event.*",
Config.isGreenfoot() ? null : "javafx.scene.*",
Config.isGreenfoot() ? null : "javafx.scene.control.*",
Config.isGreenfoot() ? null : "javafx.scene.input.*",
Config.isGreenfoot() ? null : "javafx.scene.layout.*",
Config.isGreenfoot() ? null : "javafx.stage.*",
Config.isGreenfoot() ? null : "javax.swing.*",
Config.isGreenfoot() ? null : "javax.swing.event.*"
).stream().filter(i -> i != null).map(this::importsUpdated).collect(Collectors.toList()));
}
}
public static String blockSkipModifierLabel()
{
return Config.isMacOS() ? "\u2325" : "^";
}
private static boolean hasBlockSkipModifierPressed(KeyEvent event)
{
if (Config.isMacOS()) {
return event.isAltDown();
}
else {
return event.isControlDown();
}
}
@OnThread(Tag.Worker)
private static List getFutureList(Future<List<T>> f)
{
try
{
return f.get(10, TimeUnit.SECONDS);
}
catch (Exception e) {
Debug.reportError("Problem looking up types", e);
return Collections.emptyList();
}
}
@OnThread(Tag.FXPlatform)
private Future> importsUpdated(final String x)
{
CompletableFuture<List<AssistContentThreadSafe>> f = new CompletableFuture<>();
Utility.runBackground(() -> {
try
{
f.complete(project.getImportScanner().getImportedTypes(x));
}
catch (Throwable t)
{
Debug.reportError("Exception while scanning for import " + x, t);
f.complete(Collections.emptyList());
}
});
return f;
}
@OnThread(Tag.FXPlatform)
public void initialiseFX()
{
if (startedInitialising)
return;
startedInitialising = true;
setText("");
Label titleLabel = new Label(initialSource.getName());
titleLabel.textProperty().bind(nameProperty);
HBox tabHeader = new HBox(titleLabel, makeClassGraphicIcon(imageProperty, 16, false));
tabHeader.setAlignment(Pos.CENTER);
tabHeader.setSpacing(3.0);
tabHeader.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
if (e.getButton() == MouseButton.MIDDLE)
{
setWindowVisible(false, false);
}
});
setGraphic(tabHeader);
JavaFXUtil.addStyleClass(this, "frame-editor-tab", initialSource.getStylePrefix() + "frame-editor-tab");
JavaFXUtil.addChangeListener(focusedItem, focused -> {
JavaFXUtil.runNowOrLater(() -> menuManager.setMenuItems(focused != null ? focused.getMenuItems(false) : selection != null ? selection.getEditMenuItems(false) : Collections.emptyMap()));
if (focused != null && !focused.equals(focusOwnerDuringLastManualScroll))
manualScrolledSinceLastFocusChange = false;
});
selection.addChangeListener(() -> menuManager.setMenuItems(focusedItem.get() != null ? focusedItem.get().getMenuItems(false) : Collections.emptyMap()));
contentRoot = new BorderPaneWithHighlightColor();
JavaFXUtil.addStyleClass(contentRoot, "frame-editor-tab-content", initialSource.getStylePrefix() + "frame-editor-tab-content");
scrollAndOverlays = new StackPane();
windowOverlayPane = new WindowOverlayPane();
bannerPane = new VBox();
bannerPane.setPickOnBounds(false);
scroll = new ScrollPane() {
@Override
public void requestFocus()
{
}
};
scroll.getStyleClass().add("frame-editor-scroll-pane");
scroll.setFitToWidth(true);
observableScroll = scroll.vvalueProperty();
viewportHeight = new ViewportHeightBinding(scroll);
scrollAndOverlays.getChildren().addAll(scroll, windowOverlayPane.getNode(), bannerPane);
scroll.setFitToWidth(true);
JavaFXUtil.addChangeListener(scroll.vvalueProperty(), v -> {
if (!animatingScroll)
{
manualScrolledSinceLastFocusChange = true;
if (focusedItem.get() != null)
focusOwnerDuringLastManualScroll = focusedItem.get();
}
});
scrollAndOverlays.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
final FrameCursor focusedCursor = getFocusedCursor();
boolean blockCursorFocused = focusedCursor != null;
switch (event.getCode()) {
case UP:
if (blockCursorFocused) {
FrameCursor c;
if (event.isShiftDown() && viewProperty.get() != View.JAVA_PREVIEW) {
c = focusedCursor.getPrevSkip();
selection.toggleSelectUp(focusedCursor.getFrameBefore());
}
else if (hasBlockSkipModifierPressed(event) || (viewProperty.get() == View.JAVA_PREVIEW && focusedCursor.getFrameBefore() != null && !focusedCursor.getFrameBefore().isFrameEnabled())) {
selection.clear();
c = focusedCursor.getPrevSkip();
}
else {
selection.clear();
c = focusedCursor.getParentCanvas().getPrevCursor(focusedCursor, true);
}
if (c != null) {
c.requestFocus();
}
event.consume();
}
break;
case DOWN:
if (blockCursorFocused) {
FrameCursor c;
if (event.isShiftDown() && viewProperty.get() != View.JAVA_PREVIEW) {
c = focusedCursor.getNextSkip();
selection.toggleSelectDown(focusedCursor.getFrameAfter());
}
else if (hasBlockSkipModifierPressed(event) || (viewProperty.get() == View.JAVA_PREVIEW && focusedCursor.getFrameAfter() != null && !focusedCursor.getFrameAfter().isFrameEnabled())) {
selection.clear();
c = focusedCursor.getNextSkip();
}
else {
selection.clear();
c = focusedCursor.getParentCanvas().getNextCursor(focusedCursor, true);
}
if (c != null) {
c.requestFocus();
}
event.consume();
}
break;
case HOME:
if (blockCursorFocused) {
getTopLevelFrame().focusOnBody(TopLevelFrame.BodyFocus.TOP);
selection.clear();
event.consume();
}
break;
case END:
if (blockCursorFocused) {
getTopLevelFrame().focusOnBody(TopLevelFrame.BodyFocus.BOTTOM);
selection.clear();
event.consume();
}
break;
case LEFT:
if (blockCursorFocused) {
Frame frameBefore = focusedCursor.getFrameBefore();
if (frameBefore != null) {
if ( !frameBefore.focusFrameEnd(false) ) {
focusedCursor.getUp().requestFocus();
}
}
else {
Frame enclosingFrame = focusedCursor.getEnclosingFrame();
if (enclosingFrame != null) {
enclosingFrame.focusLeft(focusedCursor.getParentCanvas());
}
else
{
Debug.message("No enclosing frame on cursor");
}
}
selection.clear();
event.consume();
}
break;
case RIGHT:
if (blockCursorFocused) {
Frame frame = focusedCursor.getFrameAfter();
if (frame != null) {
if ( !frame.focusFrameStart() ) {
focusedCursor.getParentCanvas().getNextCursor(focusedCursor, true).requestFocus();
}
}
else {
Frame enclosingFrame = focusedCursor.getEnclosingFrame();
if (enclosingFrame != null) {
enclosingFrame.focusRight(focusedCursor.getParentCanvas());
}
else
{
Debug.message("No enclosing frame on cursor");
}
}
selection.clear();
event.consume();
}
break;
case EQUALS:
if (Config.isMacOS() && event.isMetaDown()) {
increaseFontSize();
}
break;
case MINUS:
if (Config.isMacOS() && event.isMetaDown()) {
decreaseFontSize();
}
break;
default:
if (event.getCode() == Config.getKeyCodeForYesNo(ShortcutKey.YES_ANYWHERE))
{
SuggestedFollowUpDisplay.shortcutTyped(this, ShortcutKey.YES_ANYWHERE);
}
else if (event.getCode() == Config.getKeyCodeForYesNo(ShortcutKey.NO_ANYWHERE))
{
SuggestedFollowUpDisplay.shortcutTyped(this, ShortcutKey.NO_ANYWHERE);
}
break;
}
});
scrollAndOverlays.addEventFilter(MouseEvent.ANY, e -> {
anyButtonsPressed = e.isPrimaryButtonDown() || e.isSecondaryButtonDown() || e.isMiddleButtonDown();
});
controlPanel = new HBox();
Button stepButton = new Button("Step");
Button continueButton = new Button("Continue");
controlPanel.getChildren().addAll(stepButton, continueButton);
controlPanel.setSpacing(10.0);
contentRoot.setCenter(scrollAndOverlays);
codeOverlayPane = new CodeOverlayPane();
scrollContent = new StackPane();
errorOverviewBar = new ErrorOverviewBar(this, scrollContent, this::nextError);
JavaFXUtil.addChangeListener(errorOverviewBar.showingCount(), count -> {
if (count.intValue() > 0)
JavaFXUtil.addStyleClass(this, "bj-tab-error");
else{ getStyleClass().removeAll("bj-tab-error");
}
});
contentRoot.setRight(errorOverviewBar);
loading = true;
new Thread() {
@OnThread(value = Tag.FX, ignoreParent = true)
public void run()
{
TopLevelFrame<? extends TopLevelCodeElement> frame = initialSource.createTopLevelFrame(FrameEditorTab.this);
frame.regenerateCode();
TopLevelCodeElement el = frame.getCode();
JavaFXUtil.runPlatformLater(() ->
{
el.updateSourcePositions();
FrameEditorTab.this.topLevelFrameProperty.setValue(frame);
nameProperty.bind(getTopLevelFrame().nameProperty());
JavaFXUtil.addChangeListener(getTopLevelFrame().nameProperty(), n ->
{
JavaFXUtil.runNowOrLater(() -> {
editor.codeModified();
try
{
editor.save();
}
catch (IOException e)
{
Debug.reportError("Problem saving after name change", e);
}
});
});
getTopLevelFrame().bindMinHeight(viewportHeight);
scrollContent.getChildren().add(0, getTopLevelFrame().getNode());
updateFontSize();
JavaFXUtil.bindMap(FrameEditorTab.this.importedTypes, getTopLevelFrame().getImports(), FrameEditorTab.this::importsUpdated, change ->
{
importedTypesLock.writeLock().lock();
change.run();
importedTypesLock.writeLock().unlock();
});
if (getTopLevelFrame() != null)
{
saved();
editor.earlyErrorCheck(getTopLevelFrame().getCode().findEarlyErrors(), -1);
Platform.runLater(FrameEditorTab.this::updateDisplays);
}
initialised.set(true);
loading = false;
});
}
}.start();
JavaFXUtil.addChangeListener(viewProperty, menuManager::notifyView);
birdseyeSelection = new Rectangle();
JavaFXUtil.addStyleClass(birdseyeSelection, "birdseye-selection");
birdseyeSelectionPane = new Pane(birdseyeSelection);
birdseyeSelectionPane.setVisible(false);
birdseyeSelectionPane.setMouseTransparent(false);
birdseyeSelectionPane.setOnMouseClicked(e -> {
FrameCursor clickTarget = birdseyeManager.getClickedTarget(e.getSceneX(), e.getSceneY());
if (clickTarget == null)
disableBirdseyeView(Frame.ViewChangeReason.MOUSE_CLICKED);
else{ disableBirdseyeView(clickTarget.getNode(), Frame.ViewChangeReason.MOUSE_CLICKED, clickTarget::requestFocus);
}
e.consume();
});
birdseyeSelectionPane.setOnMouseMoved(e -> {
birdseyeSelectionPane.setCursor(birdseyeManager.canClick(e.getSceneX(), e.getSceneY()) ? Cursor.HAND : Cursor.DEFAULT);
});
scrollContent.getChildren().addAll(
/*topLevelFrame.getNode(),
odeOverlayPane.getNode(), birdseyeSelectionPane );
scroll.setContent(scrollContent);
setContent(contentRoot);
contentRoot.addEventHandler(MouseEvent.MOUSE_PRESSED, Event::consume);
contentRoot.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
switch (event.getCode())
{
case Y:
if (!Config.isMacOS() && event.isShortcutDown() && !event.isShiftDown())
{
redo();
event.consume();
}
break;
case Z:
if (!Config.isMacOS() && event.isShortcutDown() && !event.isShiftDown())
{
undo();
event.consume();
}
break;
case UP:
if (viewProperty.get().isBirdseye())
{
birdseyeManager.up();
calculateBirdseyeRectangle();
event.consume();
}
break;
case DOWN:
if (viewProperty.get().isBirdseye())
{
birdseyeManager.down();
calculateBirdseyeRectangle();
event.consume();
}
break;
case ENTER:
if (viewProperty.get().isBirdseye())
{
FrameCursor target = birdseyeManager.getCursorForCurrent();
disableBirdseyeView(target.getNode(), Frame.ViewChangeReason.KEY_PRESSED_ENTER, target::requestFocus);
event.consume();
}
break;
case ESCAPE:
if (viewProperty.get() == View.JAVA_PREVIEW)
{
disableJavaPreview(Frame.ViewChangeReason.KEY_PRESSED_ESCAPE);
event.consume();
}
else if (viewProperty.get().isBirdseye())
{
disableBirdseyeView(Frame.ViewChangeReason.KEY_PRESSED_ESCAPE);
event.consume();
}
break;
}
});
contentRoot.addEventFilter(ScrollEvent.SCROLL, e -> {
if (e.isShortcutDown())
{
if (e.getDeltaY() > 0)
increaseFontSize();
else{ decreaseFontSize();
}
e.consume();
}
});
addWeakFontSizeUpdater(this);
regenerateAndReparse();
}
public void cleanup()
{
FrameCursor.editorClosing(this);
}
private TopLevelFrame extends TopLevelCodeElement> getTopLevelFrame()
{
return topLevelFrameProperty.getValue();
}
@OnThread(Tag.FXPlatform)
public void withTopLevelFrame(FXPlatformConsumer<TopLevelFrame<? extends TopLevelCodeElement>> action)
{
JavaFXUtil.onceNotNull(topLevelFrameProperty, action);
}
@OnThread(Tag.FXPlatform)
public void redrawExecHistory(List<HighlightedBreakpoint> execHistory)
{
this.latestExecHistory = execHistory;
CodeOverlayPane overlay = getCodeOverlayPane();
if (execHistoryCanvas != null)
overlay.removeOverlay(execHistoryCanvas);
execHistoryCanvas = overlay.addFullSizeCanvas();
GraphicsContext g = execHistoryCanvas.getGraphicsContext2D();
double prevTargetX = 0, prevTargetY = 0;
HighlightedBreakpoint prevPoint = null;
List<HighlightedBreakpoint> drawnFrom = new ArrayList<>();
for (int i = 0; i < execHistory.size(); i++)
{
HighlightedBreakpoint b = execHistory.get(i);
if (b.getNode() != null)
{
Bounds bounds = b.getNode().localToScene(b.getNode().getBoundsInLocal());
if (!execNodesListenedTo.contains(b.getNode()))
{
JavaFXUtil.addChangeListenerPlatform(b.getNode().localToSceneTransformProperty(), t -> {
redrawExecHistory(latestExecHistory);
});
execNodesListenedTo.add(b.getNode());
}
double targetX = execHistoryCanvas.getWidth()*0.75;
double targetY = overlay.sceneYToCodeOverlayY(bounds.getMinY()) + b.getYOffset();
if (b.showExec(i))
{
if (i == 0)
{
prevTargetX = targetX;
prevTargetY = targetY - 10;
}
g.setStroke(Color.WHITE);
g.setLineWidth(4.0);
for (int k = 0; k < 2; k++)
{
if (Math.abs(prevTargetY - targetY) < 15.0f)
{
g.strokeLine(prevTargetX, prevTargetY, targetX, targetY);
g.strokeLine(targetX - 10, targetY - 10, targetX, targetY);
g.strokeLine(targetX + 10, targetY - 10, targetX, targetY);
}
else
{
double bulge, angle;
if (prevPoint != null && drawnFrom.contains(prevPoint))
{
bulge = 35;
angle = 0.15;
}
else if (Math.abs(prevTargetY - targetY) < 60.0f)
{
bulge = 5;
angle = 0.4;
}
else
{
bulge = 10;
angle = 0.15;
}
if (prevTargetY < targetY)
{
g.beginPath();
g.moveTo(prevTargetX, prevTargetY);
g.bezierCurveTo(prevTargetX + bulge, prevTargetY, prevTargetX + bulge, targetY, prevTargetX, targetY);
g.stroke();
g.strokeLine(targetX - 14.4 * Math.sin(angle), targetY - 14.4 * Math.cos(angle), targetX, targetY);
g.strokeLine(targetX + 14.4 * Math.cos(angle), targetY - 14.4 * Math.sin(angle), targetX, targetY);
}
else
{
double turnBack = overlay.sceneYToCodeOverlayY(bounds.getMinY()) + b.getYOffsetOfTurnBack();
g.beginPath();
g.moveTo(prevTargetX, prevTargetY);
g.bezierCurveTo(prevTargetX + bulge, prevTargetY, prevTargetX + bulge, turnBack, prevTargetX, turnBack);
g.bezierCurveTo(targetX - 2 * bulge, turnBack, targetX - 2 * bulge, targetY, targetX, targetY);
g.stroke();
g.strokeLine(targetX + 14.4 * Math.sin(angle), targetY + 14.4 * Math.cos(angle), targetX, targetY);
g.strokeLine(targetX - 14.4 * Math.cos(angle), targetY + 14.4 * Math.sin(angle), targetX, targetY);
}
}
g.setStroke(Color.BLUE);
g.setLineWidth(2.0);
}
if (prevPoint != null)
drawnFrom.add(prevPoint);
}
prevTargetX = targetX;
prevTargetY = targetY + 5;
prevPoint = b;
}
}
}
ObservableBooleanValue debugVarVisibleProperty()
{
return debugVarVisibleProperty;
}
@OnThread(Tag.FXPlatform)
public void addExtends(String className)
{
withTopLevelFrame(f -> {
f.addExtendsClassOrInterface(className);
saveAfterAutomatedEdit();
});
}
@OnThread(Tag.FXPlatform)
public void removeExtendsClass()
{
withTopLevelFrame(f -> {
f.removeExtendsClass();
saveAfterAutomatedEdit();
});
}
@OnThread(Tag.FXPlatform)
public void addImplements(String className)
{
withTopLevelFrame(f -> {
f.addImplements(className);
saveAfterAutomatedEdit();
});
}
@OnThread(Tag.FXPlatform)
public void removeExtendsOrImplementsInterface(String interfaceName)
{
withTopLevelFrame(f -> {
f.removeExtendsOrImplementsInterface(interfaceName);
saveAfterAutomatedEdit();
});
}
@OnThread(Tag.FXPlatform)
private void saveAfterAutomatedEdit()
{
modifiedFrame(null, true);
editor.getWatcher().scheduleCompilation(false, CompileReason.MODIFIED, CompileType.INDIRECT_USER_COMPILE);
}
@OnThread(Tag.Any)
private static enum ShowVars
{
NONE, FIELDS;
@Override
public String toString()
{
if (this == NONE)
return "None";
else{ return "Fields";
}
}
}
p.public void showDebuggerControls(DebuggerThread thread)
{
if (contentRoot.getBottom() != null)
return;
HBox buttons = new HBox();
JavaFXUtil.addStyleClass(buttons, "debugger-buttons");
ImageView stepIcon = new ImageView(Config.getFixedImageAsFXImage("step.gif"));
stepIcon.setRotate(90);
Button stepButton = new Button("Step", stepIcon);
stepButton.setOnAction(e -> thread.step());
ImageView continueIcon = new ImageView(Config.getFixedImageAsFXImage("continue.gif"));
continueIcon.setRotate(90);
Button continueButton = new Button("Continue", continueIcon);
continueButton.setOnAction(e -> {thread.cont(); hideDebuggerControls();
});
Button haltButton = new Button("Halt", Config.makeStopIcon(true));
Label showVarLabel = new Label("Show variables: ");
ComboBox<ShowVars> showVars = new ComboBox<>(FXCollections.observableArrayList(ShowVars.values()));
showVars.getSelectionModel().select(0);
debugVarVisibleProperty.bind(showVars.getSelectionModel().selectedItemProperty().isEqualTo(ShowVars.FIELDS));
buttons.getChildren().addAll(stepButton, continueButton, haltButton, showVarLabel, showVars);
contentRoot.setBottom(buttons);
}
private void hideDebuggerControls()
{
contentRoot.setBottom(null);
}
| Note: very important that this is a static inner class, so that a reference
| is not retained to the outer FrameEditorTab class.
|
private static class WeakFontSizeUpdater implements ChangeListener<Number>
{
private final WeakReference<FrameEditorTab> editorRef;
public WeakFontSizeUpdater(FrameEditorTab ed)
{
this.editorRef = new WeakReference<FrameEditorTab>(ed);
}
@Override
public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue)
{
FrameEditorTab ed = editorRef.get();
if (ed == null)
{
observable.removeListener(this);
}
else
{
JavaFXUtil.runPlatformLater(() -> ed.updateFontSize());
}
}
}
private static void addWeakFontSizeUpdater(FrameEditorTab ed)
{
PrefMgr.strideFontSizeProperty().addListener(new WeakFontSizeUpdater(ed));
}
void resetFontSize()
{
PrefMgr.strideFontSizeProperty().set(PrefMgr.DEFAULT_STRIDE_FONT_SIZE);
}
@OnThread(Tag.FXPlatform)
public void searchLink(PossibleLink link, FXPlatformConsumer<Optional<LinkedIdentifier>> paramCallback)
{
Consumer<Optional<LinkedIdentifier>> callback = new Consumer<Optional<LinkedIdentifier>>()
{
@Override
@OnThread(Tag.Any)
public void accept(Optional<LinkedIdentifier> ol)
{
Platform.runLater(() -> paramCallback.accept(ol));
}
};
TopLevelFrame<? extends TopLevelCodeElement> topLevelFrame = getTopLevelFrame();
if (topLevelFrame == null)
{
callback.accept(Optional.empty());
return;
}
if (link instanceof PossibleTypeLink)
{
String name = ((PossibleTypeLink)link).getTypeName();
bluej.pkgmgr.Package pkg = project.getPackage("");
if (pkg.getAllClassnamesWithSource().contains(name))
{
Target t = pkg.getTarget(name);
if (t instanceof ClassTarget)
{
callback.accept(Optional.of(new LinkedIdentifier(name, link.getStartPosition(), link.getEndPosition(), link.getSlot(), () -> {
link.getSlot().removeAllUnderlines();
((ClassTarget) t).open();
})));
return;
}
}
else
{
TopLevelCodeElement code = topLevelFrame.getCode();
if (code != null)
{
PackageOrClass resolved = code.getResolver().resolvePackageOrClass(name, null);
if (resolved.getName().startsWith("java.") || resolved.getName().startsWith("javax."))
{
callback.accept(Optional.of(new LinkedIdentifier(name, link.getStartPosition(), link.getEndPosition(), link.getSlot(), () -> getParent().openJavaCoreDocTab(resolved.getName()))));
return;
}
else if (resolved.getName().startsWith("greenfoot."))
{
callback.accept(Optional.of(new LinkedIdentifier(name, link.getStartPosition(), link.getEndPosition(), link.getSlot(), () -> getParent().openGreenfootDocTab(resolved.getName()))));
return;
}
}
}
callback.accept(Optional.empty());
}
p.public else if(link instanceof PossibleKnownMethodLink)
{
PossibleKnownMethodLink pml = (PossibleKnownMethodLink) link;
final String qualClassName = pml.getQualClassName();
final String urlSuffix = pml.getURLMethodSuffix();
searchMethodLink(topLevelFrame.getCode(), link, qualClassName, pml.getDisplayName(), pml.getDisplayName(), urlSuffix, callback);
}
p.public else if(link instanceof PossibleMethodUseLink)
{
PossibleMethodUseLink pmul = (PossibleMethodUseLink) link;
List<AssistContent> candidates = editor.getAvailableMembers(topLevelFrame.getCode(), pmul.getSourcePositionSupplier().get(), Collections.singleton(CompletionKind.METHOD), true)
.stream()
.filter(ac -> ac.getName().equals(pmul.getMethodName()))
.collect(Collectors.toList());
if (candidates.size() > 1)
{
if (candidates.stream().anyMatch(ac -> ac.getParams().size() == pmul.getNumParams()))
{
candidates.removeIf(ac -> ac.getParams().size() != pmul.getNumParams());
}
}
if (candidates.size() >= 1)
{
AssistContent ac = candidates.get(0);
String displayName = ac.getName() + "(" + ac.getParams().stream().map(ParamInfo::getUnqualifiedType).collect(Collectors.joining(", ")) + ")";
searchMethodLink(topLevelFrame.getCode(), link, ac.getDeclaringClass(), ac.getName(), displayName, PossibleKnownMethodLink.encodeSuffix(ac.getName(), Utility.mapList(ac.getParams(), ParamInfo::getQualifiedType)), callback);
}
else
{
callback.accept(Optional.empty());
}
}
p.public else if(link instanceof PossibleVarLink)
{
final String name = ((PossibleVarLink)link).getVarName();
final CodeElement el = ((PossibleVarLink) link).getUsePoint();
FrameEditorTab ed = (FrameEditorTab)ASTUtility.getTopLevelElement(el).getEditor();
if (ed == FrameEditorTab.this) {
callback.accept(Optional.of(new LinkedIdentifier(name, link.getStartPosition(), link.getEndPosition(), link.getSlot(), () -> el.show(ShowReason.LINK_TARGET))));
}
else {
callback.accept(Optional.of(new LinkedIdentifier(name, link.getStartPosition(), link.getEndPosition(), link.getSlot(), () -> {
getParent().setWindowVisible(true, ed);
el.show(ShowReason.LINK_TARGET);
})));
}
}
}
@OnThread(Tag.FXPlatform)
private void searchMethodLink(TopLevelCodeElement code, PossibleLink link, String qualClassName, String methodName, String methodDisplayName, String urlSuffix, Consumer<Optional<LinkedIdentifier>> callback)
{
bluej.pkgmgr.Package pkg = project.getPackage("");
if (pkg.getAllClassnamesWithSource().contains(qualClassName))
{
Target t = pkg.getTarget(qualClassName);
if (t instanceof ClassTarget)
{
ClassTarget classTarget = (ClassTarget) t;
callback.accept(Optional.of(new LinkedIdentifier(methodDisplayName, link.getStartPosition(), link.getEndPosition(), link.getSlot(), () -> {
link.getSlot().removeAllUnderlines();
classTarget.open();
classTarget.getEditor().focusMethod(methodName, null);
})));
return;
}
}
else
{
if (code != null)
{
PackageOrClass resolved = code.getResolver().resolvePackageOrClass(qualClassName, null);
if (resolved.getName().startsWith("java.") || resolved.getName().startsWith("javax."))
{
callback.accept(Optional.of(new LinkedIdentifier(methodDisplayName, link.getStartPosition(), link.getEndPosition(), link.getSlot(), () -> getParent().openJavaCoreDocTab(resolved.getName(), urlSuffix))));
return;
}
else if (resolved.getName().startsWith("greenfoot."))
{
callback.accept(Optional.of(new LinkedIdentifier(methodDisplayName, link.getStartPosition(), link.getEndPosition(), link.getSlot(), () -> getParent().openGreenfootDocTab(resolved.getName(), urlSuffix))));
return;
}
}
}
callback.accept(Optional.empty());
}
@OnThread(Tag.FXPlatform)
void enableCycleBirdseyeView()
{
selection.clear();
if (viewProperty.get() == View.NORMAL && getTopLevelFrame().canDoBirdseye())
{
if (viewChange != null)
viewChange.stop();
viewChange = new SharedTransition();
viewChange.addOnStopped(() -> JavaFXUtil.runAfter(Duration.millis(50), () -> {
birdseyeSelectionPane.setVisible(true);
calculateBirdseyeRectangle();
}));
birdseyeDefaultFocusAfter = scroll.getScene().getFocusOwner();
birdseyeDefaultRequestFocusAfter = () -> { if (birdseyeDefaultFocusAfter != null) birdseyeDefaultFocusAfter.requestFocus();
};
birdseyeManager = getTopLevelFrame().prepareBirdsEyeView(viewChange);
changeViewMode(View.BIRDSEYE_NODOC, Frame.ViewChangeReason.MENU_OR_SHORTCUT);
setupAnimateViewTo(View.NORMAL, View.BIRDSEYE_NODOC, viewChange);
viewChange.animateOver(Duration.millis(500));
}
else if (viewProperty.get() == View.JAVA_PREVIEW)
{
disableJavaPreview(Frame.ViewChangeReason.MENU_OR_SHORTCUT);
enableCycleBirdseyeView();
}
else if (viewProperty.get().isBirdseye())
{
if (viewChange != null)
viewChange.stop();
viewChange = new SharedTransition();
viewChange.addOnStopped(() -> JavaFXUtil.runAfter(Duration.millis(50), () -> {
calculateBirdseyeRectangle();
}));
JavaFXUtil.addChangeListener(viewChange.getProgress(), t -> calculateBirdseyeRectangle());
View oldView = viewProperty.get();
View newView = viewProperty.get() == View.BIRDSEYE_DOC ? View.BIRDSEYE_NODOC : View.BIRDSEYE_DOC;
changeViewMode(newView, Frame.ViewChangeReason.MENU_OR_SHORTCUT);
setupAnimateViewTo(oldView, newView, viewChange);
viewChange.animateOver(Duration.millis(500));
}
}
@OnThread(Tag.FXPlatform)
p.public void disableBirdseyeView(Node viewTarget, Frame.ViewChangeReason reason, FXRunnable requestFocus)
{
if (viewProperty.get().isBirdseye())
{
if (viewChange != null)
viewChange.stop();
viewChange = new SharedTransition();
birdseyeSelectionPane.setVisible(false);
birdseyeManager = null;
FXRunnable remove = JavaFXUtil.addChangeListener(viewTarget.localToSceneTransformProperty(), t -> {
if (!inScrollTo)
scrollTo(viewTarget, -200.0);
});
viewChange.addOnStopped(() -> {
remove.run();
if (requestFocus != null)
{
requestFocus.run();
}
});
View oldView = viewProperty.get();
changeViewMode(View.NORMAL, reason);
setupAnimateViewTo(oldView, View.NORMAL, viewChange);
viewChange.animateOver(Duration.millis(500));
}
}
@OnThread(Tag.FXPlatform)
void disableBirdseyeView(Frame.ViewChangeReason reason)
{
disableBirdseyeView(birdseyeDefaultFocusAfter, reason, birdseyeDefaultRequestFocusAfter);
}
@OnThread(Tag.FXPlatform)
void enableJavaPreview(Frame.ViewChangeReason reason)
{
if (viewProperty.get() == View.NORMAL)
{
selection.clear();
if (viewChange != null)
viewChange.stop();
viewChange = new SharedTransition();
changeViewMode(View.JAVA_PREVIEW, reason);
setupAnimateViewTo(View.NORMAL, View.JAVA_PREVIEW, viewChange);
viewChange.animateOver(Duration.millis(3000));
}
else if (viewProperty.get().isBirdseye())
{
disableBirdseyeView(reason);
enableJavaPreview(reason);
}
}
@OnThread(Tag.FXPlatform)
void disableJavaPreview(Frame.ViewChangeReason reason)
{
if (viewProperty.get() == View.JAVA_PREVIEW)
{
if (viewChange != null)
viewChange.stop();
viewChange = new SharedTransition();
changeViewMode(View.NORMAL, reason);
setupAnimateViewTo(View.JAVA_PREVIEW, View.NORMAL, viewChange);
viewChange.animateOver(Duration.millis(3000));
}
}
| Records the change of the View mode and sets the view property to the new mode.
|
| @param newView The new view mode that been switch to.
| @param reason The user interaction which triggered the change.
|
@OnThread(Tag.FXPlatform)
private void changeViewMode(View newView, Frame.ViewChangeReason reason)
{
recordViewChange(viewProperty.get(), newView, reason);
viewProperty.set(newView);
}
@OnThread(Tag.FXPlatform)
private void setupAnimateViewTo(View oldView, View newView, SharedTransition animateProgress)
{
FrameCursor fixpoint = getFocusedCursor();
double y = fixpoint == null ? 0 : (fixpoint.getSceneBounds().getMinY() - scroll.localToScene(scroll.getBoundsInLocal()).getMinY());
getTopLevelFrame().getAllFrames().forEach(f -> f.setView(oldView, newView, animateProgress));
if (fixpoint != null)
{
final FrameCursor finalFixpoint = fixpoint;
FXRunnable remove = JavaFXUtil.addChangeListener(getFocusedCursor().getNode().localToSceneTransformProperty(), ignore -> {
scrollTo(finalFixpoint.getNode(), -y);
});
animateProgress.addOnStopped(() -> JavaFXUtil.runAfterCurrent(remove));
}
getParent().scheduleUpdateCatalogue(this, newView == View.NORMAL ? getFocusedCursor() : null, CodeCompletionState.NOT_POSSIBLE, false, newView, Collections.emptyList(), Collections.emptyList());
}
@Override
@OnThread(Tag.FXPlatform)
public BooleanProperty cheatSheetShowingProperty()
{
return getParent().catalogueShowingProperty();
}
@Override
@OnThread(Tag.FXPlatform)
public void focusWhenShown()
{
withTopLevelFrame(f -> f.focusOnBody(TopLevelFrame.BodyFocus.BEST_PICK));
}
@OnThread(Tag.FXPlatform)
public void cancelFreshState()
{
withTopLevelFrame(f -> f.getAllFrames().forEach(Frame::markNonFresh));
}
| Called by FXTabbedEditor when drag ends on us, at the position of the last
| draggedToTab call.
|
@OnThread(Tag.FXPlatform)
void dragEndTab(List<Frame> dragSourceFrames, boolean fromShelf, boolean copying)
{
if (dragSourceFrames != null && !dragSourceFrames.isEmpty())
{
if (dragTarget != null)
{
boolean canMove = dragSourceFrames.stream().allMatch(src -> dragTarget.getParentCanvas().acceptsType(src));
if (canMove && !FXTabbedEditor.isUselessDrag(dragTarget, dragSourceFrames, copying))
{
beginRecordingState(dragTarget);
performDrag(dragSourceFrames, fromShelf, copying);
endRecordingState(dragTarget);
}
selection.clear();
dragTarget.stopShowAsDropTarget();
dragTarget = null;
}
}
}
@OnThread(Tag.FXPlatform)
private void performDrag(List<Frame> dragSourceFrames, boolean fromShelf, boolean copying)
{
boolean shouldDisable = !dragTarget.getParentCanvas().getParent().getFrame().isFrameEnabled();
editor.recordEdits(StrideEditReason.FLUSH);
Collections.reverse(dragSourceFrames);
List<CodeElement> elements = GreenfootFrameUtil.getElementsForMultipleFrames(dragSourceFrames);
for (CodeElement codeElement : elements) {
final Frame frame = codeElement.createFrame(this);
dragTarget.insertBlockAfter(frame);
if (shouldDisable)
frame.setFrameEnabled(false);
}
if (!copying)
dragSourceFrames.forEach(src -> src.getParentCanvas().removeBlock(src));
editor.recordEdits(fromShelf ? StrideEditReason.FRAMES_DRAG_SHELF : StrideEditReason.FRAMES_DRAG);
}
| Called by TabbedEditor when the drag location is updated, and we are the
| currently active tab.
|
@OnThread(Tag.FXPlatform)
void draggedToTab(List<Frame> dragSourceFrames, double sceneX, double sceneY, boolean copying)
{
Bounds scrollBounds = scroll.localToScene(scroll.getBoundsInLocal());
if (sceneX < scrollBounds.getMinX() || sceneX > scrollBounds.getMaxX())
{
if (dragTarget != null) {
dragTarget.stopShowAsDropTarget();
dragTarget = null;
}
}
else
{
FrameCursor newDragTarget = getTopLevelFrame().findCursor(sceneX, sceneY, null, null, dragSourceFrames, true, true);
if (newDragTarget != null && dragTarget != newDragTarget)
{
if (dragTarget != null) {
dragTarget.stopShowAsDropTarget();
dragTarget = null;
}
boolean src = FXTabbedEditor.isUselessDrag(newDragTarget, dragSourceFrames, copying);
boolean acceptsAll = true;
for (Frame srcFrame : dragSourceFrames) {
acceptsAll &= newDragTarget.getParentCanvas().acceptsType(srcFrame);
}
newDragTarget.showAsDropTarget(src, acceptsAll, copying);
dragTarget = newDragTarget;
}
}
if (dragTarget != null)
{
dragTarget.updateDragCopyState(copying);
}
}
| Called by TabbedEditor when we are no longer the target tab during a drag.
|
| We just have to tidy up any display of potential drag targets.
|
@OnThread(Tag.FXPlatform)
void draggedToAnotherTab()
{
if (dragTarget != null)
{
dragTarget.stopShowAsDropTarget();
dragTarget = null;
}
}
@Override
@OnThread(Tag.FXPlatform)
public void clickNearestCursor(double sceneX, double sceneY, boolean shiftDown)
{
FrameCursor target = getTopLevelFrame().findCursor(sceneX, sceneY, null, null, null, false, true);
if (target != null) {
if (shiftDown && viewProperty.get() != View.JAVA_PREVIEW)
{
FrameCursor anchor;
if (selection.getSelected().size() == 0)
{
anchor = getFocusedCursor();
}
else
{
anchor = (selection.getCursorAfter() == getFocusedCursor()) ? selection.getCursorBefore() : selection.getCursorAfter();
}
if (getFocusedCursor() == null || target.getParentCanvas() != anchor.getParentCanvas())
{
return;
}
selection.set(target.getParentCanvas().framesBetween(anchor, target));
target.requestFocus();
}
else
{
target.requestFocus();
selection.clear();
}
}
}
public FrameDictionary getDictionary()
{
return StrideDictionary.getDictionary();
}
@Override
public void setupFrameCursor(FrameCursor f)
{
f.getNode().setOnDragDetected(e -> {
selectingByDrag = true;
e.consume();
});
f.getNode().setOnMouseDragged(e -> {
if (!selectingByDrag || viewProperty.get() == View.JAVA_PREVIEW)
return;
FrameCanvas fCanvas = f.getParentCanvas();
FrameCursor closest = fCanvas.getParent().findCursor(e.getSceneX(), e.getSceneY(), fCanvas.getFirstCursor(), fCanvas.getLastCursor(), null, true, false);
if (closest != null)
selection.set(fCanvas.framesBetween(closest, f));
e.consume();
});
f.getNode().setOnMouseReleased(e -> {
if (selectingByDrag)
{
selectingByDrag = false;
e.consume();
}
});
JavaFXUtil.addFocusListener(f.getNode(), new FXPlatformConsumer<Boolean>()
{
private FXRunnable cancelTimer;
@OnThread(Tag.FXPlatform)
public void accept(Boolean focused)
{
if (getParent() != null)
getParent().scheduleUpdateCatalogue(FrameEditorTab.this, focused ? f : null, CodeCompletionState.NOT_POSSIBLE, !selection.getSelected().isEmpty(), getView(), Collections.emptyList(), Collections.emptyList());
if (cancelTimer != null)
{
cancelTimer.run();
cancelTimer = null;
}
if (!focused)
{
hideError();
}
else
{
cancelTimer = JavaFXUtil.runRegular(Duration.millis(1000), this::updateFocusedDisplay);
}
}
@OnThread(Tag.FXPlatform)
private void hideError()
{
if (cursorErrorDisplay != null)
{
cursorErrorDisplay.hide();
cursorErrorDisplay = null;
}
}
@OnThread(Tag.FXPlatform)
private void updateFocusedDisplay()
{
if (!f.getNode().focusedProperty().get())
{
hideError();
return;
}
Optional<CodeError> maybeErr = Optional.ofNullable(f.getFrameAfter()).flatMap(fr -> fr.getCurrentErrors().findFirst());
if (maybeErr.isPresent())
{
if (cursorErrorDisplay != null && maybeErr.get() == cursorErrorDisplay.getError())
{
return;
}
else
{
hideError();
}
cursorErrorDisplay = new ErrorAndFixDisplay(FrameEditorTab.this, "Below: ", maybeErr.get(), null);
cursorErrorDisplay.showAbove(f.getNode());
}
else
{
maybeErr = Optional.ofNullable(f.getFrameBefore()).flatMap(fr -> fr.getCurrentErrors().findFirst());
if (maybeErr.isPresent())
{
if (cursorErrorDisplay != null && maybeErr.get() == cursorErrorDisplay.getError())
{
return;
}
else
{
hideError();
}
cursorErrorDisplay = new ErrorAndFixDisplay(FrameEditorTab.this, "Above: ", maybeErr.get(), null);
cursorErrorDisplay.showBelow(f.getNode());
}
else
{
maybeErr = Optional.ofNullable(f.getParentCanvas().getParent()).map(CanvasParent::getFrame).flatMap(fr -> fr.getCurrentErrors().findFirst());
if (maybeErr.isPresent() && maybeErr.get().visibleProperty().get())
{
if (cursorErrorDisplay != null && maybeErr.get() == cursorErrorDisplay.getError())
{
return;
}
else
{
hideError();
}
cursorErrorDisplay = new ErrorAndFixDisplay(FrameEditorTab.this, "Enclosing frame: ", maybeErr.get(), null);
cursorErrorDisplay.showBelow(f.getNode());
}
else
{
hideError();
}
}
}
}
});
setupFocusable(new CursorOrSlot(f), f.getNode());
}
@Override
public void setupFrame(final Frame f)
{
JavaFXUtil.listenForContextMenu(f.getNode(), (x, y) -> {
if (viewProperty.get() != View.NORMAL)
return true;
if (!selection.contains(f)) {
selection.set(Arrays.asList(f));
}
if (menu != null) {
menu.hide();
}
menu = selection.getContextMenu();
if (menu != null) {
menu.show(f.getNode(), x, y);
return true;
}
return false;
});
f.getNode().addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
if (getFocusedCursor() != null && e.isShiftDown())
e.consume();
});
f.getNode().addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
if (event.getButton() == MouseButton.PRIMARY && event.isStillSincePress())
{
if (f.leftClicked(event.getSceneX(), event.getSceneY(), event.isShiftDown())) {
event.consume();
}
}
});
FXTabbedEditor.setupFrameDrag(f, false, this::getParent, () -> {
if (dragTarget != null)
{
throw new IllegalStateException("Drag begun while drag in progress");
}
return viewProperty.get() != Frame.View.JAVA_PREVIEW;
}, this::getSelection);
}
@Override
public FrameCursor createCursor(FrameCanvas parent)
{
return new FrameCursor(this, parent);
}
public TopLevelCodeElement getSource()
{
if (getTopLevelFrame() == null) {
return null;
}
return getTopLevelFrame().getCode();
}
private void regenerateCode()
{
if (getTopLevelFrame() != null)
getTopLevelFrame().regenerateCode();
}
@OnThread(Tag.FXPlatform)
public void flagErrorsAsOld()
{
if (getTopLevelFrame() != null)
getTopLevelFrame().flagErrorsAsOld();
}
@OnThread(Tag.FXPlatform)
public void removeOldErrors()
{
if (getTopLevelFrame() != null)
getTopLevelFrame().removeOldErrors();
errors = null;
updateErrorOverviewBar(false);
}
@OnThread(Tag.FXPlatform)
p.public void updateErrorOverviewBar(boolean waitingForCompile)
{
if (getTopLevelFrame() == null)
return;
List<ErrorInfo> errors = getAllErrors()
.filter(e -> e.getRelevantNode() != null)
.map(e -> new ErrorInfo(e.getMessage(), e.getRelevantNode(), e.visibleProperty(), e.focusedProperty(),
() -> {
if (getView().isBirdseye()) {
disableBirdseyeView(e.getRelevantNode(), Frame.ViewChangeReason.MOUSE_CLICKED,
() -> e.jumpTo(this));
}
else {
e.jumpTo(this);
}
}))
.collect(Collectors.toList());
ErrorState state;
if (waitingForCompile || getTopLevelFrame().getAllFrames().anyMatch(Frame::isFresh))
{
state = ErrorState.EDITING;
}
else
{
state = errors.stream().filter(e -> e.isVisible()).count() == 0 ? ErrorState.NO_ERRORS : ErrorState.ERRORS;
}
errorOverviewBar.update(errors, state);
}
@OnThread(Tag.FXPlatform)
private Stream getAllErrors()
{
return Stream.concat(
getTopLevelFrame().getEditableSlots().flatMap(EditableSlot::getCurrentErrors)
, getTopLevelFrame().getAllFrames().flatMap(Frame::getCurrentErrors));
}
@Override
public void modifiedFrame(Frame f, boolean force)
{
if (f != null)
f.trackBlank();
if (!isLoading() || force)
{
JavaFXUtil.runNowOrLater(() ->
{
if ((!isLoading() && getParent() != null) || force)
{
editor.codeModified();
registerStackHighlight(null);
JavaFXUtil.runNowOrLater(() -> updateErrorOverviewBar(true));
SuggestedFollowUpDisplay.modificationIn(this);
}
});
}
}
@OnThread(Tag.FXPlatform)
public void setWindowVisible(boolean vis, boolean bringToFront)
{
if (getParent() == null)
parent.setValue(project.getDefaultFXTabbedEditor());
if (vis)
getParent().addTab(this, vis, bringToFront);
getParent().setWindowVisible(vis, this);
if (bringToFront)
getParent().bringToFront(this);
}
@OnThread(Tag.FXPlatform)
public boolean isWindowVisible()
{
return getParent() != null && getParent().containsTab(this) && getParent().isWindowVisible();
}
@Override
@OnThread(Tag.FXPlatform)
public void withCompletions(PosInSourceDoc pos, ExpressionSlot<?> completing, CodeElement codeEl, FXPlatformConsumer<List<AssistContentThreadSafe>> handler)
{
withTopLevelFrame(_frame -> JavaFXUtil.runNowOrLater(() -> {
TopLevelCodeElement allCode = getSource();
handler.accept(Utility.mapList(Arrays.asList(
editor.getCompletions(allCode, pos, completing, codeEl)
), AssistContentThreadSafe::copy));
}));
}
@Override
@OnThread(Tag.FXPlatform)
public void withSuperConstructors(FXPlatformConsumer<List<AssistContentThreadSafe>> handler)
{
TopLevelCodeElement codeEl = getSource();
handler.accept(Utility.mapList(codeEl.getSuperConstructors(),
c -> new AssistContentThreadSafe(new ConstructorCompletion(c, Collections.emptyMap(),
editor.getJavadocResolver()))));
}
@Override
@OnThread(Tag.FXPlatform)
public List getThisConstructors()
{
TopLevelCodeElement codeEl = getSource();
return codeEl.getThisConstructors();
}
@Override
public void beginRecordingState(RecallableFocus f)
{
undoRedoManager.beginFrameState(getCurrentState(f));
}
@Override
public void endRecordingState(RecallableFocus f)
{
undoRedoManager.endFrameState(getCurrentState(f));
}
private FrameState getCurrentState(RecallableFocus f)
{
regenerateCode();
return new FrameState(getTopLevelFrame(), getSource(), f);
}
@OnThread(Tag.FXPlatform)
public void undo()
{
if (undoRedoManager.isRecording()) {
CursorOrSlot cursorOrSlot = focusedItem.get();
endRecordingState(cursorOrSlot != null ? cursorOrSlot.getRecallableFocus() : null);
}
editor.recordEdits(StrideEditReason.FLUSH);
undoRedoManager.startRestoring();
updateClassContents(undoRedoManager.undo());
undoRedoManager.stopRestoring();
editor.recordEdits(StrideEditReason.UNDO_GLOBAL);
}
@OnThread(Tag.FXPlatform)
public void redo()
{
editor.recordEdits(StrideEditReason.FLUSH);
undoRedoManager.startRestoring();
updateClassContents(undoRedoManager.redo());
undoRedoManager.stopRestoring();
editor.recordEdits(StrideEditReason.REDO_GLOBAL);
}
@OnThread(Tag.FXPlatform)
private void updateClassContents(FrameState state)
{
if (state != null) {
final ClassElement classElement = state.getClassElement(projectResolver,
editor.getPackage().getQualifiedName());
getTopLevelFrame().restoreCast(classElement);
getTopLevelFrame().regenerateCode();
Node n = state.recallFocus(getTopLevelFrame());
if (n != null)
{
ensureNodeVisible(n);
}
}
}
@Override
public void scrollTo(Node n, double yOffsetFromTop, Duration duration
| instant if null
|
{
if (inScrollTo)
return;
inScrollTo = true;
Bounds totalBound = scroll.getContent().localToScene(scroll.getContent().getBoundsInLocal());
Bounds targetBound = n.localToScene(n.getBoundsInLocal());
double totalMinusView = totalBound.getHeight() - scroll.getHeight();
double targetV = Math.max(0.0, Math.min(1.0, (targetBound.getMinY() + yOffsetFromTop - totalBound.getMinY()) / totalMinusView));
targetV = scroll.getVmin() + targetV * (scroll.getVmax() - scroll.getVmin());
if (duration == null)
{
scroll.setVvalue(targetV);
}
else
{
animatingScroll = true;
Timeline t = new Timeline(new KeyFrame(duration, new KeyValue(scroll.vvalueProperty(), targetV)));
t.setOnFinished(e -> { animatingScroll = false;
});
t.play();
}
inScrollTo = false;
}
@Override
public FrameSelection getSelection()
{
return selection;
}
@OnThread(Tag.FXPlatform)
public void saved()
{
getTopLevelFrame().saved();
getTopLevelFrame().getEditableSlots().forEach(EditableSlot::saved);
}
@Override
@OnThread(Tag.FXPlatform)
public void withAccessibleMembers(PosInSourceDoc pos,
Set<CompletionKind> kinds, boolean includeOverriden, FXPlatformConsumer<List<AssistContentThreadSafe>> handler)
{
TopLevelCodeElement allCode = getSource();
handler.accept(Utility.mapList(editor.getAvailableMembers(allCode, pos, kinds, includeOverriden)
, AssistContentThreadSafe::copy));
}
@OnThread(Tag.FXPlatform)
void regenerateAndReparse()
{
regenerateCode();
if (getTopLevelFrame() != null)
{
TopLevelCodeElement code = getTopLevelFrame().getCode();
code.updateSourcePositions();
}
}
@Override
@OnThread(Tag.FXPlatform)
public void afterRegenerateAndReparse(FXPlatformRunnable action)
{
withTopLevelFrame(f -> {
regenerateAndReparse();
if (action != null)
{
action.run();
}
});
}
@OnThread(Tag.FXPlatform)
private void updateDisplays()
{
CodeElement el;
if (getTopLevelFrame() != null && (el = getTopLevelFrame().getCode()) != null)
{
getTopLevelFrame().getAllFrames().forEach(f -> {
if (f instanceof NormalMethodFrame) {
((NormalMethodFrame) f).updateOverrideDisplay((ClassElement) el);
}
});
}
}
@OnThread(Tag.FXPlatform)
private void updateFontSize()
{
getTopLevelFrame().getNode().setStyle(getFontCSS().get());
JavaFXUtil.runAfter(Duration.millis(500),
() -> getTopLevelFrame().getAllFrames().forEach(Frame::fontSizeChanged));
}
@OnThread(Tag.FXPlatform)
void decreaseFontSize()
{
final IntegerProperty fontSize = PrefMgr.strideFontSizeProperty();
Utility.decreaseFontSize(fontSize);
}
@OnThread(Tag.FXPlatform)
void increaseFontSize()
{
final IntegerProperty fontSize = PrefMgr.strideFontSizeProperty();
Utility.increaseFontSize(fontSize);
}
@Override
public StringExpression getFontCSS()
{
if (strideFontSizeAsString == null)
strideFontSizeAsString = PrefMgr.strideFontSizeProperty().asString();
if (strideFontCSS == null)
strideFontCSS = Bindings.concat("-fx-font-size:", strideFontSizeAsString, "pt;");
return strideFontCSS;
}
@Override
public double getFontSize()
{
return PrefMgr.strideFontSizeProperty().get();
}
private void calculateBirdseyeRectangle()
{
Node n = birdseyeManager.getNodeForRectangle();
Point2D scene = n.localToScene(n.getBoundsInLocal().getMinX(), n.getBoundsInLocal().getMinY());
Point2D onPane = getTopLevelFrame().getNode().sceneToLocal(scene);
birdseyeSelection.setX(onPane.getX());
birdseyeSelection.setY(onPane.getY() + 1.5);
birdseyeSelection.setWidth(n.getBoundsInLocal().getWidth());
birdseyeSelection.setHeight(n.getBoundsInLocal().getHeight() - 1.5);
birdseyeSelection.setFocusTraversable(true);
birdseyeSelection.requestFocus();
ensureNodeVisible(birdseyeManager.getNodeForVisibility());
}
@OnThread(Tag.FXPlatform)
private List getPrimitiveTypes()
{
if (prims == null)
prims = PrimitiveTypeCompletion.allPrimitiveTypes().stream().map(AssistContentThreadSafe::copy).collect(Collectors.toList());
return prims;
}
@Override
@OnThread(Tag.FXPlatform)
public void withTypes(Class<?> superType, boolean includeSelf, Set<Kind> kinds, BackgroundConsumer<Map<String, AssistContentThreadSafe>> handler)
{
final Map<String, AssistContentThreadSafe> r = new HashMap<>();
if (kinds.contains(Kind.PRIMITIVE))
addAllToMap(r, getPrimitiveTypes());
addAllToMap(r, editor.getLocalTypes(superType, includeSelf, kinds));
Utility.runBackground(() -> {
addAllToMap(r, getImportedTypes(superType, includeSelf, kinds));
handler.accept(r);
});
}
@OnThread(Tag.Any)
private static void addAllToMap(Map<String, AssistContentThreadSafe> r, List<AssistContentThreadSafe> acs)
{
for (AssistContentThreadSafe ac : acs)
r.put(ac.getName(), ac);
}
@Override
@OnThread(Tag.FXPlatform)
public void withTypes(BackgroundConsumer<Map<String, AssistContentThreadSafe>> handler)
{
withTypes(null, true, Kind.all(), handler);
}
@OnThread(Tag.FXPlatform)
public void removeImports(List<String> importTargets)
{
withTopLevelFrame(topLevelFrame -> JavaFXUtil.runNowOrLater(() -> {
FrameCanvas importCanvas = topLevelFrame.getImportCanvas();
List<Frame> frames = new ArrayList<>(importCanvas.getBlockContents());
for (Frame frame : frames)
{
if (frame instanceof ImportFrame)
{
ImportFrame importFrame = (ImportFrame)frame;
if (importTargets.contains(importFrame.getImport()))
{
importCanvas.removeBlock(importFrame);
}
}
}
}));
}
@OnThread(Tag.FXPlatform)
public void insertAppendMethod(NormalMethodElement method, FXPlatformConsumer<Boolean> after)
{
withTopLevelFrame(topLevelFrame -> {
for (NormalMethodFrame normalMethodFrame : (List<NormalMethodFrame>) topLevelFrame.getMethods()) {
if (normalMethodFrame.getName().equals(method.getName())) {
insertMethodContentsIntoMethodFrame(method, normalMethodFrame);
after.accept(false);
return;
}
}
insertMethodElementAtTheEnd(method);
after.accept(true);
});
}
@OnThread(Tag.FXPlatform)
public void insertMethodCallInConstructor(CallElement methodCall, FXPlatformConsumer<Boolean> after)
{
withTopLevelFrame(topLevelFrame -> {
if (topLevelFrame.getConstructors().isEmpty())
{
topLevelFrame.addDefaultConstructor();
}
for (ConstructorFrame constructorFrame : topLevelFrame.getConstructors())
{
for (CodeFrame<?> innerFrame : constructorFrame.getMembersFrames())
{
if (innerFrame instanceof CallFrame)
{
CallFrame doFrame = (CallFrame) innerFrame;
if (doFrame.getCode().toJavaSource().toTemporaryJavaCodeString().equals(methodCall.toJavaSource().toTemporaryJavaCodeString()))
{
after.accept(true);
return;
}
}
}
insertElementIntoMethod(methodCall, constructorFrame);
}
after.accept(false);
});
}
@OnThread(Tag.FXPlatform)
public void insertElementIntoMethod(CodeElement element, MethodFrameWithBody<? extends MethodWithBodyElement> methodFrame)
{
methodFrame.getLastInternalCursor().insertBlockAfter(element.createFrame(this));
}
@OnThread(Tag.FXPlatform)
private void insertMethodContentsIntoMethodFrame(MethodWithBodyElement methodElement, MethodFrameWithBody<? extends MethodWithBodyElement> methodFrame)
{
for (CodeElement element : methodElement.getContents())
{
methodFrame.getLastInternalCursor().insertBlockAfter(element.createFrame(this));
}
}
@OnThread(Tag.FXPlatform)
private void insertMethodElementAtTheEnd(MethodWithBodyElement method)
{
getTopLevelFrame().insertAtEnd(method.createFrame(this));
}
@OnThread(Tag.Worker)
private Stream getAllImportedTypes()
{
importedTypesLock.readLock().lock();
ArrayList<Future<List<AssistContentThreadSafe>>> importedTypesCopy = new ArrayList<>(importedTypes);
importedTypesLock.readLock().unlock();
return Stream.concat(Stream.of(javaLangImports), importedTypesCopy.stream()).map(FrameEditorTab::getFutureList).flatMap(List::stream);
}
@OnThread(Tag.Worker)
private List getImportedTypes(Class<?> superType, boolean includeSelf, Set<Kind> kinds)
{
if (superType == null)
return getAllImportedTypes()
.filter(ac -> kinds.contains(ac.getTypeKind()))
.collect(Collectors.toList());
return getAllImportedTypes()
.filter(ac -> kinds.contains(ac.getTypeKind()))
.filter(ac -> ac.getSuperTypes().contains(superType.getName()) || (includeSelf && ac.getPackage() != null && (ac.getPackage() + "." + ac.getName()).equals(superType.getName())))
.collect(Collectors.toList());
}
@Override
@OnThread(Tag.Worker)
public Map> getImportSuggestions()
{
HashMap<String, Pair<SuggestionList.SuggestionShown, AssistContentThreadSafe>> imports = new HashMap<>();
Stream.<Pair<SuggestionShown, AssistContentThreadSafe>>concat(
popularImports.stream().flatMap(imps -> getFutureList(imps).stream().map(ac -> new Pair<>(SuggestionList.SuggestionShown.COMMON, ac))),
rarerImports.stream().flatMap(imps -> getFutureList(imps).stream().map(ac -> new Pair<>(SuggestionList.SuggestionShown.RARE, ac)))
)
.filter(imp -> imp.getValue().getPackage() != null)
.forEach(imp -> {
String fullName = imp.getValue().getPackage() + ".";
if (imp.getValue().getDeclaringClass() != null) {
fullName += imp.getValue().getDeclaringClass() + ".";
}
fullName += imp.getValue().getName();
imports.put(fullName, imp);
});
getAllImportedTypes()
.filter(imp -> imp.getPackage() != null)
.forEach(imp -> imports.remove(imp.getPackage() + "." + imp.getName()));
HashMap<SuggestionList.SuggestionShown, Collection<AssistContentThreadSafe>> ret = new HashMap<>();
imports.values().forEach(p -> ret.merge(p.getKey(), new ArrayList<AssistContentThreadSafe>(Arrays.asList(p.getValue())), (BiFunction<Collection<AssistContentThreadSafe>,Collection<AssistContentThreadSafe>,Collection<AssistContentThreadSafe>>)(a, b) -> {a.addAll(b); return a;
}));
return ret;
}
@Override
public void addImport(String importSrc)
{
getTopLevelFrame().addImport(importSrc);
}
@Override
public List getAvailableFilenames()
{
List<FileCompletion> r = new ArrayList<>();
if (Config.isGreenfoot())
{
File imageDir = new File(project.getProjectDir(), "images");
if (imageDir.exists()) {
File[] files = imageDir.listFiles(name -> name.getName().toLowerCase().endsWith(".png")
|| name.getName().toLowerCase().endsWith(".jpg")
|| name.getName().toLowerCase().endsWith(".jpeg"));
r.addAll(Utility.mapList(Arrays.asList(files), ImageCompletion::new));
}
File soundDir = new File(project.getProjectDir(), "sounds");
if (soundDir.exists()) {
File[] files = soundDir.listFiles(name -> name.getName().toLowerCase().endsWith(".wav"));
r.addAll(Utility.mapList(Arrays.asList(files), SoundCompletion::new));
}
}
else
{
File[] files = project.getProjectDir().listFiles(name -> name.getName().toLowerCase().endsWith(".css"));
r.addAll(Utility.<File,FileCompletion>mapList(Arrays.asList(files), file -> new FileCompletion() {
@Override
public File getFile()
{
return file;
}
@Override
public String getType()
{
return "CSS";
}
@Override
public Node getPreview(double maxWidth, double maxHeight)
{
return null;
}
@Override
public Map getShortcuts()
{
return Collections.emptyMap();
}
}));
}
return r;
}
@OnThread(Tag.FXPlatform)
public void nextError()
{
if (errors == null || !errors.hasNext())
{
errors = getAllErrors().iterator();
}
if (!errors.hasNext())
{
editor.getWatcher().scheduleCompilation(true, CompileReason.USER, CompileType.EXPLICIT_USER_COMPILE);
return;
}
while (errors.hasNext())
{
CodeError e = errors.next();
if (e.visibleProperty().get())
{
e.jumpTo(this);
return;
}
}
cancelFreshState();
}
@Override
public void registerStackHighlight(Frame frame)
{
if (stackHighlight != null && stackHighlight != frame) {
stackHighlight.removeStackHighlight();
}
stackHighlight = frame;
}
public ObservableBooleanValue initialisedProperty()
{
return initialised;
}
@Override
public ObservableStringValue nameProperty()
{
return nameProperty;
}
@Override
public void setupFocusableSlotComponent(EditableSlot parent, Node node, boolean canCodeComplete, FXSupplier<List<ExtensionDescription>> getExtensions, List<Hint> hints)
{
JavaFXUtil.addFocusListener(node, focused -> {
if (focused)
{
selection.clear();
}
getParent().scheduleUpdateCatalogue(FrameEditorTab.this, null, focused && canCodeComplete ? CodeCompletionState.POSSIBLE : CodeCompletionState.NOT_POSSIBLE, false, getView(), getExtensions.get(), hints);
});
node.addEventHandler(MouseEvent.MOUSE_MOVED, e -> {
if (dragTarget == null && e.isShortcutDown())
{
if (showingUnderlinesFor != parent)
{
if (showingUnderlinesFor != null)
showingUnderlinesFor.removeAllUnderlines();
showingUnderlinesFor = parent;
parent.findLinks().stream().forEach(l -> searchLink(l, olid -> olid.ifPresent(lid -> lid.show())));
}
}
else if (showingUnderlinesFor == parent)
{
showingUnderlinesFor = null;
parent.removeAllUnderlines();
}
});
node.addEventHandler(MouseEvent.MOUSE_EXITED, e -> {
if (showingUnderlinesFor == parent)
{
showingUnderlinesFor = null;
parent.removeAllUnderlines();
}
});
setupFocusable(new CursorOrSlot(parent), node);
}
private void setupFocusable(CursorOrSlot parent, Node node)
{
FXRunnable checkPositionChange = new FXRunnable() {
Bounds lastBounds = boundsInScrollContent(node);
@Override
public void run()
{
if (node.isFocused())
{
Bounds boundsInScroll = boundsInScrollContent(node);
if (Math.abs(boundsInScroll.getMinY() - lastBounds.getMinY()) >= 1.0
|| Math.abs(boundsInScroll.getMaxY() - lastBounds.getMaxY()) >= 1.0)
{
lastBounds = boundsInScroll;
if (!anyButtonsPressed)
ensureNodeVisible(node);
}
else
{
lastBounds = boundsInScroll;
}
}
}
};
ChangeListener<Object> listener = (a, b, c) -> JavaFXUtil.runPlatformLater(checkPositionChange);
JavaFXUtil.addFocusListener(node, focused -> {
if (focused)
{
node.localToSceneTransformProperty().addListener(listener);
node.boundsInLocalProperty().addListener(listener);
focusedItem.set(parent);
if (menu != null)
{
menu.hide();
}
if (parent.isInsideCanvas(getTopLevelFrame().getImportCanvas()))
{
getTopLevelFrame().ensureImportCanvasShowing();
}
if (!animatingScroll && !anyButtonsPressed)
{
if (!manualScrolledSinceLastFocusChange)
ensureNodeVisible(node);
}
if (getTopLevelFrame() != null)
{
List<EditableSlot> lostFocusSlots = getTopLevelFrame().getEditableSlots().filter(s -> !parent.matchesSlot(s)).collect(Collectors.toList());
lostFocusSlots.forEach(EditableSlot::lostFocus);
Frame focusedFrame = parent.getParentFrame();
HashSet<Frame> frameAndAncestors = new HashSet<>();
for (Frame f = focusedFrame; f != null; f = f.getParentCanvas() == null ? null : f.getParentCanvas().getParent().getFrame())
{
frameAndAncestors.add(f);
}
for (Frame f : Utility.iterableStream(getTopLevelFrame().getAllFrames()))
{
if (!frameAndAncestors.contains(f))
{
f.markNonFresh();
}
if (f != focusedFrame)
f.lostFocus();
}
}
}
else
{
node.localToSceneTransformProperty().removeListener(listener);
node.boundsInLocalProperty().removeListener(listener);
if (parent.equals(focusedItem.get()))
focusedItem.set(null);
}
});
}
@Override
public void ensureNodeVisible(Node node)
{
Bounds boundsInScroll = boundsInScroll(node);
if (boundsInScroll == null)
return;
|
| There is a problem to do with focusing items just added to the scene. When an item is just added,
| its position is usually invalid nonsense (zero, height, positioned high up in the scene.
| If we try to requestFocus on it, we'll end up here, trying to scroll towards this invalid
| position, which we don't want to do; we want to wait until the position is valid
| before deciding whether to scroll.
|
| A reasonable test for "valid position" appears to be whether the height is at least one pixel.
|* I've seen it zero and 0.4 with an invalid position, but after that it seems to become valid.
*
* What we do in the case the position is invalid is line up a one-time listener on the bounds
| which will call this method again. If they still aren't valid we'll go into the if-statement again,
| add a listener, come back, etc, until they are valid.
|
if (boundsInScroll.getHeight() < 1.0)
{
JavaFXUtil.addSelfRemovingListener(node.boundsInLocalProperty(), x -> ensureNodeVisible(node));
return;
}
final double MIN = 75;
final Duration SCROLL_TIME = Duration.millis(150);
if (boundsInScroll.getMaxY() < MIN) {
scrollTo(node, -MIN, SCROLL_TIME);
}
else if (boundsInScroll.getMinY() > scroll.heightProperty().get() - MIN) {
scrollTo(node, -(scroll.heightProperty().get() - MIN), SCROLL_TIME);
}
}
private Bounds boundsInScroll(Node node)
{
Bounds local = node.getBoundsInLocal();
Bounds scene = node.localToScene(local);
if (local.getMinY() != scene.getMinY() && local.getMaxY() != scene.getMaxY())
return scroll.sceneToLocal(scene);
else{ return null;
}
}
private Bounds boundsInScrollContent(Node node)
{
return scrollContent.sceneToLocal(node.localToScene(node.getBoundsInLocal()));
}
@Override
@OnThread(Tag.FX)
public boolean isLoading()
{
return loading;
}
@Override
public boolean isEditable()
{
return viewProperty.get() != View.JAVA_PREVIEW;
}
@Override
public void setupSuggestionWindow(Stage window)
{
JavaFXUtil.addFocusListener(window, focused ->
getParent().scheduleUpdateCatalogue(FrameEditorTab.this, null, focused ? CodeCompletionState.SHOWING : CodeCompletionState.NOT_POSSIBLE, false, View.NORMAL, Collections.emptyList(), Collections.emptyList())
);
}
@Override
@OnThread(Tag.FXPlatform)
public Pane getDragTargetCursorPane()
{
return getParent().getDragCursorPane();
}
@OnThread(Tag.FXPlatform)
public void compiled()
{
if (getTopLevelFrame() != null)
getTopLevelFrame().getAllFrames().forEach(Frame::compiled);
updateDisplays();
}
@Override
public void ensureImportsVisible()
{
if (getTopLevelFrame() != null)
getTopLevelFrame().ensureImportCanvasShowing();
}
@OnThread(Tag.FXPlatform)
p.public void ignoreEdits(FXPlatformRunnable during)
{
loading = true;
during.run();
loading = false;
}
@OnThread(Tag.FXPlatform)
public void updateCatalog(FrameCursor f)
{
getParent().scheduleUpdateCatalogue(FrameEditorTab.this, f, CodeCompletionState.NOT_POSSIBLE, !selection.getSelected().isEmpty(), getView(), Collections.emptyList(), Collections.emptyList());
}
@OnThread(Tag.FXPlatform)
List<Menu> getMenus()
{
return menuManager.getMenus();
}
@Override
public WindowOverlayPane getWindowOverlayPane()
{
return windowOverlayPane;
}
@Override
public CodeOverlayPane getCodeOverlayPane()
{
return codeOverlayPane;
}
@Override
public Observable getObservableScroll()
{
return observableScroll;
}
@Override
public DoubleExpression getObservableViewportHeight()
{
return viewportHeight;
}
View getView()
{
return viewProperty.get();
}
@Override
public ReadOnlyObjectProperty viewProperty()
{
return viewProperty;
}
@Override
public FrameCursor getFocusedCursor()
{
if (focusedItem.get() == null)
return null;
else{ return focusedItem.get().getCursor();
}
}
public Observable focusedItemObservable()
{
return focusedItem;
}
@Override
@OnThread(Tag.FXPlatform)
public void updateErrorOverviewBar()
{
JavaFXUtil.runAfter(Duration.millis(500), () -> updateErrorOverviewBar(false));
}
@Override
public Paint getHighlightColor()
{
return contentRoot.cssHighlightColorProperty().get();
}
@SuppressWarnings("unchecked")
public void focusMethod(String methodName)
{
if (getTopLevelFrame() != null) {
for (NormalMethodFrame normalMethodFrame :
(List<NormalMethodFrame>) getTopLevelFrame().getMethods())
{
if (normalMethodFrame.getName().equals(methodName)) {
normalMethodFrame.focusName();
}
}
}
else {
Debug.message("focusMethod @ FrameEditorTab: " + "class frame is null!" );
}
}
@OnThread(Tag.FXPlatform)
public void setParent(FXTabbedEditor parent, boolean partOfMove)
{
if (!partOfMove && parent != null)
editor.getWatcher().recordOpen();
else if (!partOfMove && parent == null)
editor.getWatcher().recordClose();
this.parent.set(parent);
}
@OnThread(Tag.FXPlatform)
FXTabbedEditor getParent()
{
return parent.get();
}
public ObservableValue windowProperty()
{
return parent;
}
Project getProject()
{
return project;
}
@Override
public ObservableStringValue windowTitleProperty()
{
return nameProperty();
}
@Override
String getWebAddress()
{
return null;
}
@Override
@OnThread(Tag.FXPlatform)
public void notifySelected()
{
editor.getWatcher().recordSelected();
}
@Override
@OnThread(Tag.FXPlatform)
public void notifyUnselected()
{
cancelFreshState();
}
@Override
public FrameEditor getFrameEditor()
{
return editor;
}
@Override
@OnThread(Tag.FXPlatform)
public void recordEdits(StrideEditReason reason)
{
editor.recordEdits(reason);
}
@Override
@OnThread(Tag.FXPlatform)
public void recordCodeCompletionStarted(SlotFragment element, int index, String stem, int codeCompletionId)
{
recordEdits(StrideEditReason.FLUSH);
editor.getWatcher().recordCodeCompletionStarted(null, null,
getLocationMap().locationFor(element), index, stem, codeCompletionId);
}
@Override
@OnThread(Tag.FXPlatform)
public void recordCodeCompletionEnded(SlotFragment element, int index, String stem, String replacement, int codeCompletionId)
{
recordEdits(StrideEditReason.CODE_COMPLETION);
editor.getWatcher().recordCodeCompletionEnded(null, null,
getLocationMap().locationFor(element), index, stem, replacement, codeCompletionId);
}
@Override
@OnThread(Tag.FXPlatform)
public void recordUnknownCommandKey(Frame enclosingFrame, int cursorIndex, char key)
{
if (key < 32 || key == 127)
return;
recordEdits(StrideEditReason.FLUSH);
editor.getWatcher().recordUnknownCommandKey(getXPath(enclosingFrame), cursorIndex, key);
}
@Override
@OnThread(Tag.FXPlatform)
public void recordShowHideFrameCatalogue(boolean show, FrameCatalogue.ShowReason reason)
{
FrameCursor focusedCursor = getFocusedCursor();
editor.getWatcher().recordShowHideFrameCatalogue(
focusedCursor != null ? getXPath(focusedCursor.getEnclosingFrame()) : null,
focusedCursor != null ? focusedCursor.getCursorIndex() : -1,
show,
reason);
}
@Override
@OnThread(Tag.FX)
public ImageView makeClassImageView()
{
return makeClassGraphicIcon(imageProperty, 48, true);
}
| Records the reason, the old view and the current one, when switching between different show modes in the Stride editor.
|
| @param oldView The old view mode that been switch from.
| @param newView The new view mode that been switch to.
| @param reason The user interaction which triggered the change.
|
@OnThread(Tag.FXPlatform)
public void recordViewChange(View oldView, View newView, Frame.ViewChangeReason reason)
{
FrameCursor focusedCursor = getFocusedCursor();
editor.getWatcher().recordViewModeChange(
focusedCursor != null ? getXPath(focusedCursor.getEnclosingFrame()) : null,
focusedCursor != null ? focusedCursor.getCursorIndex() : -1,
oldView,
newView,
reason);
}
@Override
@OnThread(Tag.FXPlatform)
public void recordErrorIndicatorShown(int identifier)
{
editor.getWatcher().recordShowErrorIndicators(Collections.singletonList(identifier));
}
@Override
public void showUndoDeleteBanner(int totalEffort)
{
if (totalEffort >= 15 && !undoBannerShowing)
{
undoBannerShowing = true;
CircleCountdown countdown = new CircleCountdown(40, Color.BLACK, Duration.seconds(15));
TextFlow bannerText = new TextFlow();
BorderPane banner = new BorderPane(bannerText);
BorderPane.setAlignment(bannerText, Pos.TOP_LEFT);
final FrameState restoreTarget = undoRedoManager.getCurrent();
undoRedoManager.addListener(new FXRunnable() {
@Override
public void run()
{
if (!undoRedoManager.canUndoToReference(restoreTarget, 2))
{
undoRedoManager.removeListener(this);
countdown.stop();
bannerPane.getChildren().remove(banner);
undoBannerShowing = false;
}
}
});
JavaFXUtil.addStyleClass(bannerText, "banner-undo-delete-text");
Button undoButton = new Button(Config.getString("frame.undobanner.button"));
JavaFXUtil.addStyleClass(undoButton, "banner-undo-delete-button");
undoButton.setOnAction(e -> {
while (undoRedoManager.canUndoToReference(restoreTarget, 3))
undo();
}
});
bannerText.getChildren().addAll(new Text(Config.getString("frame.undobanner.text") + " "), undoButton);
Button close = new Button(Config.getString("frame.undobanner.close"));
JavaFXUtil.addStyleClass(close, "banner-undo-delete-close");
countdown.addOnFinished(() -> {
bannerPane.getChildren().remove(banner);
undoBannerShowing = false;
});
banner.setRight(new VBox(close, countdown));
JavaFXUtil.addStyleClass(banner, "banner-undo-delete");
banner.styleProperty().bind(new ReadOnlyStringWrapper("-fx-font-size:").concat(PrefMgr.strideFontSizeProperty().asString()).concat("pt;"));
bannerPane.getChildren().add(0, banner);
close.setOnAction(e -> {
countdown.stop();
bannerPane.getChildren().remove(banner);
undoBannerShowing = false;
focusWhenShown();
});
}
}
| It returns the path for a code frame. If it's not a code frame, then null is returned.
|
| @param frame The frame that its path is needed.
| @return If the frame is a code frame, an XPath String identifying the location of that frame.
| Otherwise, null.
|
@SuppressWarnings("unchecked")
private String getXPath(Frame frame)
{
return (frame instanceof CodeFrame)
? getLocationMap().locationFor(((CodeFrame<? extends CodeElement>)frame).getCode())
: null;
}
| It invokes the buildLocationMap method in the LocatableElement to build a location map.
| This location map is dependent on the latest version of the code, and will invalid as soon as the code changes in future.
|
| @return A map from JavaFragment to XPath String identifying the location of that fragment.
|
private LocationMap getLocationMap()
{
return getTopLevelFrame().getCode().toXML().buildLocationMap();
}
| Set the header image (in the tab header)
| @param image The image to use (any size).
|
protected void setHeaderImage(Image image)
{
imageProperty.set(image);
}
}
top,
use,
map,
class FrameEditorTab
. FrameEditorTab
. blockSkipModifierLabel
. hasBlockSkipModifierPressed
. getFutureList
. importsUpdated
. initialiseFX
. requestFocus
. run
. cleanup
. getTopLevelFrame
. withTopLevelFrame
. redrawExecHistory
. addExtends
. removeExtendsClass
. addImplements
. removeExtendsOrImplementsInterface
. saveAfterAutomatedEdit
. toString
. showDebuggerControls
. hideDebuggerControls
. WeakFontSizeUpdater
. changed
. addWeakFontSizeUpdater
. resetFontSize
. searchLink
. accept
. if
. if
. if
. searchMethodLink
. enableCycleBirdseyeView
. disableBirdseyeView
. disableBirdseyeView
. enableJavaPreview
. disableJavaPreview
. changeViewMode
. setupAnimateViewTo
. cheatSheetShowingProperty
. focusWhenShown
. cancelFreshState
. dragEndTab
. performDrag
. draggedToTab
. draggedToAnotherTab
. clickNearestCursor
. getDictionary
. setupFrameCursor
. accept
. hideError
. updateFocusedDisplay
. setupFrame
. createCursor
. getSource
. regenerateCode
. flagErrorsAsOld
. removeOldErrors
. updateErrorOverviewBar
. getAllErrors
. modifiedFrame
. setWindowVisible
. isWindowVisible
. withCompletions
. withSuperConstructors
. getThisConstructors
. beginRecordingState
. endRecordingState
. getCurrentState
. undo
. redo
. updateClassContents
. scrollTo
. getSelection
. saved
. withAccessibleMembers
. regenerateAndReparse
. afterRegenerateAndReparse
. updateDisplays
. updateFontSize
. decreaseFontSize
. increaseFontSize
. getFontCSS
. getFontSize
. calculateBirdseyeRectangle
. getPrimitiveTypes
. withTypes
. addAllToMap
. withTypes
. removeImports
. insertAppendMethod
. insertMethodCallInConstructor
. insertElementIntoMethod
. insertMethodContentsIntoMethodFrame
. insertMethodElementAtTheEnd
. getAllImportedTypes
. getImportedTypes
. getImportSuggestions
. addImport
. getAvailableFilenames
. getFile
. getType
. getPreview
. getShortcuts
. nextError
. registerStackHighlight
. initialisedProperty
. nameProperty
. setupFocusableSlotComponent
. setupFocusable
. run
. ensureNodeVisible
. boundsInScroll
. boundsInScrollContent
. isLoading
. isEditable
. setupSuggestionWindow
. getDragTargetCursorPane
. compiled
. ensureImportsVisible
. ignoreEdits
. updateCatalog
. getWindowOverlayPane
. getCodeOverlayPane
. getObservableScroll
. getObservableViewportHeight
. viewProperty
. getFocusedCursor
. focusedItemObservable
. updateErrorOverviewBar
. getHighlightColor
. focusMethod
. setParent
. windowProperty
. windowTitleProperty
. notifySelected
. notifyUnselected
. getFrameEditor
. recordEdits
. recordCodeCompletionStarted
. recordCodeCompletionEnded
. recordUnknownCommandKey
. recordShowHideFrameCatalogue
. makeClassImageView
. recordViewChange
. recordErrorIndicatorShown
. showUndoDeleteBanner
. run
. getXPath
. CodeElement>)frame).getCode
. getLocationMap
. setHeaderImage
3591 neLoCode
+ 39 LoComm