package bluej.stride.slots;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import javafx.beans.property.ReadOnlyDoubleWrapper;
import javafx.collections.FXCollections;
import javafx.geometry.Bounds;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.paint.Color;
import bluej.stride.framedjava.ast.links.PossibleLink;
import bluej.stride.generic.FrameContentRow;
import bluej.stride.generic.InteractionManager;
import threadchecker.OnThread;
import threadchecker.Tag;
import bluej.editor.stride.CodeOverlayPane;
import bluej.stride.framedjava.ast.NameDefSlotFragment;
import bluej.stride.framedjava.elements.CodeElement;
import bluej.stride.framedjava.frames.CodeFrame;
import bluej.stride.framedjava.frames.FrameHelper;
import bluej.stride.framedjava.slots.ExpressionSlot;
import bluej.stride.framedjava.slots.StructuredSlot.PlainVarReference;
import bluej.stride.generic.Frame;
import bluej.stride.generic.SuggestedFollowUpDisplay;
import bluej.utility.Utility;
import bluej.utility.javafx.FXSupplier;
import bluej.utility.javafx.JavaFXUtil;
public class VariableNameDefTextSlot extends TextSlot<NameDefSlotFragment>{
private FXSupplier<Boolean> shouldRename;
private Canvas allUsesCanvas;
private Button hideAllUsesButton;
private SlotValueListener spaceCharactersListener = (slot, oldValue, newValue, parent) -> newValue.chars().noneMatch(Character::isSpaceChar);
public <T extends Frame & CodeFrame<? extends CodeElement>>
VariableNameDefTextSlot(InteractionManager editor, T frameParent, FrameContentRow row, String stylePrefix)
{
super(editor, frameParent, frameParent, row, null, stylePrefix, Collections.emptyList());
this.shouldRename = () -> true;
addValueListener(spaceCharactersListener);
}
public <T extends Frame & CodeFrame<? extends CodeElement>>
VariableNameDefTextSlot(InteractionManager editor, T frameParent, FrameContentRow row, FXSupplier<Boolean> shouldRename, String stylePrefix)
{
super(editor, frameParent, frameParent, row, null, stylePrefix, Collections.emptyList());
this.shouldRename = shouldRename;
addValueListener(spaceCharactersListener);
}
public VariableNameDefTextSlot(InteractionManager editor, Frame frameParent,
CodeFrame<? extends CodeElement> codeFrameParent, FrameContentRow row, String stylePrefix)
{
super(editor, frameParent, codeFrameParent, row, null, stylePrefix, Collections.emptyList());
this.shouldRename = () -> true;
addValueListener(spaceCharactersListener);
}
@Override
public NameDefSlotFragment createFragment(String content)
{
return new NameDefSlotFragment(content, this);
}
@Override
@OnThread(Tag.FXPlatform)
public void valueChangedLostFocus(String oldVal, String newVal)
{
if (!oldVal.equals(newVal) && !oldVal.isEmpty() && !newVal.isEmpty() && !shouldRename.get())
{
VariableRefFinder renamer = findVarReferences(oldVal);
if (!renamer.refs.isEmpty())
{
SuggestedFollowUpDisplay disp = new SuggestedFollowUpDisplay(editor, "Do you want to rename all uses of old variable \"" + oldVal + "\" to use \"" + newVal + "\" instead?", () -> renamer.refs.forEach(r -> r.rename.accept(newVal)));
disp.showBefore(getNode());
}
}
}
private class VariableRefFinder implements BiConsumer<Map<String, List<Frame>>, Frame>
{
private final String name;
private final ArrayList<PlainVarReference> refs = new ArrayList<>();
VariableRefFinder(String name)
{
this.name = name;
}
@Override
@OnThread(value = Tag.FX, ignoreParent = true)
public void accept(Map<String, List<Frame>> scopes, Frame f)
{
for (ExpressionSlot e : Utility.iterableStream(f.getEditableSlotsDirect().map(EditableSlot::asExpressionSlot).filter(x -> x != null)))
{
List<Frame> oldScope = scopes.get(name);
if (oldScope == null || oldScope.size() == 0)
{
refs.addAll(e.findPlainVarReferences(name));
}
}
}
}
private VariableRefFinder findVarReferences(String name)
{
VariableRefFinder renamer = new VariableRefFinder(name);
FrameHelper.processVarScopesAfter(frameParent.getParentCanvas(), frameParent, renamer);
return renamer;
}
@Override
protected Map getExtraContextMenuItems()
{
return Collections.singletonMap(TopLevelMenu.VIEW, new MenuItems(FXCollections.observableArrayList()) {
public void onShowing()
{
if (allUsesCanvas != null)
{
items.setAll(MenuItemOrder.SHOW_HIDE_USES.item(JavaFXUtil.makeMenuItem("Hide uses of \"" + getText() + "\"", () -> hideUsesOverlay(), null)));
}
else
{
VariableRefFinder refFinder = findVarReferences(getText());
if (!refFinder.refs.isEmpty())
{
items.setAll(MenuItemOrder.SHOW_HIDE_USES.item(JavaFXUtil.makeMenuItem("See uses of \"" + getText() + "\"", () -> showUsesOverlay(refFinder.refs), null)));
}
else{ items.clear();
}
}
}
});
}
@OnThread(Tag.FXPlatform)
private void showUsesOverlay(List<PlainVarReference> refs)
{
hideUsesOverlay();
CodeOverlayPane overlay = editor.getCodeOverlayPane();
allUsesCanvas = overlay.addFullSizeCanvas();
GraphicsContext g = allUsesCanvas.getGraphicsContext2D();
g.setStroke(Color.BLUE);
g.setLineWidth(2.0);
final double LEFT = 10.0;
List<Bounds> boundsList = Utility.mapList(refs, ref -> ref.refNode.localToScene(ref.refNode.getBoundsInLocal()));
Bounds ourSceneBounds = getNode().localToScene(getNode().getBoundsInLocal());
boundsList.add(ourSceneBounds);
boundsList.sort((a, b) -> Double.compare(b.getMinX(), a.getMinX()));
List<Double> yCoords = Utility.mapList(boundsList, sceneBounds -> {
double y = overlay.sceneYToCodeOverlayY(sceneBounds.getMinY());
g.clearRect(sceneBounds.getMinX(), y, sceneBounds.getWidth(), sceneBounds.getHeight());
g.strokeRect(sceneBounds.getMinX(), y, sceneBounds.getWidth(), sceneBounds.getHeight());
double midY = y + (sceneBounds.getHeight() / 2.0);
g.strokeLine(LEFT, midY, sceneBounds.getMinX(), midY);
return midY;
});
g.strokeLine(LEFT, yCoords.stream().min(Double::compare).get(), LEFT, yCoords.stream().max(Double::compare).get());
Canvas hide = new Canvas(10, 10);
GraphicsContext g2 = hide.getGraphicsContext2D();
g2.setStroke(Color.RED);
g2.strokeLine(1, 1, hide.getWidth() - 2.0, hide.getHeight() - 2.0);
g2.strokeLine(hide.getWidth() - 2.0, 1, 1, hide.getHeight() - 2.0);
hideAllUsesButton = new Button("", hide);
JavaFXUtil.addStyleClass(hideAllUsesButton, "hide-all-uses-button");
hideAllUsesButton.setOnAction(e -> hideUsesOverlay());
overlay.addOverlay(hideAllUsesButton, getNode(), new ReadOnlyDoubleWrapper(- ourSceneBounds.getMinX() + LEFT - hide.getWidth()/2.0), null);
}
@OnThread(Tag.FXPlatform)
private void hideUsesOverlay()
{
if (allUsesCanvas != null)
{
CodeOverlayPane overlayPane = editor.getCodeOverlayPane();
overlayPane.removeOverlay(allUsesCanvas);
overlayPane.removeOverlay(hideAllUsesButton);
allUsesCanvas = null;
hideAllUsesButton = null;
}
}
@Override
public List extends PossibleLink> findLinks()
{
return Collections.emptyList();
}
}
. VariableNameDefTextSlot
. createFragment
. valueChangedLostFocus
. accept
. findVarReferences
. getExtraContextMenuItems
. onShowing
. showUsesOverlay
. hideUsesOverlay
. findLinks
262 neLoCode
+ 0 LoComm