kopia lustrzana https://github.com/backface/turtlestitch
keyboard editing support
activate: - shift + click on a scripting pane's background - shift + click on any block - shift + enter in the IDE's edit mode stop editing: - left-click on scripting pane's background - esc navigate among scripts: - tab: next script - backtab (shift + tab): last script start editing a new script: - shift + enter navigate among commands within a script: - down arrow: next command - up arrow: last command navigate among all elements within a script: - right arrow: next element (block or input) - left arrow: last element move the currently edited script (stack of blocks): - shift + arrow keys (left, right, up, down) editing scripts: - backspace: * delete currently focused reporter * delete command above current insertion mark (blinking) * collapse currently focused variadic input by one element - enter: * edit currently focused input slot * expand currently focused variadic input by one element - space: * activate currently focused input slot's pull-down menu, if any * show a menu of reachable variables for the focused input or reporter - any other key: start searching for insertable matching blocks - in menus triggered by this feature: * navigate with up / down arrow keys * trigger selection with enter * cancel menu with esc - in the search bar triggered b this feature: * keep typing / deleting to narrow and update matches * navigate among shown matches with up / down arrow keys * insert selected match at the focus' position with enter * cancel searching and inserting with esc running the currently edited script: * shift+ctrl+enter simulates clicking the edited script with the mousepull/3/merge
rodzic
60554d0059
commit
76d9d6bd49
894
blocks.js
894
blocks.js
|
@ -65,6 +65,7 @@
|
|||
RingMorph
|
||||
BoxMorph*
|
||||
CommentMorph
|
||||
ScriptFocusMorph
|
||||
|
||||
* from morphic.js
|
||||
|
||||
|
@ -155,7 +156,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.blocks = '2015-June-25';
|
||||
modules.blocks = '2015-July-26';
|
||||
|
||||
var SyntaxElementMorph;
|
||||
var BlockMorph;
|
||||
|
@ -182,6 +183,7 @@ var SymbolMorph;
|
|||
var CommentMorph;
|
||||
var ArgLabelMorph;
|
||||
var TextSlotMorph;
|
||||
var ScriptFocusMorph;
|
||||
|
||||
WorldMorph.prototype.customMorphs = function () {
|
||||
// add examples to the world's demo menu
|
||||
|
@ -624,6 +626,48 @@ SyntaxElementMorph.prototype.topBlock = function () {
|
|||
return this;
|
||||
};
|
||||
|
||||
// SyntaxElementMorph reachable variables
|
||||
|
||||
SyntaxElementMorph.prototype.getVarNamesDict = function () {
|
||||
var block = this.parentThatIsA(BlockMorph),
|
||||
rcvr,
|
||||
tempVars = [],
|
||||
dict;
|
||||
|
||||
if (!block) {
|
||||
return {};
|
||||
}
|
||||
rcvr = block.receiver();
|
||||
block.allParents().forEach(function (morph) {
|
||||
if (morph instanceof PrototypeHatBlockMorph) {
|
||||
tempVars.push.apply(
|
||||
tempVars,
|
||||
morph.inputs()[0].inputFragmentNames()
|
||||
);
|
||||
} else if (morph instanceof BlockMorph) {
|
||||
morph.inputs().forEach(function (inp) {
|
||||
if (inp instanceof TemplateSlotMorph) {
|
||||
tempVars.push(inp.contents());
|
||||
} else if (inp instanceof MultiArgMorph) {
|
||||
inp.children.forEach(function (m) {
|
||||
if (m instanceof TemplateSlotMorph) {
|
||||
tempVars.push(m.contents());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (rcvr) {
|
||||
dict = rcvr.variables.allNamesDict();
|
||||
tempVars.forEach(function (name) {
|
||||
dict[name] = name;
|
||||
});
|
||||
return dict;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
// SyntaxElementMorph drag & drop:
|
||||
|
||||
SyntaxElementMorph.prototype.reactToGrabOf = function (grabbedMorph) {
|
||||
|
@ -2950,6 +2994,17 @@ BlockMorph.prototype.getHighlight = function () {
|
|||
return null;
|
||||
};
|
||||
|
||||
BlockMorph.prototype.outline = function (color, border) {
|
||||
var highlight = new BlockHighlightMorph(),
|
||||
fb = this.fullBounds(),
|
||||
edge = border;
|
||||
highlight.setExtent(fb.extent().add(edge * 2));
|
||||
highlight.color = color;
|
||||
highlight.image = this.highlightImage(color, border);
|
||||
highlight.setPosition(fb.origin.subtract(new Point(edge, edge)));
|
||||
return highlight;
|
||||
};
|
||||
|
||||
// BlockMorph zebra coloring
|
||||
|
||||
BlockMorph.prototype.fixBlockColor = function (nearestBlock, isForced) {
|
||||
|
@ -3096,7 +3151,11 @@ BlockMorph.prototype.fullCopy = function () {
|
|||
BlockMorph.prototype.mouseClickLeft = function () {
|
||||
var top = this.topBlock(),
|
||||
receiver = top.receiver(),
|
||||
shiftClicked = this.world().currentKey === 16,
|
||||
stage;
|
||||
if (shiftClicked && !this.isTemplate) {
|
||||
return this.focus();
|
||||
}
|
||||
if (top instanceof PrototypeHatBlockMorph) {
|
||||
return top.mouseClickLeft();
|
||||
}
|
||||
|
@ -3108,6 +3167,21 @@ BlockMorph.prototype.mouseClickLeft = function () {
|
|||
}
|
||||
};
|
||||
|
||||
BlockMorph.prototype.focus = function () {
|
||||
var scripts = this.parentThatIsA(ScriptsMorph),
|
||||
world = this.world(),
|
||||
focus;
|
||||
if (!scripts || !ScriptsMorph.prototype.enableKeyboard) {return; }
|
||||
if (scripts.focus) {scripts.focus.stopEditing(); }
|
||||
world.stopEditing();
|
||||
focus = new ScriptFocusMorph(scripts, this);
|
||||
scripts.focus = focus;
|
||||
focus.getFocus(world);
|
||||
if (this instanceof HatBlockMorph) {
|
||||
focus.nextCommand();
|
||||
}
|
||||
};
|
||||
|
||||
// BlockMorph thumbnail
|
||||
|
||||
BlockMorph.prototype.thumbnail = function (scale, clipWidth) {
|
||||
|
@ -4794,6 +4868,7 @@ ScriptsMorph.uber = FrameMorph.prototype;
|
|||
ScriptsMorph.prototype.cleanUpMargin = 20;
|
||||
ScriptsMorph.prototype.cleanUpSpacing = 15;
|
||||
ScriptsMorph.prototype.isPreferringEmptySlots = true;
|
||||
ScriptsMorph.prototype.enableKeyboard = true;
|
||||
|
||||
// ScriptsMorph instance creation:
|
||||
|
||||
|
@ -4813,6 +4888,9 @@ ScriptsMorph.prototype.init = function (owner) {
|
|||
this.lastPreservedBlocks = null;
|
||||
this.lastNextBlock = null;
|
||||
|
||||
// keyboard editing support:
|
||||
this.focus = null;
|
||||
|
||||
ScriptsMorph.uber.init.call(this);
|
||||
this.setColor(new Color(70, 70, 70));
|
||||
};
|
||||
|
@ -4823,6 +4901,9 @@ ScriptsMorph.prototype.fullCopy = function () {
|
|||
var cpy = new ScriptsMorph(),
|
||||
pos = this.position(),
|
||||
child;
|
||||
if (this.focus) {
|
||||
this.focus.stopEditing();
|
||||
}
|
||||
this.children.forEach(function (morph) {
|
||||
if (!morph.block) { // omit anchored comments
|
||||
child = morph.fullCopy();
|
||||
|
@ -4842,13 +4923,18 @@ ScriptsMorph.prototype.fullCopy = function () {
|
|||
// ScriptsMorph stepping:
|
||||
|
||||
ScriptsMorph.prototype.step = function () {
|
||||
var hand = this.world().hand,
|
||||
var world = this.world(),
|
||||
hand = world.hand,
|
||||
block;
|
||||
|
||||
if (this.feedbackMorph.parent) {
|
||||
this.feedbackMorph.destroy();
|
||||
this.feedbackMorph.parent = null;
|
||||
}
|
||||
if (this.focus && (!world.keyboardReceiver ||
|
||||
world.keyboardReceiver instanceof StageMorph)) {
|
||||
this.focus.getFocus(world);
|
||||
}
|
||||
if (hand.children.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
@ -5311,6 +5397,27 @@ ScriptsMorph.prototype.reactToDropOf = function (droppedMorph, hand) {
|
|||
this.adjustBounds();
|
||||
};
|
||||
|
||||
// ScriptsMorph events
|
||||
|
||||
ScriptsMorph.prototype.mouseClickLeft = function (pos) {
|
||||
var shiftClicked = this.world().currentKey === 16;
|
||||
if (shiftClicked) {
|
||||
return this.edit(pos);
|
||||
}
|
||||
if (this.focus) {this.focus.stopEditing(); }
|
||||
};
|
||||
|
||||
// ScriptsMorph keyboard support
|
||||
|
||||
ScriptsMorph.prototype.edit = function (pos) {
|
||||
var world = this.world();
|
||||
if (this.focus) {this.focus.stopEditing(); }
|
||||
world.stopEditing();
|
||||
if (!ScriptsMorph.prototype.enableKeyboard) {return; }
|
||||
this.focus = new ScriptFocusMorph(this, this, pos);
|
||||
this.focus.getFocus(world);
|
||||
};
|
||||
|
||||
// ArgMorph //////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
|
@ -6575,7 +6682,7 @@ InputSlotMorph.prototype.setContents = function (aStringOrFloat) {
|
|||
|
||||
// InputSlotMorph drop-down menu:
|
||||
|
||||
InputSlotMorph.prototype.dropDownMenu = function () {
|
||||
InputSlotMorph.prototype.dropDownMenu = function (enableKeyboard) {
|
||||
var choices = this.choices,
|
||||
key,
|
||||
menu = new MenuMorph(
|
||||
|
@ -6606,7 +6713,12 @@ InputSlotMorph.prototype.dropDownMenu = function () {
|
|||
}
|
||||
}
|
||||
if (menu.items.length > 0) {
|
||||
menu.popUpAtHand(this.world());
|
||||
if (enableKeyboard) {
|
||||
menu.popup(this.world(), this.bottomLeft());
|
||||
menu.getFocus();
|
||||
} else {
|
||||
menu.popUpAtHand(this.world());
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -6861,46 +6973,6 @@ InputSlotMorph.prototype.soundsMenu = function () {
|
|||
return dict;
|
||||
};
|
||||
|
||||
InputSlotMorph.prototype.getVarNamesDict = function () {
|
||||
var block = this.parentThatIsA(BlockMorph),
|
||||
rcvr,
|
||||
tempVars = [],
|
||||
dict;
|
||||
|
||||
if (!block) {
|
||||
return {};
|
||||
}
|
||||
rcvr = block.receiver();
|
||||
block.allParents().forEach(function (morph) {
|
||||
if (morph instanceof PrototypeHatBlockMorph) {
|
||||
tempVars.push.apply(
|
||||
tempVars,
|
||||
morph.inputs()[0].inputFragmentNames()
|
||||
);
|
||||
} else if (morph instanceof BlockMorph) {
|
||||
morph.inputs().forEach(function (inp) {
|
||||
if (inp instanceof TemplateSlotMorph) {
|
||||
tempVars.push(inp.contents());
|
||||
} else if (inp instanceof MultiArgMorph) {
|
||||
inp.children.forEach(function (m) {
|
||||
if (m instanceof TemplateSlotMorph) {
|
||||
tempVars.push(m.contents());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
if (rcvr) {
|
||||
dict = rcvr.variables.allNamesDict();
|
||||
tempVars.forEach(function (name) {
|
||||
dict[name] = name;
|
||||
});
|
||||
return dict;
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
InputSlotMorph.prototype.setChoices = function (dict, readonly) {
|
||||
// externally specify choices and read-only status,
|
||||
// used for custom blocks
|
||||
|
@ -10981,3 +11053,737 @@ CommentMorph.prototype.destroy = function () {
|
|||
CommentMorph.prototype.stackHeight = function () {
|
||||
return this.height();
|
||||
};
|
||||
|
||||
// ScriptFocusMorph //////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
I offer keyboard navigation for syntax elements, blocks and scripts:
|
||||
|
||||
activate:
|
||||
- shift + click on a scripting pane's background
|
||||
- shift + click on any block
|
||||
- shift + enter in the IDE's edit mode
|
||||
|
||||
stop editing:
|
||||
- left-click on scripting pane's background
|
||||
- esc
|
||||
|
||||
navigate among scripts:
|
||||
- tab: next script
|
||||
- backtab (shift + tab): last script
|
||||
|
||||
start editing a new script:
|
||||
- shift + enter
|
||||
|
||||
navigate among commands within a script:
|
||||
- down arrow: next command
|
||||
- up arrow: last command
|
||||
|
||||
navigate among all elements within a script:
|
||||
- right arrow: next element (block or input)
|
||||
- left arrow: last element
|
||||
|
||||
move the currently edited script (stack of blocks):
|
||||
- shift + arrow keys (left, right, up, down)
|
||||
|
||||
editing scripts:
|
||||
|
||||
- backspace:
|
||||
* delete currently focused reporter
|
||||
* delete command above current insertion mark (blinking)
|
||||
* collapse currently focused variadic input by one element
|
||||
|
||||
- enter:
|
||||
* edit currently focused input slot
|
||||
* expand currently focused variadic input by one element
|
||||
|
||||
- space:
|
||||
* activate currently focused input slot's pull-down menu, if any
|
||||
* show a menu of reachable variables for the focused input or reporter
|
||||
|
||||
- any other key:
|
||||
start searching for insertable matching blocks
|
||||
|
||||
- in menus triggered by this feature:
|
||||
* navigate with up / down arrow keys
|
||||
* trigger selection with enter
|
||||
* cancel menu with esc
|
||||
|
||||
- in the search bar triggered b this feature:
|
||||
* keep typing / deleting to narrow and update matches
|
||||
* navigate among shown matches with up / down arrow keys
|
||||
* insert selected match at the focus' position with enter
|
||||
* cancel searching and inserting with esc
|
||||
|
||||
running the currently edited script:
|
||||
* shift+ctrl+enter simulates clicking the edited script with the mouse
|
||||
*/
|
||||
|
||||
// ScriptFocusMorph inherits from BoxMorph:
|
||||
|
||||
ScriptFocusMorph.prototype = new BoxMorph();
|
||||
ScriptFocusMorph.prototype.constructor = ScriptFocusMorph;
|
||||
ScriptFocusMorph.uber = BoxMorph.prototype;
|
||||
|
||||
// ScriptFocusMorph instance creation:
|
||||
|
||||
function ScriptFocusMorph(editor, initialElement, position) {
|
||||
this.init(editor, initialElement, position);
|
||||
}
|
||||
|
||||
ScriptFocusMorph.prototype.init = function (
|
||||
editor,
|
||||
initialElement,
|
||||
position
|
||||
) {
|
||||
this.editor = editor; // a ScriptsMorph
|
||||
this.element = initialElement;
|
||||
this.atEnd = false;
|
||||
ScriptFocusMorph.uber.init.call(this);
|
||||
if (this.element instanceof ScriptsMorph) {
|
||||
this.setPosition(position);
|
||||
}
|
||||
};
|
||||
|
||||
// ScriptFocusMorph keyboard focus:
|
||||
|
||||
ScriptFocusMorph.prototype.getFocus = function (world) {
|
||||
if (!world) {world = this.world(); }
|
||||
if (world && world.keyboardReceiver !== this) {
|
||||
world.stopEditing();
|
||||
}
|
||||
world.keyboardReceiver = this;
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
// ScriptFocusMorph layout:
|
||||
|
||||
ScriptFocusMorph.prototype.fixLayout = function () {
|
||||
this.changed();
|
||||
if (this.element instanceof CommandBlockMorph ||
|
||||
this.element instanceof CommandSlotMorph ||
|
||||
this.element instanceof ScriptsMorph) {
|
||||
this.manifestStatement();
|
||||
} else {
|
||||
this.manifestExpression();
|
||||
}
|
||||
this.editor.add(this); // come to front
|
||||
this.scrollIntoView();
|
||||
this.changed();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.manifestStatement = function () {
|
||||
var newScript = this.element instanceof ScriptsMorph,
|
||||
y = this.element.top();
|
||||
this.border = 0;
|
||||
this.edge = 0;
|
||||
this.alpha = 1;
|
||||
this.color = this.editor.feedbackColor;
|
||||
this.setExtent(new Point(
|
||||
newScript ?
|
||||
SyntaxElementMorph.prototype.hatWidth : this.element.width(),
|
||||
Math.max(
|
||||
SyntaxElementMorph.prototype.corner,
|
||||
SyntaxElementMorph.prototype.feedbackMinHeight
|
||||
)
|
||||
));
|
||||
if (this.element instanceof CommandSlotMorph) {
|
||||
y += SyntaxElementMorph.prototype.corner;
|
||||
} else if (this.atEnd) {
|
||||
y = this.element.bottom();
|
||||
}
|
||||
if (!newScript) {
|
||||
this.setPosition(new Point(
|
||||
this.element.left(),
|
||||
y
|
||||
));
|
||||
}
|
||||
this.fps = 2;
|
||||
this.show();
|
||||
this.step = function () {
|
||||
this.toggleVisibility();
|
||||
};
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.manifestExpression = function () {
|
||||
this.edge = SyntaxElementMorph.prototype.rounding;
|
||||
this.border = Math.max(
|
||||
SyntaxElementMorph.prototype.edge,
|
||||
3
|
||||
);
|
||||
this.color = this.editor.feedbackColor.copy();
|
||||
this.color.a = 0.5;
|
||||
this.borderColor = this.editor.feedbackColor;
|
||||
|
||||
this.bounds = this.element.fullBounds()
|
||||
.expandBy(Math.max(
|
||||
SyntaxElementMorph.prototype.edge * 2,
|
||||
SyntaxElementMorph.prototype.reporterDropFeedbackPadding
|
||||
));
|
||||
this.drawNew();
|
||||
delete this.fps;
|
||||
delete this.step;
|
||||
this.show();
|
||||
};
|
||||
|
||||
// ScriptFocusMorph editing
|
||||
|
||||
ScriptFocusMorph.prototype.trigger = function () {
|
||||
var current = this.element;
|
||||
if (current instanceof MultiArgMorph) {
|
||||
if (current.arrows().children[1].isVisible) {
|
||||
current.addInput();
|
||||
this.fixLayout();
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (current.parent instanceof TemplateSlotMorph) {
|
||||
current.mouseClickLeft();
|
||||
return;
|
||||
}
|
||||
if (current instanceof InputSlotMorph) {
|
||||
if (!current.isReadOnly) {
|
||||
delete this.fps;
|
||||
delete this.step;
|
||||
this.hide();
|
||||
current.contents().edit();
|
||||
this.world().onNextStep = function () {
|
||||
current.contents().selectAll();
|
||||
};
|
||||
} else if (current.choices) {
|
||||
current.dropDownMenu(true);
|
||||
delete this.fps;
|
||||
delete this.step;
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.menu = function () {
|
||||
var current = this.element;
|
||||
if (current instanceof InputSlotMorph && current.choices) {
|
||||
current.dropDownMenu(true);
|
||||
delete this.fps;
|
||||
delete this.step;
|
||||
this.hide();
|
||||
} else {
|
||||
this.insertVariableGetter();
|
||||
}
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.deleteLastElement = function () {
|
||||
var current = this.element;
|
||||
if (current.parent instanceof ScriptsMorph) {
|
||||
if (this.atEnd || current instanceof ReporterBlockMorph) {
|
||||
current.destroy();
|
||||
this.element = this.editor;
|
||||
this.atEnd = false;
|
||||
}
|
||||
} else if (current instanceof MultiArgMorph) {
|
||||
if (current.arrows().children[0].isVisible) {
|
||||
current.removeInput();
|
||||
}
|
||||
} else if (current instanceof ReporterBlockMorph) {
|
||||
if (!current.isTemplate) {
|
||||
this.lastElement();
|
||||
current.prepareToBeGrabbed();
|
||||
current.destroy();
|
||||
}
|
||||
} else if (current instanceof CommandBlockMorph) {
|
||||
if (this.atEnd) {
|
||||
this.element = current.parent;
|
||||
current.userDestroy();
|
||||
} else {
|
||||
if (current.parent instanceof CommandBlockMorph) {
|
||||
current.parent.userDestroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
this.editor.adjustBounds();
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.insertBlock = function (block) {
|
||||
var pb;
|
||||
block.isTemplate = false;
|
||||
block.isDraggable = true;
|
||||
|
||||
if (block.snapSound) {
|
||||
block.snapSound.play();
|
||||
}
|
||||
|
||||
if (this.element instanceof ScriptsMorph) {
|
||||
this.editor.add(block);
|
||||
this.element = block;
|
||||
if (block instanceof CommandBlockMorph) {
|
||||
block.setLeft(this.left());
|
||||
if (block.isStop()) {
|
||||
block.setTop(this.top());
|
||||
} else {
|
||||
block.setBottom(this.top());
|
||||
this.atEnd = true;
|
||||
}
|
||||
} else {
|
||||
block.setCenter(this.center());
|
||||
block.setLeft(this.left());
|
||||
}
|
||||
} else if (this.element instanceof CommandBlockMorph) {
|
||||
if (this.atEnd) {
|
||||
this.element.nextBlock(block);
|
||||
this.element = block;
|
||||
this.fixLayout();
|
||||
} else {
|
||||
// to be done: special case if block.isStop()
|
||||
pb = this.element.parent;
|
||||
if (pb instanceof ScriptsMorph) { // top block
|
||||
block.setLeft(this.element.left());
|
||||
block.setBottom(this.element.top() + this.element.corner);
|
||||
this.editor.add(block);
|
||||
block.nextBlock(this.element);
|
||||
this.fixLayout();
|
||||
} else if (pb instanceof CommandSlotMorph) {
|
||||
pb.nestedBlock(block);
|
||||
} else if (pb instanceof CommandBlockMorph) {
|
||||
pb.nextBlock(block);
|
||||
}
|
||||
}
|
||||
} else if (this.element instanceof CommandSlotMorph) {
|
||||
// to be done: special case if block.isStop()
|
||||
this.element.nestedBlock(block);
|
||||
this.element = block;
|
||||
this.atEnd = true;
|
||||
} else {
|
||||
pb = this.element.parent;
|
||||
if (pb instanceof ScriptsMorph) {
|
||||
this.editor.add(block);
|
||||
block.setPosition(this.element.position());
|
||||
this.element.destroy();
|
||||
} else {
|
||||
pb.replaceInput(this.element, block);
|
||||
}
|
||||
this.element = block;
|
||||
}
|
||||
block.fixBlockColor();
|
||||
this.editor.adjustBounds();
|
||||
// block.scrollIntoView();
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.insertVariableGetter = function () {
|
||||
var types = this.blockTypes(),
|
||||
vars,
|
||||
myself = this,
|
||||
menu = new MenuMorph();
|
||||
if (!types || !contains(types, 'reporter')) {
|
||||
return;
|
||||
}
|
||||
vars = InputSlotMorph.prototype.getVarNamesDict.call(this.element);
|
||||
Object.keys(vars).forEach(function (vName) {
|
||||
var block = SpriteMorph.prototype.variableBlock(vName);
|
||||
block.addShadow(new Point(3, 3));
|
||||
menu.addItem(
|
||||
block,
|
||||
function () {
|
||||
block.removeShadow();
|
||||
myself.insertBlock(block);
|
||||
}
|
||||
);
|
||||
});
|
||||
if (menu.items.length > 0) {
|
||||
menu.popup(this.world(), this.element.bottomLeft());
|
||||
menu.getFocus();
|
||||
}
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.stopEditing = function () {
|
||||
this.editor.focus = null;
|
||||
this.world().keyboardReceiver = null;
|
||||
this.destroy();
|
||||
};
|
||||
|
||||
// ScriptFocusMorph navigation
|
||||
|
||||
ScriptFocusMorph.prototype.lastElement = function () {
|
||||
var items = this.items(),
|
||||
idx;
|
||||
if (!items.length) {
|
||||
this.shiftScript(new Point(-50, 0));
|
||||
return;
|
||||
}
|
||||
if (this.atEnd) {
|
||||
this.element = items[items.length - 1];
|
||||
this.atEnd = false;
|
||||
} else {
|
||||
idx = items.indexOf(this.element) - 1;
|
||||
if (idx < 0) {idx = items.length - 1; }
|
||||
this.element = items[idx];
|
||||
}
|
||||
if (this.element instanceof CommandSlotMorph &&
|
||||
this.element.nestedBlock()) {
|
||||
this.lastElement();
|
||||
} else if (this.element instanceof HatBlockMorph) {
|
||||
if (items.length > 1) {
|
||||
this.lastElement();
|
||||
} else {
|
||||
this.atEnd = true;
|
||||
}
|
||||
}
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.nextElement = function () {
|
||||
var items = this.items(), idx, nb;
|
||||
if (!items.length) {
|
||||
this.shiftScript(new Point(50, 0));
|
||||
return;
|
||||
}
|
||||
idx = items.indexOf(this.element) + 1;
|
||||
if (idx >= items.length) {
|
||||
idx = 0;
|
||||
}
|
||||
this.atEnd = false;
|
||||
this.element = items[idx];
|
||||
if (this.element instanceof CommandSlotMorph) {
|
||||
nb = this.element.nestedBlock();
|
||||
if (nb) {this.element = nb; }
|
||||
} else if (this.element instanceof HatBlockMorph) {
|
||||
if (items.length === 1) {
|
||||
this.atEnd = true;
|
||||
} else {
|
||||
this.nextElement();
|
||||
}
|
||||
}
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.lastCommand = function () {
|
||||
var cm = this.element.parentThatIsA(CommandBlockMorph),
|
||||
pb;
|
||||
if (!cm) {
|
||||
if (this.element instanceof ScriptsMorph) {
|
||||
this.shiftScript(new Point(0, -50));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (this.element instanceof CommandBlockMorph) {
|
||||
if (this.atEnd) {
|
||||
this.atEnd = false;
|
||||
} else {
|
||||
pb = cm.parent.parentThatIsA(CommandBlockMorph);
|
||||
if (pb) {
|
||||
this.element = pb;
|
||||
} else {
|
||||
pb = cm.topBlock().bottomBlock();
|
||||
if (pb) {
|
||||
this.element = pb;
|
||||
this.atEnd = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.element = cm;
|
||||
this.atEnd = false;
|
||||
}
|
||||
if (this.element instanceof HatBlockMorph && !this.atEnd) {
|
||||
this.lastCommand();
|
||||
}
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.nextCommand = function () {
|
||||
var cm = this.element,
|
||||
tb,
|
||||
nb,
|
||||
cs;
|
||||
if (cm instanceof ScriptsMorph) {
|
||||
this.shiftScript(new Point(0, 50));
|
||||
return;
|
||||
}
|
||||
while (!(cm instanceof CommandBlockMorph)) {
|
||||
cm = cm.parent;
|
||||
if (cm instanceof ScriptsMorph) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.atEnd) {
|
||||
cs = cm.parentThatIsA(CommandSlotMorph);
|
||||
if (cs) {
|
||||
this.element = cs.parentThatIsA(CommandBlockMorph);
|
||||
this.atEnd = false;
|
||||
this.nextCommand();
|
||||
} else {
|
||||
tb = cm.topBlock().parentThatIsA(CommandBlockMorph);
|
||||
if (tb) {
|
||||
this.element = tb;
|
||||
this.atEnd = false;
|
||||
if (this.element instanceof HatBlockMorph) {
|
||||
this.nextCommand();
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nb = cm.nextBlock();
|
||||
if (nb) {
|
||||
this.element = nb;
|
||||
} else {
|
||||
this.element = cm;
|
||||
this.atEnd = true;
|
||||
}
|
||||
}
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.nextScript = function () {
|
||||
var scripts = this.sortedScripts(),
|
||||
idx;
|
||||
if (scripts.length < 1) {return; }
|
||||
if (this.element instanceof ScriptsMorph) {
|
||||
this.element = scripts[0];
|
||||
}
|
||||
idx = scripts.indexOf(this.element.topBlock()) + 1;
|
||||
if (idx >= scripts.length) {idx = 0; }
|
||||
this.element = scripts[idx];
|
||||
this.element.scrollIntoView();
|
||||
this.atEnd = false;
|
||||
if (this.element instanceof HatBlockMorph) {
|
||||
return this.nextElement();
|
||||
}
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.lastScript = function () {
|
||||
var scripts = this.sortedScripts(),
|
||||
idx;
|
||||
if (scripts.length < 1) {return; }
|
||||
if (this.element instanceof ScriptsMorph) {
|
||||
this.element = scripts[0];
|
||||
}
|
||||
idx = scripts.indexOf(this.element.topBlock()) - 1;
|
||||
if (idx < 0) {idx = scripts.length - 1; }
|
||||
this.element = scripts[idx];
|
||||
this.element.scrollIntoView();
|
||||
this.atEnd = false;
|
||||
if (this.element instanceof HatBlockMorph) {
|
||||
return this.nextElement();
|
||||
}
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.shiftScript = function (deltaPoint) {
|
||||
var tb;
|
||||
if (this.element instanceof ScriptsMorph) {
|
||||
this.moveBy(deltaPoint);
|
||||
} else {
|
||||
tb = this.element.topBlock();
|
||||
if (tb && !(tb instanceof PrototypeHatBlockMorph)) {
|
||||
tb.moveBy(deltaPoint);
|
||||
}
|
||||
}
|
||||
this.editor.adjustBounds();
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.newScript = function () {
|
||||
var pos = this.position();
|
||||
if (!(this.element instanceof ScriptsMorph)) {
|
||||
pos = this.element.topBlock().fullBounds().bottomLeft().add(
|
||||
new Point(0, 50)
|
||||
);
|
||||
}
|
||||
this.setPosition(pos);
|
||||
this.element = this.editor;
|
||||
this.editor.adjustBounds();
|
||||
this.fixLayout();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.runScript = function () {
|
||||
if (this.element instanceof ScriptsMorph) {return; }
|
||||
this.element.topBlock().mouseClickLeft();
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.items = function () {
|
||||
if (this.element instanceof ScriptsMorph) {return []; }
|
||||
var script = this.element.topBlock();
|
||||
return script.allChildren().filter(function (each) {
|
||||
return each instanceof SyntaxElementMorph &&
|
||||
!(each instanceof TemplateSlotMorph) &&
|
||||
(!each.isStatic ||
|
||||
each.choices ||
|
||||
each instanceof MultiArgMorph ||
|
||||
each instanceof CommandSlotMorph);
|
||||
});
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.sortedScripts = function () {
|
||||
var scripts = this.editor.children.filter(function (each) {
|
||||
return each instanceof BlockMorph;
|
||||
});
|
||||
scripts.sort(function (a, b) {
|
||||
// make sure the prototype hat block always stays on top
|
||||
return a instanceof PrototypeHatBlockMorph ? 0 : a.top() - b.top();
|
||||
});
|
||||
return scripts;
|
||||
};
|
||||
|
||||
// ScriptFocusMorph block types
|
||||
|
||||
ScriptFocusMorph.prototype.blockTypes = function () {
|
||||
// answer an array of possible block types that fit into
|
||||
// the current situation, NULL if no block can be inserted
|
||||
|
||||
if (this.element.isTemplate) {return null; }
|
||||
if (this.element instanceof ScriptsMorph) {
|
||||
return ['hat', 'command', 'reporter', 'predicate', 'ring'];
|
||||
}
|
||||
if (this.element instanceof HatBlockMorph ||
|
||||
this.element instanceof CommandSlotMorph) {
|
||||
return ['command'];
|
||||
}
|
||||
if (this.element instanceof CommandBlockMorph) {
|
||||
if (this.atEnd && this.element.isStop()) {
|
||||
return null;
|
||||
}
|
||||
if (this.element.parent instanceof ScriptsMorph) {
|
||||
return ['hat', 'command'];
|
||||
}
|
||||
return ['command'];
|
||||
}
|
||||
if (this.element instanceof ReporterBlockMorph) {
|
||||
if (this.element.getSlotSpec() === '%n') {
|
||||
return ['reporter'];
|
||||
}
|
||||
return ['reporter', 'predicate', 'ring'];
|
||||
}
|
||||
if (this.element.getSpec() === '%n') {
|
||||
return ['reporter'];
|
||||
}
|
||||
if (this.element.isStatic) {
|
||||
return null;
|
||||
}
|
||||
return ['reporter', 'predicate', 'ring'];
|
||||
};
|
||||
|
||||
|
||||
// ScriptFocusMorph keyboard events
|
||||
|
||||
ScriptFocusMorph.prototype.processKeyDown = function (event) {
|
||||
this.processKeyEvent(
|
||||
event,
|
||||
this.reactToKeyEvent
|
||||
);
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.processKeyUp = function (event) {
|
||||
nop(event);
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.processKeyPress = function (event) {
|
||||
nop(event);
|
||||
};
|
||||
|
||||
|
||||
ScriptFocusMorph.prototype.processKeyEvent = function (event, action) {
|
||||
var keyName, ctrl, shift;
|
||||
|
||||
//console.log(event.keyCode);
|
||||
this.world().hand.destroyTemporaries(); // remove result bubbles, if any
|
||||
switch (event.keyCode) {
|
||||
case 8:
|
||||
keyName = 'backspace';
|
||||
break;
|
||||
case 9:
|
||||
keyName = 'tab';
|
||||
break;
|
||||
case 13:
|
||||
keyName = 'enter';
|
||||
break;
|
||||
case 16:
|
||||
case 17:
|
||||
case 18:
|
||||
return;
|
||||
case 27:
|
||||
keyName = 'esc';
|
||||
break;
|
||||
case 32:
|
||||
keyName = 'space';
|
||||
break;
|
||||
case 37:
|
||||
keyName = 'left arrow';
|
||||
break;
|
||||
case 39:
|
||||
keyName = 'right arrow';
|
||||
break;
|
||||
case 38:
|
||||
keyName = 'up arrow';
|
||||
break;
|
||||
case 40:
|
||||
keyName = 'down arrow';
|
||||
break;
|
||||
default:
|
||||
keyName = String.fromCharCode(event.keyCode || event.charCode);
|
||||
}
|
||||
ctrl = (event.ctrlKey || event.metaKey) ? 'ctrl ' : '';
|
||||
shift = event.shiftKey ? 'shift ' : '';
|
||||
keyName = ctrl + shift + keyName;
|
||||
action.call(this, keyName);
|
||||
};
|
||||
|
||||
ScriptFocusMorph.prototype.reactToKeyEvent = function (key) {
|
||||
var evt = key.toLowerCase(),
|
||||
shift = 50,
|
||||
types,
|
||||
vNames;
|
||||
|
||||
// console.log(evt);
|
||||
switch (evt) {
|
||||
case 'esc':
|
||||
return this.stopEditing();
|
||||
case 'enter':
|
||||
return this.trigger();
|
||||
case 'shift enter':
|
||||
return this.newScript();
|
||||
case 'ctrl shift enter':
|
||||
return this.runScript();
|
||||
case 'space':
|
||||
return this.menu();
|
||||
case 'left arrow':
|
||||
return this.lastElement();
|
||||
case 'shift left arrow':
|
||||
return this.shiftScript(new Point(-shift, 0));
|
||||
case 'right arrow':
|
||||
return this.nextElement();
|
||||
case 'shift right arrow':
|
||||
return this.shiftScript(new Point(shift, 0));
|
||||
case 'up arrow':
|
||||
return this.lastCommand();
|
||||
case 'shift up arrow':
|
||||
return this.shiftScript(new Point(0, -shift));
|
||||
case 'down arrow':
|
||||
return this.nextCommand();
|
||||
case 'shift down arrow':
|
||||
return this.shiftScript(new Point(0, shift));
|
||||
case 'tab':
|
||||
return this.nextScript();
|
||||
case 'shift tab':
|
||||
return this.lastScript();
|
||||
case 'backspace':
|
||||
return this.deleteLastElement();
|
||||
default:
|
||||
types = this.blockTypes();
|
||||
if (!(this.element instanceof ScriptsMorph) &&
|
||||
contains(types, 'reporter')) {
|
||||
vNames = Object.keys(this.element.getVarNamesDict());
|
||||
}
|
||||
if (types) {
|
||||
delete this.fps;
|
||||
delete this.step;
|
||||
this.show();
|
||||
this.editor.owner.searchBlocks(
|
||||
key,
|
||||
types,
|
||||
vNames,
|
||||
this
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
9
byob.js
9
byob.js
|
@ -106,7 +106,7 @@ SymbolMorph, isNil*/
|
|||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.byob = '2015-June-25';
|
||||
modules.byob = '2015-July-26';
|
||||
|
||||
// Declarations
|
||||
|
||||
|
@ -1966,8 +1966,13 @@ PrototypeHatBlockMorph.prototype.init = function (definition) {
|
|||
|
||||
PrototypeHatBlockMorph.prototype.mouseClickLeft = function () {
|
||||
// relay the mouse click to my prototype block to
|
||||
// pop-up a Block Dialog
|
||||
// pop-up a Block Dialog, unless the shift key
|
||||
// is pressed, in which case initiate keyboard
|
||||
// editing support
|
||||
|
||||
if (this.world().currentKey === 16) { // shift-clicked
|
||||
return this.focus();
|
||||
}
|
||||
this.children[0].mouseClickLeft();
|
||||
};
|
||||
|
||||
|
|
48
gui.js
48
gui.js
|
@ -65,11 +65,11 @@ ScriptsMorph, isNil, SymbolMorph, BlockExportDialogMorph,
|
|||
BlockImportDialogMorph, SnapTranslator, localize, List, InputSlotMorph,
|
||||
SnapCloud, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512,
|
||||
sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio,
|
||||
SpeechBubbleMorph*/
|
||||
SpeechBubbleMorph, ScriptFocusMorph*/
|
||||
|
||||
// Global stuff ////////////////////////////////////////////////////////
|
||||
|
||||
modules.gui = '2015-June-25';
|
||||
modules.gui = '2015-July-26';
|
||||
|
||||
// Declarations
|
||||
|
||||
|
@ -1806,7 +1806,8 @@ IDE_Morph.prototype.applySavedSettings = function () {
|
|||
click = this.getSetting('click'),
|
||||
longform = this.getSetting('longform'),
|
||||
longurls = this.getSetting('longurls'),
|
||||
plainprototype = this.getSetting('plainprototype');
|
||||
plainprototype = this.getSetting('plainprototype'),
|
||||
keyboard = this.getSetting('keyboard');
|
||||
|
||||
// design
|
||||
if (design === 'flat') {
|
||||
|
@ -1846,6 +1847,13 @@ IDE_Morph.prototype.applySavedSettings = function () {
|
|||
this.projectsInURLs = false;
|
||||
}
|
||||
|
||||
// keyboard editing
|
||||
if (keyboard) {
|
||||
ScriptsMorph.prototype.enableKeyboard = true;
|
||||
} else {
|
||||
ScriptsMorph.prototype.enableKeyboard = false;
|
||||
}
|
||||
|
||||
// plain prototype labels
|
||||
if (plainprototype) {
|
||||
BlockLabelPlaceHolderMorph.prototype.plainLabel = true;
|
||||
|
@ -2352,6 +2360,22 @@ IDE_Morph.prototype.settingsMenu = function () {
|
|||
'check to enable\nsprite composition',
|
||||
true
|
||||
);
|
||||
addPreference(
|
||||
'Keyboard Editing',
|
||||
function () {
|
||||
ScriptsMorph.prototype.enableKeyboard =
|
||||
!ScriptsMorph.prototype.enableKeyboard;
|
||||
if (ScriptsMorph.prototype.enableKeyboard) {
|
||||
myself.saveSetting('keyboard', true);
|
||||
} else {
|
||||
myself.removeSetting('keyboard');
|
||||
}
|
||||
},
|
||||
ScriptsMorph.prototype.enableKeyboard,
|
||||
'uncheck to disable\nkeyboard editing support',
|
||||
'check to enable\nkeyboard editing support',
|
||||
false
|
||||
);
|
||||
menu.addLine(); // everything below this line is stored in the project
|
||||
addPreference(
|
||||
'Thread safe scripts',
|
||||
|
@ -2840,6 +2864,7 @@ IDE_Morph.prototype.newProject = function () {
|
|||
StageMorph.prototype.codeMappings = {};
|
||||
StageMorph.prototype.codeHeaders = {};
|
||||
StageMorph.prototype.enableCodeMapping = false;
|
||||
StageMorph.prototype.enableInheritance = false;
|
||||
SpriteMorph.prototype.useFlatLineEnds = false;
|
||||
this.setProjectName('');
|
||||
this.projectNotes = '';
|
||||
|
@ -3059,6 +3084,7 @@ IDE_Morph.prototype.rawOpenProjectString = function (str) {
|
|||
StageMorph.prototype.codeMappings = {};
|
||||
StageMorph.prototype.codeHeaders = {};
|
||||
StageMorph.prototype.enableCodeMapping = false;
|
||||
StageMorph.prototype.enableInheritance = false;
|
||||
if (Process.prototype.isCatchingErrors) {
|
||||
try {
|
||||
this.serializer.openProject(
|
||||
|
@ -3100,6 +3126,7 @@ IDE_Morph.prototype.rawOpenCloudDataString = function (str) {
|
|||
StageMorph.prototype.codeMappings = {};
|
||||
StageMorph.prototype.codeHeaders = {};
|
||||
StageMorph.prototype.enableCodeMapping = false;
|
||||
StageMorph.prototype.enableInheritance = false;
|
||||
if (Process.prototype.isCatchingErrors) {
|
||||
try {
|
||||
model = this.serializer.parse(str);
|
||||
|
@ -3446,6 +3473,9 @@ IDE_Morph.prototype.toggleAppMode = function (appMode) {
|
|||
morph.hide();
|
||||
}
|
||||
});
|
||||
if (world.keyboardReceiver instanceof ScriptFocusMorph) {
|
||||
world.keyboardReceiver.stopEditing();
|
||||
}
|
||||
} else {
|
||||
this.setColor(this.backgroundColor);
|
||||
this.controlBar.setColor(this.frameColor);
|
||||
|
@ -5220,7 +5250,7 @@ function SpriteIconMorph(aSprite, aTemplate) {
|
|||
}
|
||||
|
||||
SpriteIconMorph.prototype.init = function (aSprite, aTemplate) {
|
||||
var colors, action, query, myself = this;
|
||||
var colors, action, query, hover, myself = this;
|
||||
|
||||
if (!aTemplate) {
|
||||
colors = [
|
||||
|
@ -5250,6 +5280,11 @@ SpriteIconMorph.prototype.init = function (aSprite, aTemplate) {
|
|||
return false;
|
||||
};
|
||||
|
||||
hover = function () {
|
||||
if (!aSprite.exemplar) {return null; }
|
||||
return (localize('parent' + ':\n' + aSprite.exemplar.name));
|
||||
};
|
||||
|
||||
// additional properties:
|
||||
this.object = aSprite || new SpriteMorph(); // mandatory, actually
|
||||
this.version = this.object.version;
|
||||
|
@ -5265,7 +5300,7 @@ SpriteIconMorph.prototype.init = function (aSprite, aTemplate) {
|
|||
this.object.name, // label string
|
||||
query, // predicate/selector
|
||||
null, // environment
|
||||
null, // hint
|
||||
hover, // hint
|
||||
aTemplate // optional, for cached background images
|
||||
);
|
||||
|
||||
|
@ -5436,6 +5471,9 @@ SpriteIconMorph.prototype.userMenu = function () {
|
|||
menu.addItem("duplicate", 'duplicateSprite');
|
||||
menu.addItem("delete", 'removeSprite');
|
||||
menu.addLine();
|
||||
if (StageMorph.prototype.enableInheritance) {
|
||||
menu.addItem("parent...", 'chooseExemplar');
|
||||
}
|
||||
if (this.object.anchor) {
|
||||
menu.addItem(
|
||||
localize('detach from') + ' ' + this.object.anchor.name,
|
||||
|
|
164
objects.js
164
objects.js
|
@ -900,17 +900,20 @@ SpriteMorph.prototype.initBlocks = function () {
|
|||
reifyScript: {
|
||||
type: 'ring',
|
||||
category: 'other',
|
||||
spec: '%rc %ringparms'
|
||||
spec: '%rc %ringparms',
|
||||
alias: 'command ring lambda'
|
||||
},
|
||||
reifyReporter: {
|
||||
type: 'ring',
|
||||
category: 'other',
|
||||
spec: '%rr %ringparms'
|
||||
spec: '%rr %ringparms',
|
||||
alias: 'reporter ring lambda'
|
||||
},
|
||||
reifyPredicate: {
|
||||
type: 'ring',
|
||||
category: 'other',
|
||||
spec: '%rp %ringparms'
|
||||
spec: '%rp %ringparms',
|
||||
alias: 'predicate ring lambda'
|
||||
},
|
||||
reportSum: {
|
||||
type: 'reporter',
|
||||
|
@ -920,12 +923,14 @@ SpriteMorph.prototype.initBlocks = function () {
|
|||
reportDifference: {
|
||||
type: 'reporter',
|
||||
category: 'operators',
|
||||
spec: '%n \u2212 %n'
|
||||
spec: '%n \u2212 %n',
|
||||
alias: '-'
|
||||
},
|
||||
reportProduct: {
|
||||
type: 'reporter',
|
||||
category: 'operators',
|
||||
spec: '%n \u00D7 %n'
|
||||
spec: '%n \u00D7 %n',
|
||||
alias: '*'
|
||||
},
|
||||
reportQuotient: {
|
||||
type: 'reporter',
|
||||
|
@ -2357,15 +2362,30 @@ SpriteMorph.prototype.freshPalette = function (category) {
|
|||
|
||||
// SpriteMorph blocks searching
|
||||
|
||||
SpriteMorph.prototype.blocksMatching = function (searchString, strictly) {
|
||||
SpriteMorph.prototype.blocksMatching = function (
|
||||
searchString,
|
||||
strictly,
|
||||
types, // optional, ['hat', 'command', 'reporter', 'predicate']
|
||||
varNames // optional, list of reachable unique variable names
|
||||
) {
|
||||
// answer an array of block templates whose spec contains
|
||||
// the given search string, ordered by descending relevance
|
||||
// types is an optional array containing block types the search
|
||||
// is limited to, e.g. "command", "hat", "reporter", "predicate".
|
||||
// Note that "predicate" is not subsumed by "reporter" and has
|
||||
// to be specified explicitly.
|
||||
// if no types are specified all blocks are searched
|
||||
var blocks = [],
|
||||
blocksDict,
|
||||
myself = this,
|
||||
search = searchString.toLowerCase(),
|
||||
stage = this.parentThatIsA(StageMorph);
|
||||
|
||||
if (!types || !types.length) {
|
||||
types = ['hat', 'command', 'reporter', 'predicate', 'ring'];
|
||||
}
|
||||
if (!varNames) {varNames = []; }
|
||||
|
||||
function labelOf(aBlockSpec) {
|
||||
var words = (BlockMorph.prototype.parseSpec(aBlockSpec)),
|
||||
filtered = words.filter(
|
||||
|
@ -2396,29 +2416,39 @@ SpriteMorph.prototype.blocksMatching = function (searchString, strictly) {
|
|||
return newBlock;
|
||||
}
|
||||
|
||||
// variable getters
|
||||
varNames.forEach(function (vName) {
|
||||
var rel = relevance(labelOf(vName), search);
|
||||
if (rel !== -1) {
|
||||
blocks.push([myself.variableBlock(vName), rel + '1']);
|
||||
}
|
||||
});
|
||||
// custom blocks
|
||||
[this.customBlocks, stage.globalBlocks].forEach(function (blocksList) {
|
||||
blocksList.forEach(function (definition) {
|
||||
var spec = localize(definition.blockSpec()).toLowerCase(),
|
||||
rel = relevance(labelOf(spec), search);
|
||||
if (rel !== -1) {
|
||||
blocks.push([definition.templateInstance(), rel + '1']);
|
||||
if (contains(types, definition.type)) {
|
||||
var spec = localize(definition.blockSpec()).toLowerCase(),
|
||||
rel = relevance(labelOf(spec), search);
|
||||
if (rel !== -1) {
|
||||
blocks.push([definition.templateInstance(), rel + '2']);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
// primitives
|
||||
blocksDict = SpriteMorph.prototype.blocks;
|
||||
Object.keys(blocksDict).forEach(function (selector) {
|
||||
if (!StageMorph.prototype.hiddenPrimitives[selector]) {
|
||||
if (!StageMorph.prototype.hiddenPrimitives[selector] &&
|
||||
contains(types, blocksDict[selector].type)) {
|
||||
var block = blocksDict[selector],
|
||||
spec = localize(block.spec).toLowerCase(),
|
||||
spec = localize(block.alias || block.spec).toLowerCase(),
|
||||
rel = relevance(labelOf(spec), search);
|
||||
if (
|
||||
(rel !== -1) &&
|
||||
(!block.dev) &&
|
||||
(!block.only || (block.only === myself.constructor))
|
||||
) {
|
||||
blocks.push([primitive(selector), rel + '2']);
|
||||
blocks.push([primitive(selector), rel + '3']);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -2426,18 +2456,43 @@ SpriteMorph.prototype.blocksMatching = function (searchString, strictly) {
|
|||
return blocks.map(function (each) {return each[0]; });
|
||||
};
|
||||
|
||||
SpriteMorph.prototype.searchBlocks = function () {
|
||||
SpriteMorph.prototype.searchBlocks = function (
|
||||
searchString,
|
||||
types,
|
||||
varNames,
|
||||
scriptFocus
|
||||
) {
|
||||
var myself = this,
|
||||
unit = SyntaxElementMorph.prototype.fontSize,
|
||||
ide = this.parentThatIsA(IDE_Morph),
|
||||
oldSearch = '',
|
||||
searchBar = new InputFieldMorph(''),
|
||||
searchPane = ide.createPalette('forSearch');
|
||||
searchBar = new InputFieldMorph(searchString || ''),
|
||||
searchPane = ide.createPalette('forSearch'),
|
||||
blocksList = [],
|
||||
selection,
|
||||
focus;
|
||||
|
||||
function showSelection() {
|
||||
if (focus) {focus.destroy(); }
|
||||
if (!selection || !scriptFocus) {return; }
|
||||
focus = selection.outline(
|
||||
MorphicPreferences.isFlat ? new Color(150, 200, 255)
|
||||
: new Color(255, 255, 255),
|
||||
2
|
||||
);
|
||||
searchPane.contents.add(focus);
|
||||
focus.scrollIntoView();
|
||||
}
|
||||
|
||||
function show(blocks) {
|
||||
var oldFlag = Morph.prototype.trackChanges,
|
||||
x = searchPane.contents.left() + 5,
|
||||
y = (searchBar.bottom() + unit);
|
||||
blocksList = blocks;
|
||||
selection = null;
|
||||
if (blocks.length && scriptFocus) {
|
||||
selection = blocks[0];
|
||||
}
|
||||
Morph.prototype.trackChanges = false;
|
||||
searchPane.contents.children = [searchPane.contents.children[0]];
|
||||
blocks.forEach(function (block) {
|
||||
|
@ -2447,6 +2502,7 @@ SpriteMorph.prototype.searchBlocks = function () {
|
|||
y += unit * 0.3;
|
||||
});
|
||||
Morph.prototype.trackChanges = oldFlag;
|
||||
showSelection();
|
||||
searchPane.changed();
|
||||
}
|
||||
|
||||
|
@ -2463,17 +2519,52 @@ SpriteMorph.prototype.searchBlocks = function () {
|
|||
searchBar.drawNew();
|
||||
|
||||
searchPane.accept = function () {
|
||||
var search = searchBar.getValue();
|
||||
if (search.length > 0) {
|
||||
show(myself.blocksMatching(search));
|
||||
var search;
|
||||
if (scriptFocus) {
|
||||
searchBar.cancel();
|
||||
if (selection) {
|
||||
scriptFocus.insertBlock(selection);
|
||||
}
|
||||
} else {
|
||||
search = searchBar.getValue();
|
||||
if (search.length > 0) {
|
||||
show(myself.blocksMatching(search));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
searchPane.reactToKeystroke = function () {
|
||||
var search = searchBar.getValue();
|
||||
if (search !== oldSearch) {
|
||||
oldSearch = search;
|
||||
show(myself.blocksMatching(search, search.length < 2));
|
||||
searchPane.reactToKeystroke = function (evt) {
|
||||
var search, idx, code = evt ? evt.keyCode : 0;
|
||||
switch (code) {
|
||||
case 38: // up arrow
|
||||
if (!scriptFocus || !selection) {return; }
|
||||
idx = blocksList.indexOf(selection) - 1;
|
||||
if (idx < 0) {
|
||||
idx = blocksList.length - 1;
|
||||
}
|
||||
selection = blocksList[idx];
|
||||
showSelection();
|
||||
return;
|
||||
case 40: // down arrow
|
||||
if (!scriptFocus || !selection) {return; }
|
||||
idx = blocksList.indexOf(selection) + 1;
|
||||
if (idx >= blocksList.length) {
|
||||
idx = 0;
|
||||
}
|
||||
selection = blocksList[idx];
|
||||
showSelection();
|
||||
return;
|
||||
default:
|
||||
search = searchBar.getValue();
|
||||
if (search !== oldSearch) {
|
||||
oldSearch = search;
|
||||
show(myself.blocksMatching(
|
||||
search,
|
||||
search.length < 2,
|
||||
types,
|
||||
varNames
|
||||
));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2484,6 +2575,7 @@ SpriteMorph.prototype.searchBlocks = function () {
|
|||
|
||||
ide.fixLayout('refreshPalette');
|
||||
searchBar.edit();
|
||||
if (searchString) {searchPane.reactToKeystroke(); }
|
||||
};
|
||||
|
||||
// SpriteMorph variable management
|
||||
|
@ -4791,6 +4883,8 @@ StageMorph.prototype.processKeyEvent = function (event, action) {
|
|||
keyName = 'enter';
|
||||
if (event.ctrlKey || event.metaKey) {
|
||||
keyName = 'ctrl enter';
|
||||
} else if (event.shiftKey) {
|
||||
keyName = 'shift enter';
|
||||
}
|
||||
break;
|
||||
case 27:
|
||||
|
@ -4831,6 +4925,9 @@ StageMorph.prototype.fireKeyEvent = function (key) {
|
|||
if (evt === 'ctrl enter') {
|
||||
return this.fireGreenFlagEvent();
|
||||
}
|
||||
if (evt === 'shift enter') {
|
||||
return this.editScripts();
|
||||
}
|
||||
if (evt === 'ctrl f') {
|
||||
if (!ide.isAppMode) {ide.currentSprite.searchBlocks(); }
|
||||
return;
|
||||
|
@ -4931,6 +5028,25 @@ StageMorph.prototype.removeAllClones = function () {
|
|||
this.cloneCount = 0;
|
||||
};
|
||||
|
||||
StageMorph.prototype.editScripts = function () {
|
||||
var ide = this.parentThatIsA(IDE_Morph),
|
||||
scripts,
|
||||
sorted;
|
||||
if (ide.isAppMode || !ScriptsMorph.prototype.enableKeyboard) {return; }
|
||||
scripts = this.parentThatIsA(IDE_Morph).currentSprite.scripts;
|
||||
scripts.edit(scripts.position());
|
||||
sorted = scripts.focus.sortedScripts();
|
||||
if (sorted.length) {
|
||||
scripts.focus.element = sorted[0];
|
||||
if (scripts.focus.element instanceof HatBlockMorph) {
|
||||
scripts.focus.nextCommand();
|
||||
}
|
||||
} else {
|
||||
scripts.focus.moveBy(new Point(50, 50));
|
||||
}
|
||||
scripts.focus.fixLayout();
|
||||
};
|
||||
|
||||
// StageMorph block templates
|
||||
|
||||
StageMorph.prototype.blockTemplates = function (category) {
|
||||
|
|
Ładowanie…
Reference in New Issue