package bluej.editor.stride;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import bluej.collect.StrideEditReason;
import bluej.stride.framedjava.frames.StrideCategory;
import bluej.stride.generic.ExtensionDescription.ExtensionSource;
import bluej.stride.generic.InteractionManager;
import bluej.utility.javafx.FXPlatformRunnable;
import bluej.utility.javafx.binding.ConcatListBinding;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.geometry.Rectangle2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.Label;
import javafx.scene.control.OverrunStyle;
import javafx.scene.effect.ColorAdjust;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import bluej.Config;
import bluej.editor.stride.FXTabbedEditor.CodeCompletionState;
import bluej.stride.framedjava.ast.Loader;
import bluej.stride.framedjava.elements.CodeElement;
import bluej.stride.framedjava.frames.CodeFrame;
import bluej.stride.generic.CanvasParent;
import bluej.stride.generic.ExtensionDescription;
import bluej.stride.generic.Frame;
import bluej.stride.generic.FrameCursor;
import bluej.stride.generic.FrameDictionary;
import bluej.stride.generic.FrameDictionary.Entry;
import bluej.stride.generic.FrameFactory;
import bluej.utility.Utility;
import bluej.utility.javafx.FXBiConsumer;
import bluej.utility.javafx.FXRunnable;
import bluej.utility.javafx.JavaFXUtil;
import threadchecker.OnThread;
import threadchecker.Tag;


| The pop-out catalogue displayed on the right-hand side with frames and shortcuts | public class FrameCatalogue extends VBox{
| A callback to update an entry (or entries) in the frame catalogue | private static interface Updater {
| Called to update the frame catalogue entries based on the current editing state. | | @param c The currently focused frame cursor (or null if focus is not on a frame cursor) | @param codeCompletion Whether code completion is possible, impossible, or currently showing | @param hasFrameSelection Whether there is a frame selection | @param viewMode The current view mode (Normal, Java, Birdseye w/o documentation) | public void update(FrameCursor c, CodeCompletionState codeCompletion, boolean hasFrameSelection, Frame.View viewMode); }
| A list of update callbacks. Will be called in order. | private final List<Updater> catalogueUpdate = new ArrayList<>();
| A list of extension key items for frames. Updated when frame cursor focus changes. | private final ObservableList<Node> extensionItems = FXCollections.observableArrayList();
| A list of hints (not commands, but example code) to show. | private final ObservableList<Node> hintItems = FXCollections.observableArrayList();
| The current callback to cancel the next update of the frame catalogue. | See the scheduleUpdateCatalogue() method | private FXPlatformRunnable cancelUpdateCatalogue;
| Keep track of which frame cursor is focused, if any. | private FrameCursor currentCursor;
| Whether the catalogue has been initialised. | See the fillCatalogue method. | private boolean filled = false; public final static double CATALOGUE_FRAME_WIDTH = 220.0;
| An enum to list all the cases in which the frame catalogue will be expanded. | This is used for the DataCollection. | public enum ShowReason { UNKNOWN_FRAME_COMMAND("unknown_frame_command"), MENU_OR_SHORTCUT("menu_or_shortcut"), ARROW("arrow"), PROPERTIES("properties"); private final String text; ShowReason(String text) { this.text = text; } public String getText() { return text; } } FrameCatalogue() { JavaFXUtil.addStyleClass(this, "catalogue"); setFocusTraversable(false); }
| Fills the catalogue. We can't call this from the constructor because the FrameCatalogue | is created with the FXTabbedEditor, but we need to borrow a FrameEditorTab reference in order | to mock up the frames for the preview pictures. So we only fill the catalogue the first time | that an update is requested by an editor. | @OnThread(Tag.FXPlatform) private void fillCatalogue(FrameEditorTab editor) { if (filled || editor == null) return; filled = true; ObservableList<Node> standardItems = FXCollections.observableArrayList(); Node shortcutHeader = makeSectionHeader(Config.getString("frame.catalogue.shortcuts")); standardItems.add(shortcutHeader);
| | |Node enterFrame = makeTextItem("\u2192", "Edit frame beneath", false); JavaFXUtil.setPseudoclass("bj-catalogue-special", true, enterFrame); standardItems.add(enterFrame); */ catalogueUpdate.add((c, code, hasSelection, birdseye) -> { //boolean show = c != null && c.getFrameAfter() != null && c.getFrameAfter().getEditableSlotsDirect().findFirst().isPresent(); | |//enterFrame.setVisible(show); | |//enterFrame.setManaged(show); | |// We only show "Shortcuts" header when frame cursor is null (and thus we have things like // code completion shortcuts to show) or it's non-null and there's no selection -- shortcuts // for the selection are short under selectionheader, below | |shortcutHeader.setVisible(c == null /*|| show ; shortcutHeader.setManaged(c == null /*|| show ; }); Node commandHeader = makeSectionHeader(Config.getString("frame.catalogue.commands")); standardItems.add(commandHeader); catalogueUpdate.add((c, code, hasSelection, birdseye) -> { commandHeader.setVisible(c != null && !hasSelection); commandHeader.setManaged(c != null && !hasSelection); }); editor.ignoreEdits(() -> { FrameDictionary<StrideCategory> dictionary = editor.getDictionary(); BorderPane p = new BorderPane(); p.setMinWidth(CATALOGUE_FRAME_WIDTH); p.setPrefWidth(CATALOGUE_FRAME_WIDTH); p.setMaxWidth(CATALOGUE_FRAME_WIDTH); Scene temp = new Scene(p); Config.addEditorStylesheets(temp); Comparator<Entry<StrideCategory>> comparator = Comparator.<Entry<StrideCategory>, StrideCategory>comparing(Entry::getCategory).thenComparing(e -> getDisplayShortcut(e.getShortcuts())); final SnapshotParameters params = new SnapshotParameters(); params.setFill(Color.TRANSPARENT); for (Entry<StrideCategory> e : Utility.iterableStream(dictionary.getAllBlocks().stream().filter(b -> b.isShowingInCatalogue()).sorted(comparator))) { Frame f = e.getFactory().createBlock(editor); p.setCenter(f.getNode()); WritableImage image = p.snapshot(params, null); AnchorPane item = new AnchorPane(); JavaFXUtil.addStyleClass(item, "catalogue-item"); JavaFXUtil.setPseudoclass("bj-catalogue-clickable", true, item); item.setMinWidth(CATALOGUE_FRAME_WIDTH); item.setMaxWidth(CATALOGUE_FRAME_WIDTH); item.setMinHeight(30.0); JavaFXUtil.initializeCustomTooltipCatalogue(editor.getParent(), item, "Click, or press \'" + keyTooltipName(e.getShortcuts()) + "\' to insert " + e.getName().toLowerCase() + " frame", Duration.millis(1500)); ImageView imageView = new ImageView(image); imageView.setPreserveRatio(true); Node header = f.getHeaderItems().findFirst().flatMap(h -> h.getComponents().stream().findFirst()).orElse(null); if (header != null) { double headerY = header.localToScene(header.getBoundsInLocal()).getMinY(); imageView.setViewport(new Rectangle2D(0, headerY, 75, 18)); } else { imageView.setViewport(new Rectangle2D(0, 0, 75, 18)); } imageView.setEffect(new ColorAdjust(0, 0.0, 0.2, 0)); AnchorPane.setBottomAnchor(imageView, 0.0); AnchorPane.setRightAnchor(imageView, 0.0); item.getChildren().add(imageView); Pane keyAndName = getKeyAndName(Collections.singletonList(getDisplayShortcut(e.getShortcuts())), e.getName(), true); item.getChildren().addAll(keyAndName); setupClick(item, e.getFactory()); f.cleanup(); item.setVisible(false); item.setManaged(false); standardItems.add(item); catalogueUpdate.add((c, code, hasSelection, birdseye) -> { boolean show = c != null && c.canInsert() && c.check().canInsert(e.getCategory()) && (!hasSelection || e.isValidOnSelection()); item.setVisible(show); item.setManaged(show); if (hasSelection && show) { commandHeader.setVisible(true); commandHeader.setManaged(true); } }); } Node hbox = makeTextItem(Arrays.asList("Ctrl", "Space"), Config.getString("frame.catalogue.codecompletion"), false); JavaFXUtil.setPseudoclass("bj-catalogue-special", true, hbox); standardItems.add(hbox); catalogueUpdate.add((c, codeCompletion, selection, birdseye) -> { hbox.setVisible(codeCompletion == CodeCompletionState.POSSIBLE); hbox.setManaged(codeCompletion == CodeCompletionState.POSSIBLE); }); hbox.setVisible(false); hbox.setManaged(false); FXBiConsumer<String, String> addCodeCompletionShortcut = (shortcut, actionName) -> { Node content = makeTextItem(Collections.singletonList(shortcut), actionName, false); standardItems.add(content); catalogueUpdate.add((c, codeCompletion, selection, birdseye) -> { content.setVisible(codeCompletion == CodeCompletionState.SHOWING); content.setManaged(codeCompletion == CodeCompletionState.SHOWING); }); }; addCodeCompletionShortcut.accept("Esc", Config.getString("frame.catalogue.codecompletion.hide")); addCodeCompletionShortcut.accept("\u2191", Config.getString("frame.catalogue.codecompletion.up")); addCodeCompletionShortcut.accept("\u2193", Config.getString("frame.catalogue.codecompletion.down")); addCodeCompletionShortcut.accept("\u21B5", Config.getString("frame.catalogue.codecompletion.select")); FXBiConsumer<String, String> addBirdseyeShortcut = (shortcut, actionName) -> { Node content = makeTextItem(Collections.singletonList(shortcut), actionName, false); standardItems.add(content); catalogueUpdate.add((c, codeCompletion, selection, view) -> { content.setVisible(view.isBirdseye()); content.setManaged(view.isBirdseye()); }); }; addBirdseyeShortcut.accept("Esc", Config.getString("frame.catalogue.birdseye.exit")); addBirdseyeShortcut.accept("\u2191", Config.getString("frame.catalogue.birdseye.up")); addBirdseyeShortcut.accept("\u2193", Config.getString("frame.catalogue.birdseye.down")); addBirdseyeShortcut.accept("\u21B5", Config.getString("frame.catalogue.birdseye.select")); Node content = makeTextItem(Collections.singletonList("Esc"), Config.getString("frame.catalogue.birdseye.exit"), false); standardItems.add(content); catalogueUpdate.add((c, codeCompletion, selection, view) -> { content.setVisible(view == Frame.View.JAVA_PREVIEW); content.setManaged(view == Frame.View.JAVA_PREVIEW); }); }); ConcatListBinding.bind(getChildren(), FXCollections.observableArrayList(standardItems, extensionItems, hintItems)); } private Pane getKeyAndName(List<String> shortcutKeys, String title, boolean showingPreview) { HBox keysHBox = new HBox(shortcutKeys.stream().map(shortcut -> { Label keyLabel = new Label(shortcut); keyLabel.setMouseTransparent(true); JavaFXUtil.addStyleClass(keyLabel, "catalogue-key"); if (shortcut.length() > 1) { JavaFXUtil.setPseudoclass("bj-wide", true, keyLabel); keyLabel.setTextOverrun(OverrunStyle.CLIP); } return (Node)keyLabel; }).collect(Utility.intersperse(() -> (Node)new Label("+"))).toArray(new Node[0])); keysHBox.setSpacing(1.0); keysHBox.setFillHeight(false); keysHBox.setAlignment(Pos.CENTER_LEFT); Label name = new Label(title); name.setWrapText(true); name.setMaxWidth(showingPreview ? 100.0 : 160.0); name.setMouseTransparent(true); Pane keyAndName; if (shortcutKeys.size() == 1) { keyAndName = new HBox(keysHBox, name) { { setSpacing(5.0); setFillHeight(false); setAlignment(Pos.CENTER_LEFT); } }; } else { keyAndName = new VBox(keysHBox, name) { { setSpacing(5.0); } }; name.setStyle("-fx-padding: 0 0 0 15;"); } AnchorPane.setLeftAnchor(keyAndName, 0.0); AnchorPane.setTopAnchor(keyAndName, 0.0); AnchorPane.setBottomAnchor(keyAndName, 0.0); return keyAndName; } private void setupClick(Node item, FrameFactory<? extends Frame> factory) { item.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> { if (currentCursor != null) { InteractionManager editor = currentCursor.getEditor(); editor.recordEdits(StrideEditReason.FLUSH); FrameSelection selection = currentCursor.getEditor().getSelection(); if (selection.getSelected().isEmpty()) { Frame f = factory.createBlock(currentCursor.getEditor()); currentCursor.insertBlockAfter(f); f.markFresh(); f.focusWhenJustAdded(); editor.recordEdits(StrideEditReason.SINGLE_FRAME_INSERTION_CHEAT); } else { List<Frame> selected = selection.getSelected(); List<Frame> selectedCopy = Utility.mapList(selected, f -> Loader.loadElement(((CodeFrame<CodeElement>)f).getCode().toXML()).createFrame(currentCursor.getEditor())); Frame newFrame = factory.createBlock(currentCursor.getEditor(), selectedCopy); currentCursor.insertBlockBefore(newFrame); selected.forEach(f -> f.getParentCanvas().removeBlock(f)); selection.clear(); newFrame.markFresh(); newFrame.focusWhenJustAdded(); editor.recordEdits(StrideEditReason.SELECTION_WRAP_CHEAT); } } e.consume(); }); } private void setupClick(Node item, FrameCursor c, FXRunnable action) { item.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> { if (currentCursor == c) { action.run(); } e.consume(); }); } private String getDisplayShortcut(String shortcut) { if (shortcut.equals("\n")) return "\u21B5"; else if (shortcut.equals("\b")) return "\u21A4"; else if (shortcut.equals(" ")) return "\u02FD"; else{ return shortcut; } } private String keyTooltipName(String shortcut) { switch (shortcut) { case " ": return "space"; case "\n": return "return"; case "\b": return "backspace"; default: return shortcut; } } public static class Hint { public final String exampleCode; public final String explanation; public Hint(String exampleCode, String explanation) { this.exampleCode = exampleCode; this.explanation = explanation; } } @OnThread(Tag.FXPlatform) p.public void scheduleUpdateCatalogue(FrameEditorTab editor, FrameCursor c, CodeCompletionState codeCompletion, boolean selection, Frame.View viewMode, List<ExtensionDescription> altExtensions, List<Hint> hints) { currentCursor = c; if (cancelUpdateCatalogue != null) { cancelUpdateCatalogue.run(); } cancelUpdateCatalogue = JavaFXUtil.runAfter(Duration.millis(500), () -> { if (getScene().getWindow().isFocused() || codeCompletion == CodeCompletionState.SHOWING) { fillCatalogue(editor); catalogueUpdate.forEach(updater -> updater.update(c, codeCompletion, selection, viewMode)); updateExtensions(selection ? null : c, altExtensions); replaceHints(hints); } }); } private void updateExtensions(FrameCursor c, List<ExtensionDescription> altExtensions) { extensionItems.clear(); if (c != null) { final Frame frameBefore = c.getFrameBefore(); final Frame frameAfter = c.getFrameAfter(); Set<Character> keysAlreadyUsed = new HashSet<>(); CanvasParent parent = c.getParentCanvas().getParent(); if (parent != null && c.canInsert()) { for (ExtensionDescription ext : parent.getAvailableExtensions(c.getParentCanvas(), c)) { if (!keysAlreadyUsed.contains(ext.getShortcutKey()) && ext.validFor(frameBefore == null ? ExtensionSource.INSIDE_FIRST : ExtensionSource.INSIDE_LATER) && ext.showInCatalogue()) { Node item = makeTextItem(Collections.singletonList(getDisplayShortcut("" + ext.getShortcutKey())), ext.getDescription(), true); setupClick(item, c, ext::activate); extensionItems.add(item); keysAlreadyUsed.add(ext.getShortcutKey()); } } } if (frameAfter != null && frameAfter.isFrameEnabled()) { for (ExtensionDescription ext : frameAfter.getAvailableExtensions(null, null)) { if (!keysAlreadyUsed.contains(ext.getShortcutKey()) && ext.validFor(ExtensionSource.BEFORE) && ext.showInCatalogue()) { Node item = makeTextItem(Collections.singletonList(getDisplayShortcut("" + ext.getShortcutKey())), ext.getDescription(), true); setupClick(item, c, ext::activate); extensionItems.add(item); keysAlreadyUsed.add(ext.getShortcutKey()); } } } if (frameBefore != null && frameBefore.isFrameEnabled()) { for (ExtensionDescription ext : frameBefore.getAvailableExtensions(null, null)) { if (!keysAlreadyUsed.contains(ext.getShortcutKey()) && ext.validFor(ExtensionSource.AFTER) && ext.showInCatalogue()) { Node item = makeTextItem(Collections.singletonList(getDisplayShortcut("" + ext.getShortcutKey())), ext.getDescription(), true); setupClick(item, c, ext::activate); extensionItems.add(item); keysAlreadyUsed.add(ext.getShortcutKey()); } } } } for (ExtensionDescription ext : altExtensions) { if (ext.validFor(ExtensionSource.MODIFIER) && ext.showInCatalogue()) { Node item = makeTextItem(Arrays.asList("Ctrl", "Shift", getDisplayShortcut("" + ext.getShortcutKey())), ext.getDescription(), true); setupClick(item, c, ext::activate); extensionItems.add(item); } } } private Node makeTextItem(List<String> shortcut, String description, boolean clickable) { AnchorPane item = new AnchorPane(); JavaFXUtil.addStyleClass(item, "catalogue-item"); JavaFXUtil.setPseudoclass("bj-catalogue-clickable", clickable, item); item.setMaxWidth(CATALOGUE_FRAME_WIDTH); item.setMinHeight(30.0); item.getChildren().add(getKeyAndName(shortcut, description, false)); return item; } private Node makeHint(Hint h) { VBox item = new VBox(); Label example = new Label(h.exampleCode); Label explanation = new Label(h.explanation); example.setWrapText(true); explanation.setWrapText(true); JavaFXUtil.addStyleClass(example, "catalogue-example-code"); JavaFXUtil.addStyleClass(explanation, "catalogue-example-description"); VBox.setMargin(explanation, new Insets(3, 0, 0, 20)); item.getChildren().addAll(example, explanation); item.setMaxWidth(CATALOGUE_FRAME_WIDTH); item.setMinHeight(30.0); JavaFXUtil.addStyleClass(example, "catalogue-example"); return item; } private final Node exampleHeader = makeSectionHeader(Config.getString("frame.catalogue.examples")); private void replaceHints(List<Hint> hints) { hintItems.setAll(Utility.mapList(hints, h -> makeHint(h))); if (!hintItems.isEmpty()) hintItems.add(0, exampleHeader); } private Node makeSectionHeader(String title) { Label l = new Label(title); JavaFXUtil.addStyleClass(l, "catalogue-header"); VBox pane = new VBox(l); JavaFXUtil.addStyleClass(pane, "catalogue-item"); JavaFXUtil.setPseudoclass("bj-catalogue-header", true, pane); pane.setMinWidth(CATALOGUE_FRAME_WIDTH); return pane; } }
top, use, map, class FrameCatalogue

top, use, map, interface FrameCatalogue . Updater

.   update
.   getText
.   fillCatalogue
.   getKeyAndName
.   setupClick
.   setupClick
.   getDisplayShortcut
.   keyTooltipName

top, use, map, class Hint

.   Hint
.   scheduleUpdateCatalogue
.   updateExtensions
.   makeTextItem
.   makeHint
.   replaceHints
.   makeSectionHeader




617 neLoCode + 28 LoComm