package bluej.stride.generic;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import bluej.Config;
import bluej.collect.StrideEditReason;
import bluej.editor.stride.FrameCatalogue;
import bluej.stride.framedjava.frames.StrideCategory;
import bluej.stride.framedjava.frames.StrideDictionary;
import bluej.stride.slots.EditableSlot.MenuItemOrder;
import javafx.animation.Animation;
import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding;
import javafx.beans.binding.NumberExpressionBase;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ReadOnlyIntegerWrapper;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.collections.FXCollections;
import javafx.geometry.Bounds;
import javafx.scene.Node;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.KeyCode;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.paint.Color;
import javafx.util.Duration;
import bluej.stride.framedjava.ast.Loader;
import bluej.stride.framedjava.elements.CodeElement;
import bluej.stride.framedjava.frames.CodeFrame;
import bluej.stride.generic.FrameDictionary.Entry;
import bluej.stride.operations.PasteFrameOperation;
import bluej.stride.slots.EditableSlot;
import bluej.utility.Debug;
import bluej.utility.Utility;
import bluej.utility.javafx.JavaFXUtil;
import bluej.utility.javafx.SharedTransition;
import threadchecker.OnThread;
import threadchecker.Tag;
| Between-block horizontal cursor placeholder/button
| @author Fraser McKay
|
public class FrameCursor
implements RecallableFocus{
public static final int FULL_HEIGHT = 5;
public static final int HIDE_HEIGHT = 0;
final int ERROR_COUNT_TRIGGER = 2;
int consecutiveErrors = 0;
private ContextMenu menu;
private final FrameCanvas parentCanvas;
private final Button node = new Button();
@OnThread(Tag.FXPlatform)
public boolean keyTyped(final InteractionManager editor, final FrameCanvas parentCanvas, char key, boolean ctrlDown)
{
if (!editor.isEditable() || !canInsert())
return false;
key = Character.toLowerCase(key);
if (key == '?')
{
boolean show = !editor.cheatSheetShowingProperty().get();
editor.cheatSheetShowingProperty().set(show);
editor.recordShowHideFrameCatalogue(show, FrameCatalogue.ShowReason.MENU_OR_SHORTCUT);
return true;
}
if (Character.isLetter(key) || Arrays.asList('/', '\\', '*', '=', '+', '-', '\n', ' ').contains(key))
{
List<Entry<?>> available = editor.getDictionary().getFramesForShortcutKey(key).stream()
.filter(t -> parentCanvas.getParent().check(parentCanvas).canInsert(t.getCategory()))
.collect(Collectors.toList());
final boolean selection = !editor.getSelection().getSelected().isEmpty();
if (false && (key == '+' || key == '-')) {
return true;
}
if (selection && editor.getSelection().executeKey(this, key))
return true;
if (!selection) {
Frame before = getFrameBefore();
if (CanvasParent.processInnerExtensionKey(getParentCanvas().getParent(), getParentCanvas(), this, key, FrameCursor.this, before == null))
{
return true;
}
else if ( getFrameAfter() != null && getFrameAfter().notifyKeyBefore(key, FrameCursor.this) ) {
return true;
}
else if ( before != null && before.notifyKeyAfter(key, FrameCursor.this) ) {
return true;
}
}
if (available.size() > 1) {
throw new IllegalStateException("Ambigious keypress: " + key + " in frame: " + parentCanvas.getParent() + " [" + available.stream().map(e -> e.getName()).collect(Collectors.joining(", ")) + "]");
}
else if (available.size() == 1) {
Entry<?> frameType = available.get(0);
if (!selection || frameType.isValidOnSelection()) {
editor.beginRecordingState(FrameCursor.this);
editor.recordEdits(StrideEditReason.FLUSH);
disappear();
Frame newFrame;
if (selection) {
List<Frame> selected = editor.getSelection().getSelected();
List<Frame> selectedCopy = Utility.mapList(selected, f -> Loader.loadElement(((CodeFrame<CodeElement>) f).getCode().toXML()).createFrame(editor));
newFrame = frameType.getFactory().createBlock(editor, selectedCopy);
insertBlockBefore(newFrame);
selected.forEach(f -> f.getParentCanvas().removeBlock(f));
editor.getSelection().clear();
}
else {
newFrame = frameType.getFactory().createBlock(editor);
insertBlockBefore(newFrame);
}
editor.recordEdits(selection ? StrideEditReason.SELECTION_WRAP_KEY : StrideEditReason.SINGLE_FRAME_INSERTION_KEY);
editor.modifiedFrame(newFrame, false);
newFrame.markFresh();
if (!newFrame.focusWhenJustAdded()) {
appear();
editor.updateCatalog(FrameCursor.this);
}
if (ctrlDown)
newFrame.insertedWithCtrl();
editor.endRecordingState(null);
consecutiveErrors = 0;
return true;
}
}
else {
consecutiveErrors++;
if (consecutiveErrors >= ERROR_COUNT_TRIGGER) {
BooleanProperty cheatSheetShowingProperty = editor.cheatSheetShowingProperty();
if ( ! cheatSheetShowingProperty.get() ) {
cheatSheetShowingProperty.set(true);
editor.recordShowHideFrameCatalogue(true, FrameCatalogue.ShowReason.UNKNOWN_FRAME_COMMAND);
}
}
editor.recordUnknownCommandKey(getEnclosingFrame(), getCursorIndex(), key);
return true;
}
}
else
{
editor.recordUnknownCommandKey(getEnclosingFrame(), getCursorIndex(), key);
}
editor.getSelection().clear();
return false;
}
| Gets the index of this cursor object, relative to the start of cursors' positions within the inclusive frame.
|
| @return Our relative position in the enclosing frame.
|
public int getCursorIndex()
{
return parentCanvas.getCursors().indexOf(this);
}
public static void editorClosing(InteractionManager editor)
{
shrinkingHeightBindings.remove(editor);
}
| As the cursor moves up/down, we want to make it look like the blocks are sliding out of the
| way for the cursor. To achieve this, we actually shrink old cursor positions while
| growing new position.
|
| To link the size of the growing new position to the old shrinking positions we maintain
| a list of current shrinkers in this class, and bind our property to their size.
| Then we bind the height of the growing portion to be the target, minus this total height
| of remaining shrinkers.
|
private static class TotalHeightBinding
extends IntegerBinding
{
private static final Duration DEFAULT_DURATION = Duration.millis(100);
public static final long ANIMATE_GAP = 800L;
private Set<NumberExpressionBase> shrinkingSpace = new HashSet<>();
private FrameCursor growing;
private long lastShrink = 0;
private long lastGrow = 0;
public synchronized void shrink(FrameCursor c, double target, boolean animate)
{
if (animate && System.currentTimeMillis() - lastShrink < ANIMATE_GAP)
{
animate = false;
}
if (growing == c) {
growing = null;
}
SimpleIntegerProperty heightProp = new SimpleIntegerProperty((int)c.node.getMaxHeight());
c.node.maxHeightProperty().bind(heightProp);
if (animate)
{
c.animation = new Timeline(new KeyFrame(DEFAULT_DURATION, new KeyValue(heightProp, target)));
c.animation.play();
}
else
{
heightProp.set((int)target);
}
shrinkingSpace.add(c.node.maxHeightProperty());
bind(c.node.maxHeightProperty());
lastShrink = System.currentTimeMillis();
}
private synchronized void remove(FrameCursor c)
{
shrinkingSpace.remove(c.node.maxHeightProperty());
unbind(c.node.maxHeightProperty());
if (shrinkingSpace.isEmpty() && growing != null)
{
growing.node.maxHeightProperty().unbind();
growing = null;
}
}
@Override
protected int computeValue()
{
int height = 0;
for (Iterator<NumberExpressionBase> it = shrinkingSpace.iterator(); it.hasNext();)
{
NumberExpressionBase val = it.next();
int h = (int)Math.ceil(val.doubleValue());
height += h - HIDE_HEIGHT;
if (h == 0)
{
it.remove();
unbind(val);
}
}
return height;
}
public synchronized void grow(FrameCursor c, int target, boolean animate)
{
if (animate && System.currentTimeMillis() - lastGrow < ANIMATE_GAP)
{
animate = false;
}
remove(c);
growing = c;
if (shrinkingSpace.isEmpty())
{
IntegerProperty dummy = new SimpleIntegerProperty(target - (int)c.node.getMaxHeight() + HIDE_HEIGHT);
bind(dummy);
shrinkingSpace.add(dummy);
if (animate)
{
Animation anim = new Timeline(new KeyFrame(DEFAULT_DURATION, new KeyValue(dummy, HIDE_HEIGHT)));
anim.play();
}
else
{
dummy.set(HIDE_HEIGHT);
}
}
growing.node.maxHeightProperty().bind(Bindings.max(HIDE_HEIGHT, new ReadOnlyIntegerWrapper(target).subtract(this)));
lastGrow = System.currentTimeMillis();
}
}
private static final Map<InteractionManager, TotalHeightBinding> shrinkingHeightBindings = new IdentityHashMap<>();
private final InteractionManager editor;
private Timeline animation;
private Canvas redCross;
private Canvas copyingPlus;
private ImageView dragTargetOverlayFake;
| Constructor
|
public FrameCursor(final InteractionManager editor, final FrameCanvas parentCanvas)
{
node.getStyleClass().add("frame-cursor");
node.setMaxWidth(100);
node.setMaxHeight(HIDE_HEIGHT);
node.setOpacity(0);
this.parentCanvas = parentCanvas;
if (parentCanvas == null) {
throw new IllegalArgumentException("BlockCursor: parentCanvas cannot be null");
}
this.editor = editor;
if (editor != null)
{
shrinkingHeightBindings.putIfAbsent(editor, new TotalHeightBinding());
}
node.minWidthProperty().bind(node.maxWidthProperty());
node.prefWidthProperty().bind(node.maxWidthProperty());
node.minHeightProperty().bind(node.maxHeightProperty());
node.prefHeightProperty().bind(node.maxHeightProperty());
shrinkingHeightBindings.get(editor).shrink(this,HIDE_HEIGHT,false);
JavaFXUtil.addChangeListener(node.focusedProperty(), nowFocused -> {
if (node.getScene() != null)
{
animateShowHide(nowFocused, false);
}
});
JavaFXUtil.addChangeListener(node.localToSceneTransformProperty(), t -> JavaFXUtil.runNowOrLater(() -> adjustDragTargetPosition()));
if (editor != null) {
editor.setupFrameCursor(this);
}
node.addEventFilter(MouseEvent.MOUSE_PRESSED, e -> {
if (e.getButton() == MouseButton.PRIMARY && editor != null && !isFocused())
{
editor.clickNearestCursor(e.getSceneX(), e.getSceneY(), e.isShiftDown());
e.consume();
}
});
JavaFXUtil.listenForContextMenu(node, this::showContextMenu);
getNode().focusedProperty().addListener((observable, oldValue, nowFocused) -> {
{
if (!nowFocused)
{
consecutiveErrors = 0;
}
}
});
| To insert other blocks when text input is made from this cursor
|
getNode().setOnKeyTyped(event -> {
try{
String character = event.getCharacter();
if (!character.isEmpty()) {
char key = character.toCharArray()[0];
boolean ignore = Config.isMacOS() && event.isShortcutDown();
if (!ignore && keyTyped(editor, parentCanvas, key, event.isControlDown())) {
event.consume();
}
}
}
catch(Exception ex){
Debug.reportError("CURSOR KEY PRESS: ", ex);
}
});
getNode().setOnKeyPressed(event -> {
if (!editor.isEditable())
return;
if ((event.getCode() == KeyCode.BACK_SPACE || event.getCode() == KeyCode.DELETE)
&& !editor.getSelection().getSelected().isEmpty())
{
List<Frame> toDelete = editor.getSelection().getSelected();
FrameCursor focusAfter = toDelete.get(0).getCursorBefore();
editor.beginRecordingState(FrameCursor.this);
if (toDelete.stream().allMatch(f -> f.getParentCanvas() == getParentCanvas()))
{
editor.recordEdits(StrideEditReason.FLUSH);
int effort = toDelete.stream().mapToInt(Frame::calculateEffort).sum();
editor.showUndoDeleteBanner(effort);
FrameCanvas c = getParentCanvas();
toDelete.forEach(f -> c.removeBlock(f));
editor.recordEdits(event.getCode() == KeyCode.BACK_SPACE ? StrideEditReason.DELETE_FRAMES_KEY_BKSP : StrideEditReason.DELETE_FRAMES_KEY_DELETE);
}
else
{
Debug.message("Warning: trying to delete selection from remote cursor");
}
editor.getSelection().clear();
focusAfter.requestFocus();
editor.endRecordingState(focusAfter);
editor.updateCatalog(focusAfter);
event.consume();
return;
}
if (event.getCode() == KeyCode.BACK_SPACE)
{
Frame target = parentCanvas.getFrameBefore(FrameCursor.this);
if (target != null)
{
editor.beginRecordingState(FrameCursor.this);
FrameCursor cursorBeforeTarget = parentCanvas.getCursorBefore(target);
editor.recordEdits(StrideEditReason.FLUSH);
editor.showUndoDeleteBanner(target.calculateEffort());
parentCanvas.removeBlock(target);
editor.recordEdits(StrideEditReason.DELETE_FRAMES_KEY_BKSP);
editor.modifiedFrame(target, false);
cursorBeforeTarget.requestFocus();
editor.endRecordingState(cursorBeforeTarget);
}
else
{
editor.beginRecordingState(FrameCursor.this);
FrameCursor cursorBeforeTarget = parentCanvas.getParent().getCursorBefore(parentCanvas);
CanvasParent.processInnerExtensionKey(parentCanvas.getParent(), parentCanvas, this, '\b', FrameCursor.this, true);
editor.endRecordingState(cursorBeforeTarget);
}
}
else if (event.getCode() == KeyCode.ESCAPE)
{
Frame target = parentCanvas.getFrameBefore(FrameCursor.this);
if (target != null){
target.escape(null, null);
}
else
{
Frame BeforeTarget = parentCanvas.getParent().getCursorBefore(parentCanvas).getFrameAfter();
if (BeforeTarget != null)
{
BeforeTarget.escape(null, null);
}
}
}
else if (event.getCode() == KeyCode.DELETE)
{
Frame target = parentCanvas.getFrameAfter(FrameCursor.this);
if (target != null)
{
editor.beginRecordingState(FrameCursor.this);
editor.recordEdits(StrideEditReason.FLUSH);
editor.showUndoDeleteBanner(target.calculateEffort());
parentCanvas.removeBlock(target);
editor.recordEdits(StrideEditReason.DELETE_FRAMES_KEY_DELETE);
editor.modifiedFrame(target, false);
editor.endRecordingState(FrameCursor.this);
}
}
else if (event.getCode() == KeyCode.ENTER)
{
keyTyped(editor, parentCanvas, '\n', false);
}
else if (event.getCode() == KeyCode.SPACE && event.isControlDown())
{
keyTyped(editor, parentCanvas, ' ', event.isControlDown());
}
});
}
private void animateShowHide(boolean show, boolean animate)
{
if (animation != null) {
animation.stop();
}
node.setOpacity(show ? 1.0 : 0.0);
if (getParentCanvas().blockCount() == 0) {
node.maxHeightProperty().unbind();
node.setMaxHeight(show ? FULL_HEIGHT : HIDE_HEIGHT);
}
else {
if (show) {
shrinkingHeightBindings.get(editor).grow(FrameCursor.this, FULL_HEIGHT, animate);
}
else {
TotalHeightBinding heightBinding = shrinkingHeightBindings.get(editor);
if (heightBinding != null)
heightBinding.shrink(FrameCursor.this, HIDE_HEIGHT, animate);
}
}
}
@OnThread(Tag.FXPlatform)
public void showAsDropTarget(boolean showAsSource, boolean dragPossible, boolean copying)
{
int chosen;
if (dragPossible) {
chosen = showAsSource ? 1 : 0;
}
else {
chosen = 2;
}
animateShowHide(true, false);
setDragClass(chosen);
updateDragCopyState(copying);
}
|
| @param classIndex -1 for no drag target, 0 for possible, 1 for source, 2 for imposibble
|
@OnThread(Tag.FXPlatform)
private void setDragClass(int classIndex)
{
JavaFXUtil.selectPseudoClass(node, classIndex,
"bj-drag-possible", "bj-drag-source", "bj-drag-impossible");
setDragTargetOverlayVisible(classIndex != -1, classIndex == 2);
}
@OnThread(Tag.FXPlatform)
private void adjustDragTargetPosition()
{
if (dragTargetOverlayFake != null)
{
Pane dragTargetCursorPane = editor.getDragTargetCursorPane();
Bounds scenePos = getNode().localToScene(getNode().getBoundsInLocal());
Bounds panePos = dragTargetCursorPane.sceneToLocal(scenePos);
dragTargetOverlayFake.setLayoutX(panePos.getMinX());
dragTargetOverlayFake.setLayoutY(panePos.getMinY());
if (redCross != null)
{
redCross.setLayoutX(dragTargetOverlayFake.getLayoutX() + node.widthProperty().divide(2.0).subtract(redCross.getWidth()/2.0).get());
redCross.setLayoutY(dragTargetOverlayFake.getLayoutY() + node.heightProperty().divide(2.0).subtract(redCross.getHeight()/2.0).get());
}
if (copyingPlus != null)
{
copyingPlus.setLayoutX(dragTargetOverlayFake.getLayoutX() + node.getWidth());
copyingPlus.setLayoutY(dragTargetOverlayFake.getLayoutY() - copyingPlus.getWidth());
}
}
}
@OnThread(Tag.FXPlatform)
private void setDragTargetOverlayVisible(boolean visible, boolean showCross)
{
Pane dragTargetCursorPane = editor.getDragTargetCursorPane();
if (visible)
{
Image snapshot = getNode().snapshot(null, null);
dragTargetOverlayFake = new ImageView(snapshot);
dragTargetCursorPane.getChildren().add(dragTargetOverlayFake);
if (showCross && redCross == null)
{
redCross = new Canvas(FULL_HEIGHT * 3, FULL_HEIGHT * 3);
GraphicsContext gc = redCross.getGraphicsContext2D();
gc.setStroke(Color.RED);
gc.setLineWidth(2.0);
gc.strokeLine(1.0, 1.0, redCross.getWidth() - 2.0, redCross.getHeight() - 2.0);
gc.strokeLine(redCross.getWidth() - 2.0, 1.0, 1.0, redCross.getHeight() - 2.0);
dragTargetCursorPane.getChildren().add(redCross);
}
adjustDragTargetPosition();
}
else
{
if (dragTargetOverlayFake != null)
{
dragTargetCursorPane.getChildren().remove(dragTargetOverlayFake);
dragTargetOverlayFake = null;
}
if (redCross != null)
{
dragTargetCursorPane.getChildren().remove(redCross);
redCross = null;
}
if (copyingPlus != null)
{
dragTargetCursorPane.getChildren().remove(copyingPlus);
copyingPlus = null;
}
}
}
@OnThread(Tag.FXPlatform)
public void stopShowAsDropTarget()
{
animateShowHide(false, false);
setDragClass(-1);
}
public void insertBlockAfter(Frame b)
{
getParentCanvas().insertBlockAfter(b, this);
}
public void insertBlockBefore(Frame b)
{
getParentCanvas().insertBlockBefore(b, this);
}
public FrameTypeCheck check()
{
return getParentCanvas().getParent().check(getParentCanvas());
}
public void insertFramesAfter(List<Frame> frames)
{
List<Frame> rev = new ArrayList<>(frames);
Collections.reverse(rev);
rev.forEach(f -> getParentCanvas().insertBlockAfter(f, this));
}
| Gets the next block at the same level (in the same canvas) if possible,
| or the next valid block cursor after that (by going up a level).
|
public FrameCursor getNextSkip()
{
return parentCanvas.getNextCursor(this, false);
}
| Gets the previous block at the same level (in the same canvas) if possible,
| or the previous valid block cursor after that (by going up a level).
|
public FrameCursor getPrevSkip()
{
return parentCanvas.getPrevCursor(this, false);
}
| Gets the cursor just before the enclosing block for this cursor
|
public FrameCursor getUp()
{
Frame frameBefore = getFrameBefore();
if (frameBefore != null) {
return frameBefore.getCursorBefore();
}
return parentCanvas.getParent().getCursorBefore(parentCanvas);
}
| Gets the cursor just after the enclosing block for this cursor
|
public FrameCursor getDown()
{
Frame frameAfter = getFrameAfter();
if (frameAfter != null) {
return frameAfter.getCursorAfter();
}
return parentCanvas.getParent().getCursorAfter(parentCanvas);
}
public Frame getFrameBefore()
{
return parentCanvas.getFrameBefore(this);
}
public Frame getFrameAfter()
{
return parentCanvas.getFrameAfter(this);
}
public FrameCanvas getParentCanvas()
{
return parentCanvas;
}
public Frame getEnclosingFrame()
{
final FrameCanvas parentCanvas = getParentCanvas();
if (parentCanvas == null) return null;
final CanvasParent canvasParent = parentCanvas.getParent();
if (canvasParent == null) return null;
return canvasParent.getFrame();
}
public Region getNode()
{
return node;
}
public void requestFocus()
{
node.requestFocus();
}
public Bounds getSceneBounds()
{
return node.localToScene(node.getBoundsInLocal());
}
| Vanish without animating
|
protected void disappear()
{
animateShowHide(false, false);
}
| Appear without animating
|
protected void appear()
{
animateShowHide(true, false);
}
public InteractionManager getEditor()
{
return this.editor;
}
@OnThread(Tag.FXPlatform)
private boolean showContextMenu(double screenX, double screenY)
{
if (JavaFXUtil.hasPseudoclass(node, "bj-java-preview"))
return false;
if (menu != null) {
menu.hide();
}
menu = EditableSlot.MenuItems.makeContextMenu(Collections.singletonMap(EditableSlot.TopLevelMenu.EDIT, getMenuItems(true)));
if (menu.getItems().size() > 0) {
menu.show(node, screenX, screenY);
return true;
}
return false;
}
@OnThread(Tag.FXPlatform)
public EditableSlot.MenuItems getMenuItems(boolean contextMenu)
{
boolean selection = !editor.getSelection().isEmpty();
EditableSlot.MenuItems menuItems = new EditableSlot.MenuItems(FXCollections.observableArrayList(new PasteFrameOperation(editor).getMenuItem(contextMenu)));
if (!editor.getSelection().isEmpty())
{
menuItems = EditableSlot.MenuItems.concat( editor.getSelection().getMenuItems(contextMenu), menuItems);
}
if (canInsert() && contextMenu && !selection)
{
Menu insertMenu = new Menu("Insert");
insertMenu.getItems().addAll(getAcceptedFramesMenuItems());
menuItems = EditableSlot.MenuItems.concat( new EditableSlot.MenuItems(FXCollections.observableArrayList(MenuItemOrder.INSERT_FRAME.item(insertMenu))), menuItems);
}
return menuItems;
}
private List
top,
use,
map,
class FrameCursor
. keyTyped
. getCursorIndex
. editorClosing
top,
use,
map,
class TotalHeightBinding
. shrink
. remove
. computeValue
. grow
. FrameCursor
. animateShowHide
. showAsDropTarget
. setDragClass
. adjustDragTargetPosition
. setDragTargetOverlayVisible
. stopShowAsDropTarget
. insertBlockAfter
. insertBlockBefore
. check
. insertFramesAfter
. getNextSkip
. getPrevSkip
. getUp
. getDown
. getFrameBefore
. getFrameAfter
. getParentCanvas
. getEnclosingFrame
. getNode
. requestFocus
. getSceneBounds
. disappear
. appear
. getEditor
. showContextMenu
. getMenuItems
. getAcceptedFramesMenuItems
. createMenuItem
. isFocused
. getFocusInfo
. recallFocus
. updateDragCopyState
. setView
. canInsert
1119 neLoCode
+ 22 LoComm