Changeset 8036


Ignore:
Timestamp:
Sep 6, 2018, 12:19:27 PM (12 months ago)
Author:
jrpelegrina
Message:

Update files to version 4.2.1.3

Location:
byob-snap/trunk/fuentes/install/usr/share/byob-snap
Files:
18 added
127 edited

Legend:

Unmodified
Added
Removed
  • byob-snap/trunk/fuentes/install/usr/share/byob-snap/FileSaver.min.js

    r2503 r8036  
    11/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
    2 var saveAs=saveAs||function(e){"use strict";if(typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),i="download"in r,o=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),f=e.webkitRequestFileSystem,u=e.requestFileSystem||f||e.mozRequestFileSystem,s=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},c="application/octet-stream",d=0,l=500,w=function(t){var r=function(){if(typeof t==="string"){n().revokeObjectURL(t)}else{t.remove()}};if(e.chrome){r()}else{setTimeout(r,l)}},p=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var i=e["on"+t[r]];if(typeof i==="function"){try{i.call(e,n||e)}catch(o){s(o)}}}},v=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob(["\ufeff",e],{type:e.type})}return e},y=function(t,s,l){if(!l){t=v(t)}var y=this,m=t.type,S=false,h,R,O=function(){p(y,"writestart progress write writeend".split(" "))},g=function(){if(R&&a&&typeof FileReader!=="undefined"){var r=new FileReader;r.onloadend=function(){var e=r.result;R.location.href="data:attachment/file"+e.slice(e.search(/[,;]/));y.readyState=y.DONE;O()};r.readAsDataURL(t);y.readyState=y.INIT;return}if(S||!h){h=n().createObjectURL(t)}if(R){R.location.href=h}else{var i=e.open(h,"_blank");if(i==undefined&&a){e.location.href=h}}y.readyState=y.DONE;O();w(h)},b=function(e){return function(){if(y.readyState!==y.DONE){return e.apply(this,arguments)}}},E={create:true,exclusive:false},N;y.readyState=y.INIT;if(!s){s="download"}if(i){h=n().createObjectURL(t);r.href=h;r.download=s;setTimeout(function(){o(r);O();w(h);y.readyState=y.DONE});return}if(e.chrome&&m&&m!==c){N=t.slice||t.webkitSlice;t=N.call(t,0,t.size,c);S=true}if(f&&s!=="download"){s+=".download"}if(m===c||f){R=e}if(!u){g();return}d+=t.size;u(e.TEMPORARY,d,b(function(e){e.root.getDirectory("saved",E,b(function(e){var n=function(){e.getFile(s,E,b(function(e){e.createWriter(b(function(n){n.onwriteend=function(t){R.location.href=e.toURL();y.readyState=y.DONE;p(y,"writeend",t);w(e)};n.onerror=function(){var e=n.error;if(e.code!==e.ABORT_ERR){g()}};"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=y["on"+e]});n.write(t);y.abort=function(){n.abort();y.readyState=y.DONE};y.readyState=y.WRITING}),g)}),g)};e.getFile(s,{create:false},b(function(e){e.remove();n()}),b(function(e){if(e.code===e.NOT_FOUND_ERR){n()}else{g()}}))}),g)}),g)},m=y.prototype,S=function(e,t,n){return new y(e,t,n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){if(!n){e=v(e)}return navigator.msSaveOrOpenBlob(e,t||"download")}}m.abort=function(){var e=this;e.readyState=e.DONE;p(e,"abort")};m.readyState=m.INIT=0;m.WRITING=1;m.DONE=2;m.error=m.onwritestart=m.onprogress=m.onwrite=m.onabort=m.onerror=m.onwriteend=null;return S}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!=null){define([],function(){return saveAs})}
     2var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,a=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},i=/constructor/i.test(e.HTMLElement)||e.safari,f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},s="application/octet-stream",d=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,d)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(a){u(a)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,d){if(!d){t=p(t)}var v=this,w=t.type,m=w===s,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&i)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;a(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define("FileSaver.js",function(){return saveAs})}
  • byob-snap/trunk/fuentes/install/usr/share/byob-snap/README.md

    r2503 r8036  
    1010jens@moenig.org, bh@cs.berkeley.edu
    1111
    12 Copyright (C) 2016 by Jens Mönig and Brian Harvey
     12Copyright (C) 2018 by Jens Mönig and Brian Harvey
    1313
    1414Snap! is free software: you can redistribute it and/or modify
     
    2323
    2424You should have received a copy of the GNU Affero General Public License
    25 along with this program.  If not, see <http://www.gnu.org/licenses/>.
     25along with this program. If not, see <https://www.gnu.org/licenses/>.
     26
     27Want to use Snap! but scared by the open-source license? Get in touch with us,
     28we'll make it work.
  • byob-snap/trunk/fuentes/install/usr/share/byob-snap/blocks.js

    r2503 r8036  
    1010    jens@moenig.org
    1111
    12     Copyright (C) 2016 by Jens Mönig
     12    Copyright (C) 2018 by Jens Mönig
    1313
    1414    This file is part of Snap!.
     
    3030    prerequisites:
    3131    --------------
    32     needs morphic.js
     32    needs morphic.js and symbols.js
    3333
    3434
     
    4343            BlockHighlightMorph
    4444            ScriptsMorph
    45             SymbolMorph
    4645            SyntaxElementMorph
    4746                ArgMorph
     
    9190        ArrowMorph
    9291        TextSlotMorph
    93         SymbolMorph
    9492        ColorSlotMorph
    9593        TemplateSlotMorph
     
    140138Color, ColorPaletteMorph, FrameMorph, Function, HandleMorph, Math, MenuMorph,
    141139Morph, MorphicPreferences, Object, Point, ScrollFrameMorph, ShadowMorph,
    142 String, StringMorph, TextMorph, WorldMorph, contains, degrees, detect,
     140String, StringMorph, TextMorph, contains, degrees, detect, PianoMenuMorph,
    143141document, getDocumentPositionOf, isNaN, isString, newCanvas, nop, parseFloat,
    144 radians, useBlurredShadows, SpeechBubbleMorph, modules, StageMorph,
     142radians, useBlurredShadows, SpeechBubbleMorph, modules, StageMorph, Sound,
    145143fontHeight, TableFrameMorph, SpriteMorph, Context, ListWatcherMorph,
    146144CellMorph, DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph,
    147145Costume, IDE_Morph, BlockDialogMorph, BlockEditorMorph, localize, isNil,
    148 isSnapObject, copy, PushButtonMorph, SpriteIconMorph*/
     146isSnapObject, PushButtonMorph, SpriteIconMorph, Process, AlignmentMorph,
     147CustomCommandBlockMorph, SymbolMorph, ToggleButtonMorph, DialMorph*/
    149148
    150149// Global stuff ////////////////////////////////////////////////////////
    151150
    152 modules.blocks = '2016-July-15';
     151modules.blocks = '2018-July-13';
    153152
    154153var SyntaxElementMorph;
     
    173172var RingCommandSlotMorph;
    174173var RingReporterSlotMorph;
    175 var SymbolMorph;
    176174var CommentMorph;
    177175var ArgLabelMorph;
     
    179177var ScriptFocusMorph;
    180178
    181 WorldMorph.prototype.customMorphs = function () {
    182     // add examples to the world's demo menu
    183 
    184     return [];
    185 
    186 /*
    187     return [
    188         new SymbolMorph(
    189             'pipette',
    190             50,
    191             new Color(250, 250, 250),
    192             new Point(-1, -1),
    193             new Color(20, 20, 20)
    194         )
    195     ];
    196 */
    197 /*
    198     var sm = new ScriptsMorph();
    199     sm.setExtent(new Point(800, 600));
    200 
    201     return [
    202         new SymbolMorph(),
    203         new HatBlockMorph(),
    204         new CommandBlockMorph(),
    205         sm,
    206         new CommandSlotMorph(),
    207         new CSlotMorph(),
    208         new InputSlotMorph(),
    209         new InputSlotMorph(null, true),
    210         new BooleanSlotMorph(),
    211         new ColorSlotMorph(),
    212         new TemplateSlotMorph('foo'),
    213         new ReporterBlockMorph(),
    214         new ReporterBlockMorph(true),
    215         new ArrowMorph(),
    216         new MultiArgMorph(),
    217         new FunctionSlotMorph(),
    218         new ReporterSlotMorph(),
    219         new ReporterSlotMorph(true),
    220 //        new DialogBoxMorph('Dialog Box'),
    221 //        new InputFieldMorph('Input Field')
    222         new RingMorph(),
    223         new RingCommandSlotMorph(),
    224         new RingReporterSlotMorph(),
    225         new RingReporterSlotMorph(true)
    226     ];
    227 */
    228 };
    229 
    230 
    231179// SyntaxElementMorph //////////////////////////////////////////////////
    232180
     
    247195    outline:
    248196
    249         corner        - radius of command block rounding
     197        corner      - radius of command block rounding
    250198        rounding    - radius of reporter block rounding
    251199        edge        - width of 3D-ish shading box
    252         hatHeight    - additional top space for hat blocks
     200        hatHeight   - additional top space for hat blocks
    253201        hatWidth    - minimum width for hat blocks
    254202        rfBorder    - pixel width of reification border (grey outline)
     
    257205    jigsaw shape:
    258206
    259         inset        - distance from indentation to left edge
     207        inset       - distance from indentation to left edge
    260208        dent        - width of indentation bottom
    261209
    262210    paddings:
    263211
    264         bottomPadding    - adds to the width of the bottom most c-slot
     212        bottomPadding   - adds to the width of the bottom most c-slot
    265213        cSlotPadding    - adds to the width of the open "C" in c-slots
    266         typeInPadding    - adds pixels between text and edge in input slots
     214        typeInPadding   - adds pixels between text and edge in input slots
    267215        labelPadding    - adds left/right pixels to block labels
    268216
    269217    label:
    270218
    271         labelFontName    - <string> specific font family name
    272         labelFontStyle    - <string> generic font family name, cascaded
    273         fontSize        - duh
    274         embossing        - <Point> offset for embossing effect
    275         labelWidth        - column width, used for word wrapping
    276         labelWordWrap    - <bool> if true labels can break after each word
    277         dynamicInputLabels - <bool> if true inputs can have dynamic labels
     219        labelFontName       - <string> specific font family name
     220        labelFontStyle      - <string> generic font family name, cascaded
     221        fontSize            - duh
     222        embossing           - <Point> offset for embossing effect
     223        labelWidth          - column width, used for word wrapping
     224        labelWordWrap       - <bool> if true labels can break after each word
     225        dynamicInputLabels  - <bool> if true inputs can have dynamic labels
    278226
    279227    snapping:
    280228
    281         feedbackColor        - <Color> for displaying drop feedbacks
    282         feedbackMinHeight    - height of white line for command block snaps
    283         minSnapDistance        - threshold when commands start snapping
    284         reporterDropFeedbackPadding    - increases reporter drop feedback
     229        feedbackColor       - <Color> for displaying drop feedbacks
     230        feedbackMinHeight   - height of white line for command block snaps
     231        minSnapDistance     - threshold when commands start snapping
     232        reporterDropFeedbackPadding  - increases reporter drop feedback
    285233
    286234    color gradients:
    287235
    288236        contrast        - <percent int> 3D-ish shading gradient contrast
    289         labelContrast    - <percent int> 3D-ish label shading contrast
    290         activeHighlight    - <Color> for stack highlighting when active
    291         errorHighlight    - <Color> for error highlighting
    292         activeBlur        - <pixels int> shadow for blurred activeHighlight
     237        labelContrast   - <percent int> 3D-ish label shading contrast
     238        activeHighlight - <Color> for stack highlighting when active
     239        errorHighlight  - <Color> for error highlighting
     240        activeBlur      - <pixels int> shadow for blurred activeHighlight
    293241        activeBorder    - <pixels int> unblurred activeHighlight
    294         rfColor            - <Color> for reified outlines and slot backgrounds
     242        rfColor         - <Color> for reified outlines and slot backgrounds
    295243*/
    296244
     
    347295    this.cachedClrBright = null;
    348296    this.cachedClrDark = null;
     297    this.cachedNormalColor = null; // for single-stepping
    349298    this.isStatic = false; // if true, I cannot be exchanged
    350299
     
    561510    var idx = this.parts().indexOf(arg),
    562511        inp = this.inputs().indexOf(arg),
    563         deflt = new InputSlotMorph();
     512        deflt = new InputSlotMorph(),
     513        def;
    564514
    565515    if (idx !== -1) {
    566516        if (this instanceof BlockMorph) {
    567517            deflt = this.labelPart(this.parseSpec(this.blockSpec)[idx]);
    568             if (deflt instanceof InputSlotMorph && this.definition) {
    569                 deflt.setChoices.apply(
    570                     deflt,
    571                     this.definition.inputOptionsOfIdx(inp)
    572                 );
    573                 deflt.setContents(
    574                     this.definition.defaultValueOfInputIdx(inp)
    575                 );
     518            if (this.isCustomBlock) {
     519                def = this.isGlobal ? this.definition
     520                        : this.scriptTarget().getMethod(this.blockSpec);
     521                if (deflt instanceof InputSlotMorph) {
     522                    deflt.setChoices.apply(
     523                        deflt,
     524                        def.inputOptionsOfIdx(inp)
     525                    );
     526                }
     527                if (deflt instanceof InputSlotMorph ||
     528                    (deflt instanceof BooleanSlotMorph)
     529                ) {
     530                    deflt.setContents(
     531                        def.defaultValueOfInputIdx(inp)
     532                    );
     533                }
    576534            }
    577535        } else if (this instanceof MultiArgMorph) {
     
    626584        return {};
    627585    }
    628     rcvr = block.receiver();
     586    rcvr = block.scriptTarget();
    629587    block.allParents().forEach(function (morph) {
    630588        if (morph instanceof PrototypeHatBlockMorph) {
     
    656614            dict[name] = name;
    657615        });
     616        if (block.selector === 'doSetVar') {
     617            // add settable object attributes
     618            dict['~'] = null;
     619            dict.my = {
     620                'anchor' : ['anchor'],
     621                'parent' : ['parent'],
     622                // 'temporary?' : ['temporary?'],
     623                'dangling?' : ['dangling?'],
     624                'rotation x' : ['rotation x'],
     625                'rotation y' : ['rotation y']
     626            };
     627        }
    658628        return dict;
    659629    }
    660630    return {};
     631};
     632
     633// Variable refactoring
     634
     635SyntaxElementMorph.prototype.refactorVarInStack = function (
     636    oldName,
     637    newName,
     638    isScriptVar
     639) {
     640    // Rename all oldName var occurrences found in this block stack into newName
     641    // taking care of not being too greedy
     642
     643    if ((this instanceof RingMorph && contains(this.inputNames(), oldName))
     644            || (!isScriptVar && this.definesScriptVariable(oldName))) {
     645        return;
     646    }
     647
     648    if (this.selector === 'reportGetVar'
     649            && this.blockSpec === oldName) {
     650        this.setSpec(newName);
     651        this.fullChanged();
     652        this.fixLabelColor();
     653    }
     654
     655    if (this.choices === 'getVarNamesDict'
     656            && this.contents().text === oldName) {
     657        this.setContents(newName);
     658    }
     659
     660    if (this instanceof CustomCommandBlockMorph
     661            && this.definition.body
     662            && isNil(this.definition.declarations.get(oldName))
     663            && !contains(this.definition.variableNames, oldName)) {
     664        this.definition.body.expression.refactorVarInStack(oldName, newName);
     665    }
     666
     667    this.inputs().forEach(function (input) {
     668        input.refactorVarInStack(oldName, newName);
     669    });
     670
     671    if (this.nextBlock) {
     672        var nb = this.nextBlock();
     673        if (nb) {
     674            nb.refactorVarInStack(oldName, newName);
     675        }
     676    }
     677};
     678
     679SyntaxElementMorph.prototype.definesScriptVariable = function (name) {
     680    // Returns true if this block is defining either a script local var or
     681    // an upVar called `name`
     682    return ((this.selector === 'doDeclareVariables'
     683                || (this.blockSpec && this.blockSpec.match('%upvar')))
     684            && (detect(this.inputs()[0].allInputs(), function (input) {
     685                return (input.selector === 'reportGetVar'
     686                        && input.blockSpec === name);
     687            })));
     688};
     689
     690// SyntaxElementMorph copy-on-write support:
     691
     692SyntaxElementMorph.prototype.selectForEdit = function () {
     693    var scripts = this.parentThatIsA(ScriptsMorph),
     694        ide = this.parentThatIsA(IDE_Morph),
     695        rcvr = ide ? ide.currentSprite : null,
     696        selected;
     697    if (scripts && rcvr && rcvr.inheritsAttribute('scripts')) {
     698        // copy on write:
     699        this.selectionID = true;
     700        rcvr.shadowAttribute('scripts');
     701        selected = detect(rcvr.scripts.allChildren(), function (m) {
     702            return m.selectionID;
     703        });
     704        delete this.selectionID;
     705        delete selected.selectionID;
     706        return selected;
     707    }
     708    return this;
    661709};
    662710
     
    702750            if (!silently) {this.drawNew(); }
    703751            this.children.forEach(function (child) {
    704                 if (!silently || child instanceof TemplateSlotMorph) {
     752                if ((!silently || child instanceof TemplateSlotMorph) &&
     753                                !(child instanceof BlockHighlightMorph)) {
    705754                    child.drawNew();
    706755                    child.changed();
     
    732781};
    733782
     783SyntaxElementMorph.prototype.flash = function () {
     784    if (!this.cachedNormalColor) {
     785        this.cachedNormalColor = this.color;
     786        this.setColor(this.activeHighlight);
     787    }
     788};
     789
     790SyntaxElementMorph.prototype.unflash = function () {
     791    if (this.cachedNormalColor) {
     792        var clr = this.cachedNormalColor;
     793        this.cachedNormalColor = null;
     794        this.setColor(clr);
     795    }
     796};
    734797
    735798// SyntaxElementMorph zebra coloring
     
    884947                true,
    885948                {
     949                        '§_dir': null,
    886950                    '(90) right' : 90,
    887951                    '(-90) left' : -90,
    888952                    '(0) up' : '0',
    889                     '(180) down' : 180
     953                    '(180) down' : 180,
     954                    'random' : ['random']
    890955                }
    891956            );
    892957            part.setContents(90);
     958            break;
     959        case '%note':
     960            part = new InputSlotMorph(
     961                null, // test
     962                true, // numeric
     963                'pianoKeyboardMenu',
     964                false // read-only
     965            );
    893966            break;
    894967        case '%inst':
     
    897970                true,
    898971                {
    899                     '(1) Acoustic Grand' : 1,
    900                     '(2) Bright Acoustic' : 2,
    901                     '(3) Electric Grand' : 3,
    902                     '(4) Honky Tonk' : 4,
    903                     '(5) Electric Piano 1' : 5,
    904                     '(6) Electric Piano 2' : 6,
    905                     '(7) Harpsichord' : 7
     972                    '(1) sine' : 1,
     973                    '(2) square' : 2,
     974                    '(3) sawtooth' : 3,
     975                    '(4) triangle' : 4
    906976                }
    907977            );
     
    9381008                    'dropped' : ['dropped'],
    9391009                    'mouse-entered' : ['mouse-entered'],
    940                     'mouse-departed' : ['mouse-departed']
     1010                    'mouse-departed' : ['mouse-departed'],
     1011                    'scrolled-up' : ['scrolled-up'],
     1012                    'scrolled-down' : ['scrolled-down'],
     1013                    'stopped' : ['stopped'] // experimental
    9411014                },
    9421015                true // read-only
     
    9711044                    'line' : ['line'],
    9721045                    'tab' : ['tab'],
    973                     'cr' : ['cr']
     1046                    'cr' : ['cr'],
     1047                    'csv' : ['csv']
    9741048                },
    9751049                false // read-only
     
    10001074            );
    10011075            part.setContents(1);
     1076            break;
     1077        case '%rel':
     1078            part = new InputSlotMorph(
     1079                null, // text
     1080                false, // numeric?
     1081                {
     1082                    'distance' : ['distance'],
     1083                    'direction' : ['direction']
     1084                },
     1085                true // read-only
     1086            );
    10021087            break;
    10031088        case '%spr':
     
    10541139                null,
    10551140                false,
    1056                 {   color: ['color'],
     1141                {
     1142                    color: ['color'],
    10571143                    fisheye: ['fisheye'],
    10581144                    whirl: ['whirl'],
     
    12081294                    'all' : ['all'],
    12091295                    'this script' : ['this script'],
    1210                     'this block' : ['this block']
    1211                 },
    1212                 true
    1213             );
    1214             part.setContents(['all']);
    1215             part.isStatic = true;
    1216             break;
    1217         case '%stopOthersChoices':
    1218             part = new InputSlotMorph(
    1219                 null,
    1220                 false,
    1221                 {
     1296                    'this block' : ['this block'],
    12221297                    'all but this script' : ['all but this script'],
    12231298                    'other scripts in sprite' : ['other scripts in sprite']
     
    12251300                true
    12261301            );
    1227             part.setContents(['all but this script']);
     1302            part.setContents(['all']);
    12281303            part.isStatic = true;
    12291304            break;
     
    12361311            );
    12371312            part.setContents(['number']);
     1313            break;
     1314        case '%mapValue':
     1315            part = new InputSlotMorph(
     1316                null,
     1317                false,
     1318                {
     1319                    String : ['String'],
     1320                    Number : ['Number'],
     1321                    'true' : ['true'],
     1322                    'false' : ['false']
     1323                },
     1324                true
     1325            );
     1326            part.setContents(['String']);
     1327            part.isStatic = true;
    12381328            break;
    12391329        case '%var':
     
    12531343                true
    12541344            );
    1255             part.isStatic = true;
     1345            // part.isStatic = true;
    12561346            break;
    12571347        case '%lst':
     
    14491539        // commented out for now
    14501540
    1451         var rcvr = this.definition.receiver || this.receiver(),
     1541        var rcvr = this.definition.receiver || this.scriptTarget(),
    14521542            id = spec.slice(1),
    14531543            cst;
     
    14661556        // allow GUI symbols as label icons
    14671557        // usage: $symbolName[-size-r-g-b], size and color values are optional
     1558        // If there isn't a symbol under that name, it just styles whatever is
     1559        // after "$", so you can add unicode icons to your blocks, for example
     1560        // ☺️
    14681561        tokens = spec.slice(1).split('-');
    14691562        if (!contains(SymbolMorph.prototype.names, tokens[0])) {
    1470             part = new StringMorph(spec);
     1563            part = new StringMorph(tokens[0]);
    14711564            part.fontName = this.labelFontName;
    14721565            part.fontStyle = this.labelFontStyle;
    1473             part.fontSize = this.fontSize;
    1474             part.color = new Color(255, 255, 255);
    1475             part.isBold = true;
    1476             part.shadowColor = this.color.darker(this.labelContrast);
    1477             part.shadowOffset = MorphicPreferences.isFlat ?
    1478                     new Point() : this.embossing;
    1479             part.drawNew();
    1480             return part;
    1481         }
    1482         part = new SymbolMorph(tokens[0]);
    1483         part.size = this.fontSize * (+tokens[1] || 1.2);
     1566            part.fontSize = this.fontSize * (+tokens[1] || 1);
     1567        } else {
     1568            part = new SymbolMorph(tokens[0]);
     1569            part.size = this.fontSize * (+tokens[1] || 1.2);
     1570        }
    14841571        part.color = new Color(
    14851572            +tokens[2] === 0 ? 0 : +tokens[2] || 255,
     
    15341621        space = this.isPrototype ?
    15351622                1 : Math.floor(fontHeight(this.fontSize) / 3),
     1623        ico = this instanceof BlockMorph && this.hasLocationPin() ?
     1624                this.methodIconExtent().x + space : 0,
    15361625        bottomCorrection,
    15371626        initialExtent = this.extent();
    15381627
    1539     if ((this instanceof MultiArgMorph) && (this.slotSpec !== '%c')) {
     1628    if ((this instanceof MultiArgMorph) && (this.slotSpec !== '%cs')) {
    15401629        blockWidth += this.arrows().width();
    15411630    } else if (this instanceof ReporterBlockMorph) {
     
    15551644    parts.forEach(function (part) {
    15561645        if ((part instanceof CSlotMorph)
    1557                 || (part.slotSpec === '%c')) {
     1646                || (part.slotSpec === '%cs')) {
    15581647            if (l.length > 0) {
    15591648                lines.push(l);
     
    16001689            || this instanceof ArgLabelMorph) {
    16011690        y = this.top();
     1691        if (this.slotSpec === '%cs' && this.inputs().length > 0) {
     1692            y -= this.rounding;
     1693        }
    16021694    }
    16031695    lines.forEach(function (line) {
    1604         x = myself.left() + myself.edge + myself.labelPadding;
     1696        x = myself.left() + ico + myself.edge + myself.labelPadding;
    16051697        if (myself instanceof RingMorph) {
    16061698            x = myself.left() + space; //myself.labelPadding;
    16071699        } else if (myself.isPredicate) {
    1608             x = myself.left() + myself.rounding;
     1700            x = myself.left() + ico + myself.rounding;
    16091701        } else if (myself instanceof MultiArgMorph
    16101702                || myself instanceof ArgLabelMorph) {
     
    16171709                x -= myself.labelPadding;
    16181710                if (myself.isPredicate) {
    1619                     x = myself.left() + myself.rounding;
     1711                    x = myself.left() + ico + myself.rounding;
    16201712                }
    16211713                part.setColor(myself.color);
     1714                part.setPosition(new Point(x, y));
     1715                lineHeight = part.height();
     1716            } else if (part instanceof MultiArgMorph &&
     1717                    (part.slotSpec === '%cs')) {
     1718                if (myself.isPredicate) {
     1719                    x += myself.corner;
     1720                }
    16221721                part.setPosition(new Point(x, y));
    16231722                lineHeight = part.height();
     
    16781777            maxX - this.left() + this.rounding
    16791778        );
    1680     } else if (this instanceof MultiArgMorph
     1779    } else if ((this instanceof MultiArgMorph && this.slotSpec !== '%cs')
    16811780            || this instanceof ArgLabelMorph) {
    16821781        blockWidth = Math.max(
     
    17051804    // adjust CSlots
    17061805    parts.forEach(function (part) {
    1707         if (part instanceof CSlotMorph) {
     1806        var adjustMultiWidth = 0;
     1807        if (part instanceof CSlotMorph || (part.slotSpec === '%cs')) {
    17081808            if (myself.isPredicate) {
    1709                 part.setWidth(blockWidth - myself.rounding * 2);
     1809                part.setWidth(
     1810                    blockWidth - ico - myself.rounding * 2 - myself.corner
     1811                );
    17101812            } else {
    1711                 part.setWidth(blockWidth - myself.edge);
     1813                part.setWidth(blockWidth - myself.edge - ico);
     1814                adjustMultiWidth = myself.corner + myself.edge;
    17121815            }
     1816        }
     1817        if (part.slotSpec === '%cs') {
     1818            part.inputs().forEach(function (slot) {
     1819                slot.setWidth(part.right() - slot.left() - adjustMultiWidth);
     1820            });
    17131821        }
    17141822    });
     
    17641872};
    17651873
     1874SyntaxElementMorph.prototype.methodIconExtent = function () {
     1875    // answer the span of the icon for the "local method" indicator
     1876    var ico = this.fontSize * 1.2;
     1877    return this.hasLocationPin() ? new Point(ico * 0.66, ico)
     1878                : new Point(0, 0);
     1879};
     1880
    17661881// SyntaxElementMorph evaluating:
    17671882
     
    17781893// SyntaxElementMorph speech bubble feedback:
    17791894
    1780 SyntaxElementMorph.prototype.showBubble = function (value, exportPic) {
     1895SyntaxElementMorph.prototype.showBubble = function (value, exportPic, target) {
    17811896    var bubble,
    17821897        txt,
     
    17851900        isClickable = false,
    17861901        ide = this.parentThatIsA(IDE_Morph),
    1787         rcvr = this.receiver(),
    17881902        anchor = this,
    17891903        pos = this.rightCenter().add(new Point(2, 0)),
     
    18351949        morphToShow.silentSetHeight(img.height);
    18361950        morphToShow.image = img;
     1951    } else if (value instanceof Sound) {
     1952        morphToShow = new SymbolMorph('notes', 30);
    18371953    } else if (value instanceof Context) {
    18381954        img = value.image();
     
    18681984        );
    18691985    }
    1870     if (ide && (ide.currentSprite !== rcvr)) {
    1871         if (rcvr instanceof StageMorph) {
     1986    if (ide && (ide.currentSprite !== target)) {
     1987        if (target instanceof StageMorph) {
    18721988            anchor = ide.corral.stageIcon;
    1873         } else {
     1989        } else if (target) {
     1990                if (target.isTemporary) {
     1991                        target = detect(
     1992                                        target.allExemplars(),
     1993                                function (each) {return !each.isTemporary; }
     1994                        );
     1995                }
    18741996            anchor = detect(
    18751997                ide.corral.frame.contents.children,
    1876                 function (icon) {return icon.object === rcvr; }
     1998                function (icon) {return icon.object === target; }
    18771999            );
     2000        } else {
     2001                target = ide;
    18782002        }
    18792003        pos = anchor.center();
     
    19152039    ide.saveCanvasAs(
    19162040        pic,
    1917         ide.projetName || localize('Untitled') + ' ' + localize('script pic'),
    1918         true
     2041        (ide.projectName || localize('untitled')) + ' ' + localize('script pic')
    19192042    );
    19202043};
     
    19572080    accessors are:
    19582081
    1959     selector    - (string) name of method to be triggered
    1960     receiver()    - answer the object (sprite) to which I apply
    1961     inputs()    - answer an array with my arg slots and nested reporters
    1962     defaults    - an optional Array containing default input values
    1963     topBlock()    - answer the top block of the stack I'm attached to
    1964     blockSpec    - a formalized description of my label parts
    1965     setSpec()    - force me to change my label structure
    1966     evaluate()    - answer the result of my evaluation
     2082    selector        - (string) name of method to be triggered
     2083    scriptTarget()  - answer the object (sprite) to which I apply
     2084    inputs()        - answer an array with my arg slots and nested reporters
     2085    defaults        - an optional Array containing default input values
     2086    topBlock()      - answer the top block of the stack I'm attached to
     2087    blockSpec       - a formalized description of my label parts
     2088    setSpec()       - force me to change my label structure
     2089    evaluate()      - answer the result of my evaluation
    19672090    isUnevaluated() - answer whether I am part of a special form
    19682091
     
    20022125    %idx    - white roundish type-in slot for indices incl. "any"
    20032126    %obj    - specially drawn slot for object reporters
     2127    %rel    - chameleon colored rectangular drop-down for relation options
    20042128    %spr    - chameleon colored rectangular drop-down for object-names
    20052129    %col    - chameleon colored rectangular drop-down for collidables
     
    20382162    arity: multiple
    20392163
    2040     %mult%x    - where %x stands for any of the above single inputs
    2041     %inputs - for an additional text label 'with inputs'
    2042     %words - for an expandable list of default 2 (used in JOIN)
    2043     %exp - for a static expandable list of minimum 0 (used in LIST)
    2044     %scriptVars - for an expandable list of variable reporter templates
    2045     %parms - for an expandable list of formal parameters
    2046     %ringparms - the same for use inside Rings
     2164    %mult%x      - where %x stands for any of the above single inputs
     2165    %inputs      - for an additional text label 'with inputs'
     2166    %words       - for an expandable list of default 2 (used in JOIN)
     2167    %exp         - for a static expandable list of minimum 0 (used in LIST)
     2168    %scriptVars  - for an expandable list of variable reporter templates
     2169    %parms       - for an expandable list of formal parameters
     2170    %ringparms   - the same for use inside Rings
    20472171
    20482172    special form: upvar
    20492173
    2050     %upvar - same as %t (inline variable reporter template)
     2174    %upvar       - same as %t (inline variable reporter template)
    20512175
    20522176    special form: input name
    20532177
    2054     %inputName - variable blob (used in input type dialog)
     2178    %inputName   - variable blob (used in input type dialog)
    20552179
    20562180    examples:
    20572181
    20582182        'if %b %c else %c'        - creates Scratch's If/Else block
    2059         'set pen color to %clr'    - creates Scratch's Pen color block
     2183        'set pen color to %clr'   - creates Scratch's Pen color block
    20602184        'list %mult%s'            - creates BYOB's list reporter block
    2061         'call %n %inputs'        - creates BYOB's Call block
     2185        'call %n %inputs'         - creates BYOB's Call block
    20622186        'the script %parms %c'    - creates BYOB's THE SCRIPT block
    20632187*/
     
    21022226    this.instantiationSpec = null; // spec to set upon fullCopy() of template
    21032227    this.category = null; // for zebra coloring (non persistent)
     2228    this.isCorpse = false; // marked for deletion fom a custom block definition
    21042229
    21052230    BlockMorph.uber.init.call(this, silently);
    21062231    this.color = new Color(0, 17, 173);
    2107     this.cashedInputs = null;
    2108 };
    2109 
    2110 BlockMorph.prototype.receiver = function () {
    2111     // answer the object to which I apply (whose method I represent)
    2112     var up = this.parent;
    2113     while (!!up) {
    2114         if (up.owner) {
    2115             return up.owner;
    2116         }
    2117         up = up.parent;
    2118     }
    2119     return null;
     2232    this.cachedInputs = null;
     2233};
     2234
     2235BlockMorph.prototype.scriptTarget = function () {
     2236    // answer the sprite or stage that this block acts on,
     2237    // if the user clicks on it.
     2238    // NOTE: since scripts can be shared by more than a single sprite
     2239    // this method only gives the desired result within the context of
     2240    // the user actively clicking on a block inside the IDE
     2241    // there is no direct relationship between a block and a sprite.
     2242    var scripts = this.parentThatIsA(ScriptsMorph),
     2243        ide;
     2244    if (scripts) {
     2245        return scripts.scriptTarget();
     2246    }
     2247    ide = this.parentThatIsA(IDE_Morph);
     2248    if (ide) {
     2249        return ide.currentSprite;
     2250    }
     2251    throw new Error('script target cannot be found for orphaned block');
    21202252};
    21212253
     
    21682300};
    21692301
    2170 BlockMorph.prototype.setSpec = function (spec, silently) {
     2302BlockMorph.prototype.setSpec = function (spec, silently, definition) {
    21712303    var myself = this,
    21722304        part,
     
    21812313    }
    21822314    this.parseSpec(spec).forEach(function (word) {
    2183         if (word[0] === '%') {
     2315        if (word[0] === '%' && (word !== '%br')) {
    21842316            inputIdx += 1;
    21852317        }
    21862318        part = myself.labelPart(word);
     2319        if (isNil(part)) {
     2320            // console.log('could not create label part', word);
     2321            return;
     2322        }
    21872323        myself.add(part);
    21882324        if (!(part instanceof CommandSlotMorph ||
     
    22012337            myself.add(myself.placeHolder());
    22022338        }
    2203         if (part instanceof InputSlotMorph && myself.definition) {
     2339        if (part instanceof InputSlotMorph && myself.isCustomBlock) {
    22042340            part.setChoices.apply(
    22052341                part,
    2206                 myself.definition.inputOptionsOfIdx(inputIdx)
     2342                (definition || myself.definition).inputOptionsOfIdx(inputIdx)
    22072343            );
    22082344        }
     
    22632399                proc.context.outerContext.variables.names() : [],
    22642400        alternatives,
    2265         top,
    2266         blck;
     2401        field,
     2402        rcvr,
     2403        top;
    22672404
    22682405    function addOption(label, toggle, test, onHint, offHint) {
     
    22762413    }
    22772414
     2415    function renameVar() {
     2416        var blck = myself.fullCopy();
     2417        blck.addShadow();
     2418        new DialogBoxMorph(
     2419            myself,
     2420            myself.userSetSpec,
     2421            myself
     2422        ).prompt(
     2423            "Variable name",
     2424            myself.blockSpec,
     2425            world,
     2426            blck.fullImage(), // pic
     2427            InputSlotMorph.prototype.getVarNamesDict.call(myself)
     2428        );
     2429    }
     2430
    22782431    menu.addItem(
    22792432        "help...",
     
    22862439                "script pic with result...",
    22872440                function () {
    2288                     top.ExportResultPic();
     2441                    top.exportResultPic();
    22892442                },
    22902443                'open a new window\n' +
     
    22952448    }
    22962449    if (this.isTemplate) {
    2297         if (!(this.parent instanceof SyntaxElementMorph)) {
     2450        if (this.parent instanceof SyntaxElementMorph) { // in-line
     2451            if (this.selector === 'reportGetVar') { // script var definition
     2452                menu.addLine();
     2453                menu.addItem(
     2454                    'rename...',
     2455                    function () {
     2456                        myself.refactorThisVar(true); // just the template
     2457                    },
     2458                    'rename only\nthis reporter'
     2459                );
     2460                menu.addItem(
     2461                    'rename all...',
     2462                    'refactorThisVar',
     2463                    'rename all blocks that\naccess this variable'
     2464                );
     2465            }
     2466        } else { // in palette
    22982467            if (this.selector === 'reportGetVar') {
    2299                 addOption(
    2300                     'transient',
    2301                     'toggleTransientVariable',
    2302                     myself.isTransientVariable(),
    2303                     'uncheck to save contents\nin the project',
    2304                     'check to prevent contents\nfrom being saved'
    2305                 );
     2468                rcvr = this.scriptTarget();
     2469                if (this.isInheritedVariable(false)) { // fully inherited
     2470                    addOption(
     2471                        'inherited',
     2472                        function () {
     2473                            rcvr.toggleInheritedVariable(myself.blockSpec);
     2474                        },
     2475                        true,
     2476                        'uncheck to\ndisinherit',
     2477                        null
     2478                    );
     2479                } else { // not inherited
     2480                    if (this.isInheritedVariable(true)) { // shadowed
     2481                        addOption(
     2482                            'inherited',
     2483                            function () {
     2484                                rcvr.toggleInheritedVariable(myself.blockSpec);
     2485                            },
     2486                            false,
     2487                            null,
     2488                            localize('check to inherit\nfrom')
     2489                                + ' ' + rcvr.exemplar.name
     2490                        );
     2491                    }
     2492                    addOption(
     2493                        'transient',
     2494                        'toggleTransientVariable',
     2495                        myself.isTransientVariable(),
     2496                        'uncheck to save contents\nin the project',
     2497                        'check to prevent contents\nfrom being saved'
     2498                    );
     2499                    menu.addLine();
     2500                    menu.addItem(
     2501                        'rename...',
     2502                        function () {
     2503                            myself.refactorThisVar(true); // just the template
     2504                        },
     2505                        'rename only\nthis reporter'
     2506                    );
     2507                    menu.addItem(
     2508                        'rename all...',
     2509                        'refactorThisVar',
     2510                        'rename all blocks that\naccess this variable'
     2511                    );
     2512                }
    23062513            } else if (this.selector !== 'evaluateCustomBlock') {
    23072514                menu.addItem(
     
    23102517                );
    23112518            }
     2519
     2520            // allow toggling inheritable attributes
     2521            if (StageMorph.prototype.enableInheritance) {
     2522                rcvr = this.scriptTarget();
     2523                field = {
     2524                    xPosition: 'x position',
     2525                    yPosition: 'y position',
     2526                    direction: 'direction',
     2527                    getScale: 'size',
     2528                    getCostumeIdx: 'costume #'
     2529                }[this.selector];
     2530                if (field && rcvr && rcvr.exemplar) {
     2531                    menu.addLine();
     2532                    addOption(
     2533                        'inherited',
     2534                        function () {
     2535                            rcvr.toggleInheritanceForAttribute(field);
     2536                        },
     2537                        rcvr.inheritsAttribute(field),
     2538                        'uncheck to\ndisinherit',
     2539                        localize('check to inherit\nfrom')
     2540                            + ' ' + rcvr.exemplar.name
     2541                    );
     2542                }
     2543            }
     2544
    23122545            if (StageMorph.prototype.enableCodeMapping) {
    23132546                menu.addLine();
     
    23262559    menu.addLine();
    23272560    if (this.selector === 'reportGetVar') {
    2328         blck = this.fullCopy();
    2329         blck.addShadow();
    23302561        menu.addItem(
    23312562            'rename...',
    2332             function () {
    2333                 new DialogBoxMorph(
    2334                     myself,
    2335                     myself.setSpec,
    2336                     myself
    2337                 ).prompt(
    2338                     "Variable name",
    2339                     myself.blockSpec,
    2340                     world,
    2341                     blck.fullImage(), // pic
    2342                     InputSlotMorph.prototype.getVarNamesDict.call(myself)
    2343                 );
    2344             }
     2563            renameVar,
     2564            'rename only\nthis reporter'
    23452565        );
    23462566    } else if (SpriteMorph.prototype.blockAlternatives[this.selector]) {
     
    23532573            }
    23542574        );
    2355     } else if (this.definition && this.alternatives) { // custom block
     2575    } else if (this.isCustomBlock && this.alternatives) {
    23562576        alternatives = this.alternatives();
    23572577        if (alternatives.length > 0) {
     
    23672587        function () {
    23682588            var dup = myself.fullCopy(),
    2369                 ide = myself.parentThatIsA(IDE_Morph);
     2589                ide = myself.parentThatIsA(IDE_Morph),
     2590                blockEditor = myself.parentThatIsA(BlockEditorMorph);
    23702591            dup.pickUp(world);
     2592            // register the drop-origin, so the block can
     2593            // slide back to its former situation if dropped
     2594            // somewhere where it gets rejected
     2595            if (!ide && blockEditor) {
     2596                ide = blockEditor.target.parentThatIsA(IDE_Morph);
     2597            }
    23712598            if (ide) {
    23722599                world.hand.grabOrigin = {
     
    23842611                var cpy = myself.fullCopy(),
    23852612                    nb = cpy.nextBlock(),
    2386                     ide = myself.parentThatIsA(IDE_Morph);
     2613                    ide = myself.parentThatIsA(IDE_Morph),
     2614                    blockEditor = myself.parentThatIsA(BlockEditorMorph);
    23872615                if (nb) {nb.destroy(); }
    23882616                cpy.pickUp(world);
     2617                if (!ide && blockEditor) {
     2618                    ide = blockEditor.target.parentThatIsA(IDE_Morph);
     2619                }
    23892620                if (ide) {
    23902621                    world.hand.grabOrigin = {
     
    24102641            ide.saveCanvasAs(
    24112642                myself.topBlock().scriptPic(),
    2412                 ide.projetName || localize('Untitled') + ' ' +
    2413                     localize('script pic'),
    2414                 true // request new window
     2643                (ide.projectName || localize('untitled')) + ' ' +
     2644                    localize('script pic')
    24152645            );
    24162646        },
    24172647        'open a new window\nwith a picture of this script'
    24182648    );
     2649    if (shiftClicked) {
     2650        menu.addItem(
     2651            'download script',
     2652            function () {
     2653                var ide = myself.parentThatIsA(IDE_Morph),
     2654                    blockEditor = myself.parentThatIsA(BlockEditorMorph);
     2655                if (!ide && blockEditor) {
     2656                    ide = blockEditor.target.parentThatIsA(IDE_Morph);
     2657                }
     2658                if (ide) {
     2659                    ide.saveXMLAs(
     2660                        ide.serializer.serialize(myself),
     2661                        myself.selector + ' script',
     2662                        false);
     2663                }
     2664            },
     2665            'download this script\nas an XML file',
     2666            new Color(100, 0, 0)
     2667            );
     2668    }
    24192669    if (proc) {
    24202670        if (vNames.length) {
     
    24292679            });
    24302680        }
     2681        proc.homeContext.variables.names().forEach(function (vn) {
     2682            if (!contains(vNames, vn)) {
     2683                menu.addItem(
     2684                    vn + '...',
     2685                    function () {
     2686                        proc.doShowVar(vn);
     2687                    }
     2688                );
     2689            }
     2690        });
    24312691        return menu;
    24322692    }
    2433     if (this.parentThatIsA(RingMorph)) {
     2693    if (this.parent.parentThatIsA(RingMorph)) {
    24342694        menu.addLine();
    24352695        menu.addItem("unringify", 'unringify');
    2436         menu.addItem("ringify", 'ringify');
     2696        top = this.topBlock();
     2697        if (this instanceof ReporterBlockMorph ||
     2698                (!(top instanceof HatBlockMorph))) {
     2699            menu.addItem("ringify", 'ringify');
     2700        }
    24372701        return menu;
    24382702    }
     
    24982762};
    24992763
     2764BlockMorph.prototype.isInheritedVariable = function (shadowedOnly) {
     2765    // private - only for variable getter template inside the palette
     2766    if (this.isTemplate &&
     2767            (this.selector === 'reportGetVar') &&
     2768            (this.parent instanceof FrameMorph)) {
     2769        return contains(
     2770            this.scriptTarget().inheritedVariableNames(shadowedOnly),
     2771            this.blockSpec
     2772        );
     2773    }
     2774    return false;
     2775};
     2776
    25002777BlockMorph.prototype.isTransientVariable = function () {
    25012778    // private - only for variable getter template inside the palette
    2502     var varFrame = this.receiver().variables.silentFind(this.blockSpec);
     2779    var varFrame = this.scriptTarget().variables.silentFind(this.blockSpec);
    25032780    return varFrame ? varFrame.vars[this.blockSpec].isTransient : false;
    25042781};
     
    25062783BlockMorph.prototype.toggleTransientVariable = function () {
    25072784    // private - only for variable getter template inside the palette
    2508     var varFrame = this.receiver().variables.silentFind(this.blockSpec);
     2785    var varFrame = this.scriptTarget().variables.silentFind(this.blockSpec);
    25092786    if (!varFrame) {return; }
    25102787    varFrame.vars[this.blockSpec].isTransient =
     
    25282805        });
    25292806    }
    2530     if (this instanceof ReporterBlockMorph) {
    2531         if (this.parent instanceof BlockMorph) {
    2532             this.parent.revertToDefaultInput(this);
    2533         }
     2807    if (this instanceof ReporterBlockMorph &&
     2808                        ((this.parent instanceof BlockMorph)
     2809                || (this.parent instanceof MultiArgMorph)
     2810                || (this.parent instanceof ReporterSlotMorph))) {
     2811        this.parent.revertToDefaultInput(this);
    25342812    } else { // CommandBlockMorph
    2535         if (this.parent) {
    2536             if (this.parent.fixLayout) {
    2537                 tobefixed = this.parentThatIsA(ArgMorph);
    2538             }
     2813        if (this.parent && this.parent.fixLayout) {
     2814            tobefixed = this.parentThatIsA(ArgMorph);
    25392815        } else { // must be in a custom block definition
    25402816            isindef = true;
     
    25572833BlockMorph.prototype.ringify = function () {
    25582834    // wrap a Ring around me
    2559     var ring = new RingMorph(),
    2560         top = this.topBlock(),
    2561         center = top.fullBounds().center();
    2562 
     2835    var ring, top, center,
     2836        target = this.selectForEdit(); // copy-on-edit
     2837    if (target !== this) {
     2838        return this.ringify.call(target);
     2839    }
     2840    ring = new RingMorph();
     2841    top = this.topBlock();
     2842    center = top.fullBounds().center();
    25632843    if (this.parent === null) {return null; }
    25642844    top.fullChanged();
     
    25682848            ring.embed(this);
    25692849        } else if (top) { // command
     2850            if (top instanceof HatBlockMorph) {
     2851                return;
     2852            }
    25702853            top.parent.add(ring);
    25712854            ring.embed(top);
     
    25792862    this.fixBlockColor(null, true);
    25802863    top.fullChanged();
    2581 
    25822864};
    25832865
    25842866BlockMorph.prototype.unringify = function () {
    25852867    // remove a Ring around me, if any
    2586     var ring = this.parentThatIsA(RingMorph),
    2587         top = this.topBlock(),
    2588         scripts = this.parentThatIsA(ScriptsMorph),
    2589         block,
    2590         center;
    2591 
     2868    var ring, top, center, scripts, block,
     2869        target = this.selectForEdit(); // copy-on-edit
     2870    if (target !== this) {
     2871        return this.unringify.call(target);
     2872    }
     2873    ring = this.parent.parentThatIsA(RingMorph);
     2874    top = this.topBlock();
     2875    scripts = this.parentThatIsA(ScriptsMorph);
    25922876    if (ring === null) {return null; }
    25932877    block = ring.contents();
     
    26142898
    26152899BlockMorph.prototype.relabel = function (alternativeSelectors) {
    2616     var menu = new MenuMorph(this),
    2617         oldInputs = this.inputs(),
    2618         myself = this;
     2900    var menu, oldInputs, myself,
     2901        target = this.selectForEdit(); // copy-on-edit
     2902    if (target !== this) {
     2903        return this.relabel.call(target, alternativeSelectors);
     2904    }
     2905    menu = new MenuMorph(this);
     2906    oldInputs = this.inputs();
     2907    myself = this;
    26192908    alternativeSelectors.forEach(function (sel) {
    26202909        var block = SpriteMorph.prototype.blockForSelector(sel);
     
    26382927    // private - used only for relabel()
    26392928    var oldInputs = this.inputs(),
     2929        scripts = this.parentThatIsA(ScriptsMorph),
     2930        surplus,
    26402931        info;
    26412932    info = SpriteMorph.prototype.blocks[aSelector];
     
    26432934    this.selector = aSelector;
    26442935    this.setSpec(localize(info.spec));
    2645     this.restoreInputs(oldInputs);
     2936    surplus = this.restoreInputs(oldInputs);
    26462937    this.fixLabelColor();
     2938
     2939    // place surplus blocks on scipts
     2940    if (scripts && surplus.length) {
     2941        surplus.forEach(function (blk) {
     2942            blk.moveBy(10);
     2943            scripts.add(blk);
     2944        });
     2945    }
    26472946};
    26482947
     
    26502949    // private - used only for relabel()
    26512950    // try to restore my previous inputs when my spec has been changed
     2951    // return an Array of left-over blocks, if any
    26522952    var i = 0,
    26532953        old,
    26542954        nb,
     2955        leftOver = [],
    26552956        myself = this;
    26562957
     
    26742975        i += 1;
    26752976    });
     2977
     2978    // gather surplus blocks
     2979    for (i; i < oldInputs.length; i += 1) {
     2980        old = oldInputs[i];
     2981        if (old instanceof ReporterBlockMorph) {
     2982            leftOver.push(old);
     2983        } else if (old instanceof CommandSlotMorph) {
     2984            nb = old.nestedBlock();
     2985            if (nb) {
     2986                leftOver.push(nb);
     2987            }
     2988        }
     2989    }
    26762990    this.cachedInputs = null;
     2991    return leftOver;
    26772992};
    26782993
     
    26853000        comment,
    26863001        block,
    2687         isCustomBlock = this.selector === 'evaluateCustomBlock',
    2688         spec = isCustomBlock ?
     3002        spec = this.isCustomBlock ?
    26893003                this.definition.helpSpec() : this.selector,
    26903004        ctx;
     
    27093023    };
    27103024
    2711     if (isCustomBlock && this.definition.comment) {
     3025    if (this.isCustomBlock && this.definition.comment) {
    27123026        block = this.fullCopy();
    27133027        block.addShadow();
     
    27473061    block.addShadow(new Point(3, 3));
    27483062    pic = block.fullImageClassic();
    2749     if (this.definition) {
     3063    if (this.isCustomBlock) {
    27503064        help = 'Enter code that corresponds to the block\'s definition. ' +
    27513065            'Use the formal parameter\nnames as shown and <body> to ' +
     
    28123126            'reify' : this.selector;
    28133127    if (aString) {
    2814         if (this.definition) { // custom block
     3128        if (this.isCustomBlock) {
    28153129            this.definition.codeHeader = aString;
    28163130        } else {
     
    28253139            'reify' : this.selector;
    28263140    if (aString) {
    2827         if (this.definition) { // custom block
     3141        if (this.isCustomBlock) {
    28283142            this.definition.codeMapping = aString;
    28293143        } else {
     
    28443158        body,
    28453159        bodyLines,
    2846         defKey = this.definition ? this.definition.spec : key,
     3160        defKey = this.isCustomBlock ? this.definition.spec : key,
    28473161        defs = definitions || {},
    28483162        parts = [];
    28493163    code = key === 'reportGetVar' ? this.blockSpec
    2850             : this.definition ? this.definition.codeMapping || ''
     3164            : this.isCustomBlock ? this.definition.codeMapping || ''
    28513165                    : StageMorph.prototype.codeMappings[key] || '';
    28523166
     
    28543168    if (key !== 'reportGetVar' && !defs.hasOwnProperty(defKey)) {
    28553169        defs[defKey] = null; // create the property for recursive definitions
    2856         if (this.definition) {
     3170        if (this.isCustomBlock) {
    28573171            header = this.definition.codeHeader || '';
    28583172            if (header.indexOf('<body') !== -1) { // replace with def mapping
     
    29313245
    29323246BlockMorph.prototype.codeDefinitionHeader = function () {
    2933     var block = this.definition ? new PrototypeHatBlockMorph(this.definition)
     3247    var block = this.isCustomBlock ? new PrototypeHatBlockMorph(this.definition)
    29343248            : SpriteMorph.prototype.blockForSelector(this.selector),
    29353249        hat = new HatBlockMorph(),
    29363250        count = 1;
    29373251
    2938     if (this.definition) {return block; }
     3252    if (this.isCustomBlock) {return block; }
    29393253    block.inputs().forEach(function (input) {
    29403254        var part = new TemplateSlotMorph('#' + count);
     
    29533267
    29543268BlockMorph.prototype.codeMappingHeader = function () {
    2955     var block = this.definition ? this.definition.blockInstance()
     3269    var block = this.isCustomBlock ? this.definition.blockInstance()
    29563270            : SpriteMorph.prototype.blockForSelector(this.selector),
    29573271        hat = new HatBlockMorph(),
     
    29733287};
    29743288
     3289// Variable refactoring
     3290
     3291BlockMorph.prototype.refactorThisVar = function (justTheTemplate) {
     3292    // Rename all occurrences of the variable this block is holding,
     3293    // taking care of its lexical scope
     3294
     3295    var receiver = this.scriptTarget(),
     3296        oldName = this.instantiationSpec || this.blockSpec,
     3297        cpy = this.fullCopy();
     3298
     3299    cpy.addShadow();
     3300
     3301    new DialogBoxMorph(this, renameVarTo, this).prompt(
     3302        'Variable name',
     3303        oldName,
     3304        this.world(),
     3305        cpy.fullImage(), // pic
     3306        InputSlotMorph.prototype.getVarNamesDict.call(this)
     3307    );
     3308
     3309    function renameVarTo (newName) {
     3310        if (this.parent instanceof SyntaxElementMorph) {
     3311            if (this.parentThatIsA(BlockEditorMorph)) {
     3312                this.doRefactorBlockParameter(
     3313                    oldName,
     3314                    newName,
     3315                    justTheTemplate
     3316                );
     3317            } else if (this.parentThatIsA(RingMorph)) {
     3318                this.doRefactorRingParameter(oldName, newName, justTheTemplate);
     3319            } else {
     3320                this.doRefactorScriptVar(oldName, newName, justTheTemplate);
     3321            }
     3322        } else if (receiver.hasSpriteVariable(oldName)) {
     3323            this.doRefactorSpriteVar(oldName, newName, justTheTemplate);
     3324        } else {
     3325            this.doRefactorGlobalVar(oldName, newName, justTheTemplate);
     3326        }
     3327    }
     3328};
     3329
     3330BlockMorph.prototype.varExistsError = function (ide, where) {
     3331    ide.inform(
     3332        'Variable exists',
     3333        'A variable with this name already exists ' +
     3334        (where || 'in this context') + '.'
     3335    );
     3336};
     3337
     3338BlockMorph.prototype.doRefactorBlockParameter = function (
     3339    oldName,
     3340    newName,
     3341    justTheTemplate
     3342) {
     3343    var fragMorph = this.parentThatIsA(BlockInputFragmentMorph),
     3344        fragment = fragMorph.fragment.copy(),
     3345        definer = fragMorph.parent,
     3346        editor = this.parentThatIsA(BlockEditorMorph),
     3347        scripts = editor.body.contents;
     3348
     3349    if (definer.anyChild(function (any) {
     3350        return (any.blockSpec === newName);
     3351    })) {
     3352        this.varExistsError(editor.target.parentThatIsA(IDE_Morph));
     3353        return;
     3354    }
     3355
     3356    fragment.labelString = newName;
     3357    fragMorph.updateBlockLabel(fragment);
     3358
     3359    if (justTheTemplate) {
     3360        return;
     3361    }
     3362
     3363    scripts.children.forEach(function (script) {
     3364        script.refactorVarInStack(oldName, newName);
     3365    });
     3366};
     3367
     3368BlockMorph.prototype.doRefactorRingParameter = function (
     3369    oldName,
     3370    newName,
     3371    justTheTemplate
     3372) {
     3373    var ring = this.parentThatIsA(RingMorph),
     3374        script = ring.contents(),
     3375        tb = this.topBlock();
     3376
     3377    if (contains(ring.inputNames(), newName)) {
     3378        this.varExistsError(this.parentThatIsA(IDE_Morph));
     3379        return;
     3380    }
     3381
     3382    tb.fullChanged();
     3383    this.setSpec(newName);
     3384
     3385    if (justTheTemplate) {
     3386        tb.fullChanged();
     3387        return;
     3388    }
     3389
     3390    if (script) {
     3391        script.refactorVarInStack(oldName, newName);
     3392    }
     3393
     3394    tb.fullChanged();
     3395};
     3396
     3397BlockMorph.prototype.doRefactorScriptVar = function (
     3398    oldName,
     3399    newName,
     3400    justTheTemplate
     3401) {
     3402    var definer = this.parentThatIsA(CommandBlockMorph),
     3403        receiver, ide;
     3404
     3405    if (definer.definesScriptVariable(newName)) {
     3406        receiver = this.scriptTarget();
     3407        ide = receiver.parentThatIsA(IDE_Morph);
     3408        this.varExistsError(ide);
     3409        return;
     3410    }
     3411
     3412    this.userSetSpec(newName);
     3413
     3414    if (justTheTemplate) {
     3415        return;
     3416    }
     3417
     3418    definer.refactorVarInStack(oldName, newName, true);
     3419};
     3420
     3421BlockMorph.prototype.doRefactorSpriteVar = function (
     3422    oldName,
     3423    newName,
     3424    justTheTemplate
     3425) {
     3426    var receiver = this.scriptTarget(),
     3427        ide = receiver.parentThatIsA(IDE_Morph),
     3428        oldWatcher = receiver.findVariableWatcher(oldName),
     3429        oldValue, newWatcher;
     3430
     3431    if (receiver.hasSpriteVariable(newName)) {
     3432        this.varExistsError(ide);
     3433        return;
     3434    } else if (!isNil(ide.globalVariables.vars[newName])) {
     3435        this.varExistsError(ide, 'as a global variable');
     3436        return;
     3437    } else {
     3438        oldValue = receiver.variables.getVar(oldName);
     3439        receiver.deleteVariable(oldName);
     3440        receiver.addVariable(newName, false);
     3441        receiver.variables.setVar(newName, oldValue);
     3442
     3443        if (oldWatcher && oldWatcher.isVisible) {
     3444            newWatcher = receiver.toggleVariableWatcher(
     3445                newName,
     3446                false
     3447            );
     3448            newWatcher.setPosition(oldWatcher.position());
     3449        }
     3450
     3451        if (!justTheTemplate) {
     3452            receiver.refactorVariableInstances(
     3453                oldName,
     3454                newName,
     3455                false
     3456            );
     3457            receiver.customBlocks.forEach(function (eachBlock) {
     3458                eachBlock.body.expression.refactorVarInStack(
     3459                    oldName,
     3460                    newName
     3461                );
     3462            });
     3463        }
     3464    }
     3465
     3466    ide.flushBlocksCache('variables');
     3467    ide.refreshPalette();
     3468};
     3469
     3470BlockMorph.prototype.doRefactorGlobalVar = function (
     3471    oldName,
     3472    newName,
     3473    justTheTemplate
     3474) {
     3475    var receiver = this.scriptTarget(),
     3476        ide = receiver.parentThatIsA(IDE_Morph),
     3477        stage = ide ? ide.stage : null,
     3478        oldWatcher = receiver.findVariableWatcher(oldName),
     3479        oldValue, newWatcher;
     3480
     3481    if (!isNil(ide.globalVariables.vars[newName])) {
     3482        this.varExistsError(ide);
     3483        return;
     3484    } else if (
     3485            detect(
     3486                stage.children,
     3487                function (any) {
     3488                    return any instanceof SpriteMorph &&
     3489                        any.hasSpriteVariable(newName);
     3490                })
     3491            ) {
     3492        this.varExistsError(ide, 'as a sprite local variable');
     3493        return;
     3494    } else {
     3495        oldValue = ide.globalVariables.getVar(oldName);
     3496        stage.deleteVariable(oldName);
     3497        stage.addVariable(newName, true);
     3498        ide.globalVariables.setVar(newName, oldValue);
     3499
     3500        if (oldWatcher && oldWatcher.isVisible) {
     3501            newWatcher = receiver.toggleVariableWatcher(
     3502                    newName,
     3503                    true
     3504                    );
     3505            newWatcher.setPosition(oldWatcher.position());
     3506        }
     3507
     3508        if (!justTheTemplate) {
     3509            stage.refactorVariableInstances(
     3510                oldName,
     3511                newName,
     3512                true
     3513            );
     3514            stage.globalBlocks.forEach(function (eachBlock) {
     3515                if (eachBlock.body) {
     3516                    eachBlock.body.expression.refactorVarInStack(
     3517                        oldName,
     3518                        newName
     3519                    );
     3520                }
     3521            });
     3522            stage.forAllChildren(function (child) {
     3523                if (child instanceof SpriteMorph) {
     3524                    child.refactorVariableInstances(
     3525                        oldName,
     3526                        newName,
     3527                        true
     3528                    );
     3529                    child.customBlocks.forEach(
     3530                        function (eachBlock) {
     3531                            eachBlock.body.expression
     3532                                .refactorVarInStack(
     3533                                    oldName,
     3534                                    newName
     3535                                );
     3536                        }
     3537                    );
     3538                }
     3539            });
     3540        }
     3541    }
     3542
     3543    ide.flushBlocksCache('variables');
     3544    ide.refreshPalette();
     3545};
     3546
    29753547// BlockMorph drawing
    29763548
     
    29813553        gradient,
    29823554        rightX,
    2983         holes = this.parts().filter(function (part) {
    2984             return part.isHole;
    2985         });
    2986 
     3555        holes = [];
     3556   
     3557    this.parts().forEach(function (part) {
     3558        if (part.isHole) {
     3559            holes.push(part);
     3560        } else if (part instanceof MultiArgMorph) {
     3561            holes.push.apply(holes, part.inputs().filter(function (inp) {
     3562                return inp.isHole;
     3563            }));
     3564        }
     3565    });
    29873566    if (this.isPredicate && (holes.length > 0)) {
    29883567        rightX = this.width() - this.rounding;
     
    30253604};
    30263605
     3606BlockMorph.prototype.hasLocationPin = function () {
     3607        return (this.isCustomBlock && !this.isGlobal) || this.isLocalVarTemplate;
     3608};
     3609
    30273610// BlockMorph highlighting
    30283611
     
    31733756    }
    31743757    if (!this.zebraContrast && isForced) {
    3175         return this.forceNormalColoring();
     3758        return this.forceNormalColoring(true);
    31763759    }
    31773760
     
    32093792};
    32103793
    3211 BlockMorph.prototype.forceNormalColoring = function () {
     3794BlockMorph.prototype.forceNormalColoring = function (silently) {
    32123795    var clr = SpriteMorph.prototype.blockColor[this.category];
    3213     this.setColor(clr, true); // silently
     3796    this.setColor(clr, silently);
    32143797    this.setLabelColor(
    32153798        new Color(255, 255, 255),
     
    32913874// BlockMorph copying
    32923875
    3293 BlockMorph.prototype.fullCopy = function (forClone) {
    3294     if (forClone) {
    3295         if (this.hasBlockVars()) {
    3296             forClone = false;
    3297         } else {
    3298             return copy(this);
    3299         }
    3300     }
     3876BlockMorph.prototype.fullCopy = function () {
    33013877    var ans = BlockMorph.uber.fullCopy.call(this);
    33023878    ans.removeHighlight();
     
    33083884        if (block instanceof SyntaxElementMorph) {
    33093885            block.cachedInputs = null;
    3310             if (block.definition) {
     3886            if (block.isCustomBlock) {
    33113887                block.initializeVariables();
    33123888            }
     
    33233899
    33243900BlockMorph.prototype.reactToTemplateCopy = function () {
     3901    if (this.isLocalVarTemplate) {
     3902        this.isLocalVarTemplate = null;
     3903        this.drawNew();
     3904        this.fixLayout();
     3905    }
    33253906    this.forceNormalColoring();
    33263907};
     
    33283909BlockMorph.prototype.hasBlockVars = function () {
    33293910    return this.anyChild(function (any) {
    3330         return any.definition && any.definition.variableNames.length;
     3911        return any.isCustomBlock &&
     3912            any.isGlobal &&
     3913            any.definition.variableNames.length;
    33313914    });
    33323915};
     
    33363919BlockMorph.prototype.mouseClickLeft = function () {
    33373920    var top = this.topBlock(),
    3338         receiver = top.receiver(),
     3921        receiver = top.scriptTarget(),
    33393922        shiftClicked = this.world().currentKey === 16,
    33403923        stage;
    33413924    if (shiftClicked && !this.isTemplate) {
    3342         return this.focus();
     3925        return this.selectForEdit().focus(); // enable coopy-on-edit
    33433926    }
    33443927    if (top instanceof PrototypeHatBlockMorph) {
    3345         return top.mouseClickLeft();
     3928        return; // top.mouseClickLeft();
    33463929    }
    33473930    if (receiver) {
    33483931        stage = receiver.parentThatIsA(StageMorph);
    33493932        if (stage) {
    3350             stage.threads.toggleProcess(top);
     3933            stage.threads.toggleProcess(top, receiver);
    33513934        }
    33523935    }
     
    33703953BlockMorph.prototype.activeProcess = function () {
    33713954    var top = this.topBlock(),
    3372         receiver = top.receiver(),
     3955        receiver = top.scriptTarget(),
    33733956        stage;
    33743957    if (top instanceof PrototypeHatBlockMorph) {
     
    33783961        stage = receiver.parentThatIsA(StageMorph);
    33793962        if (stage) {
    3380             return stage.threads.findProcess(top);
     3963            return stage.threads.findProcess(top, receiver);
    33813964        }
    33823965    }
     
    34434026};
    34444027
     4028// BlockMorph local method indicator drawing
     4029
     4030BlockMorph.prototype.drawMethodIcon = function (context) {
     4031    var ext = this.methodIconExtent(),
     4032        w = ext.x,
     4033        h = ext.y,
     4034        r = w / 2,
     4035        x = this.edge + this.labelPadding,
     4036        y = this.edge,
     4037        isNormal =
     4038            this.color === SpriteMorph.prototype.blockColor[this.category];
     4039
     4040    if (this.isPredicate) {
     4041        x = this.rounding;
     4042    }
     4043    if (this instanceof CommandBlockMorph) {
     4044        y += this.corner;
     4045    }
     4046    context.fillStyle = isNormal ? this.cachedClrBright : this.cachedClrDark;
     4047
     4048    // pin
     4049    context.beginPath();
     4050    context.arc(x + r, y + r, r, radians(-210), radians(30), false);
     4051    context.lineTo(x + r, y + h);
     4052    context.closePath();
     4053    context.fill();
     4054
     4055    // hole
     4056    context.fillStyle = this.cachedClr;
     4057    context.beginPath();
     4058    context.arc(x + r, y + r, r * 0.4, radians(0), radians(360), false);
     4059    context.closePath();
     4060    context.fill();
     4061};
     4062
    34454063// BlockMorph dragging and dropping
    34464064
     
    34774095    // answer a dictionary specifying where I am right now, so
    34784096    // I can slide back to it if I'm dropped somewhere else
    3479     var scripts = this.parentThatIsA(ScriptsMorph);
    3480     if (scripts) {
    3481         return {
    3482             origin: scripts,
    3483             position: this.position().subtract(scripts.position())
    3484         };
     4097    if (!(this.parent instanceof TemplateSlotMorph)) {
     4098        var scripts = this.parentThatIsA(ScriptsMorph);
     4099        if (scripts) {
     4100            return {
     4101                origin: scripts,
     4102                position: this.position().subtract(scripts.position())
     4103            };
     4104        }
    34854105    }
    34864106    return BlockMorph.uber.situation.call(this);
     
    34914111BlockMorph.prototype.prepareToBeGrabbed = function (hand) {
    34924112    var myself = this;
     4113    this.allInputs().forEach(function (input) {
     4114        delete input.bindingID;
     4115    });
    34934116    this.allComments().forEach(function (comment) {
    34944117        comment.startFollowing(myself, hand.world);
     
    35114134};
    35124135
    3513 BlockMorph.prototype.destroy = function () {
    3514     this.allComments().forEach(function (comment) {
    3515         comment.destroy();
    3516     });
    3517 
    3518     if (!this.parent || !this.parent.topBlock
    3519             && this.activeProcess()) {
    3520         this.activeProcess().stop();
    3521     }
    3522 
     4136BlockMorph.prototype.destroy = function (justThis) {
     4137    // private - use IDE_Morph.removeBlock() to first stop all my processes
     4138    if (justThis) {
     4139        if (!isNil(this.comment)) {
     4140            this.comment.destroy();
     4141        }
     4142    } else {
     4143        this.allComments().forEach(function (comment) {
     4144            comment.destroy();
     4145        });
     4146    }
    35234147    BlockMorph.uber.destroy.call(this);
    35244148};
     
    35654189    // register generic hat blocks
    35664190    if (this.selector === 'receiveCondition') {
    3567         receiver = top.receiver();
     4191        receiver = top.scriptTarget();
    35684192        if (receiver) {
    35694193            stage = receiver.parentThatIsA(StageMorph);
     
    36954319};
    36964320
     4321CommandBlockMorph.prototype.wrapAttachPoint = function () {
     4322    var cslot = detect( // could be a method making uses of caching...
     4323        this.inputs(), // ... although these already are cached
     4324        function (each) {return each instanceof CSlotMorph; }
     4325    );
     4326    if (cslot && !cslot.nestedBlock()) {
     4327        return new Point(
     4328            cslot.left() + (cslot.inset * 2) + cslot.corner,
     4329            cslot.top() + (cslot.corner * 2)
     4330        );
     4331    }
     4332    return null;
     4333};
     4334
    36974335CommandBlockMorph.prototype.dentLeft = function () {
    36984336    return this.left()
     
    37084346
    37094347CommandBlockMorph.prototype.attachTargets = function () {
    3710     var answer = [];
     4348    var answer = [],
     4349        tp;
    37114350    if (!(this instanceof HatBlockMorph)) {
     4351        tp = this.topAttachPoint();
    37124352        if (!(this.parent instanceof SyntaxElementMorph)) {
    37134353            answer.push({
    3714                 point: this.topAttachPoint(),
     4354                point: tp,
    37154355                element: this,
    37164356                loc: 'top',
     4357                type: 'block'
     4358            });
     4359        }
     4360        if (ScriptsMorph.prototype.enableNestedAutoWrapping ||
     4361                !this.parentThatIsA(CommandSlotMorph)) {
     4362            answer.push({
     4363                point: tp,
     4364                element: this,
     4365                loc: 'wrap',
    37174366                type: 'block'
    37184367            });
     
    37664415        dist,
    37674416        ref = [],
    3768         minDist = 1000;
     4417        minDist = 1000,
     4418        wrap;
    37694419
    37704420    if (!(this instanceof HatBlockMorph)) {
     
    37754425            }
    37764426        );
     4427        wrap = this.wrapAttachPoint();
     4428        if (wrap) {
     4429            ref.push(
     4430                {
     4431                    point: wrap,
     4432                    loc: 'wrap'
     4433                }
     4434            );
     4435        }
    37774436    }
    37784437    if (!bottomBlock.isStop()) {
     
    37844443        );
    37854444    }
    3786 
    37874445    this.allAttachTargets(target).forEach(function (eachTarget) {
    37884446        ref.forEach(function (eachRef) {
    3789             if (eachRef.loc !== eachTarget.loc) {
     4447            // match: either both locs are 'wrap' or both are different,
     4448            // none being 'wrap' (can this be expressed any better?)
     4449            if ((eachRef.loc === 'wrap' && (eachTarget.loc === 'wrap')) ||
     4450                ((eachRef.loc !== eachTarget.loc) &&
     4451                    (eachRef.loc !== 'wrap') && (eachTarget.loc !== 'wrap'))
     4452            ) {
    37904453                dist = eachRef.point.distanceTo(eachTarget.point);
    37914454                if ((dist < thresh) && (dist < minDist)) {
     
    37994462};
    38004463
    3801 CommandBlockMorph.prototype.snap = function () {
     4464CommandBlockMorph.prototype.snap = function (hand) {
    38024465    var target = this.closestAttachTarget(),
    38034466        scripts = this.parentThatIsA(ScriptsMorph),
     4467        before,
    38044468        next,
    38054469        offsetY,
     4470        cslot,
    38064471        affected;
    38074472
    3808     scripts.clearDropHistory();
     4473    scripts.clearDropInfo();
    38094474    scripts.lastDroppedBlock = this;
    3810 
    38114475    if (target === null) {
    38124476        this.startLayout();
     
    38144478        this.endLayout();
    38154479        CommandBlockMorph.uber.snap.call(this); // align stuck comments
     4480        if (hand) {
     4481            scripts.recordDrop(hand.grabOrigin);
     4482        }
    38164483        return;
    38174484    }
     
    38464513        this.setLeft(target.element.left());
    38474514        this.bottomBlock().nextBlock(target.element);
     4515    } else if (target.loc === 'wrap') {
     4516        cslot = detect( // this should be a method making use of caching
     4517            this.inputs(), // these are already cached, so maybe it's okay
     4518            function (each) {return each instanceof CSlotMorph; }
     4519        );
     4520        // assume the cslot is (still) empty, was checked determining the target
     4521        before = (target.element.parent);
     4522        scripts.lastWrapParent = before;
     4523
     4524        // adjust position of wrapping block
     4525        this.moveBy(target.point.subtract(cslot.slotAttachPoint()));
     4526
     4527        // wrap c-slot around target
     4528        cslot.nestedBlock(target.element);
     4529        if (before instanceof CommandBlockMorph) {
     4530            before.nextBlock(this);
     4531        } else if (before instanceof CommandSlotMorph) {
     4532            before.nestedBlock(this);
     4533        }
     4534
     4535        // fix zebra coloring.
     4536        // this could probably be generalized into the fixBlockColor mechanism
     4537        target.element.blockSequence().forEach(
     4538            function (cmd) {cmd.fixBlockColor(); }
     4539        );
    38484540    }
    38494541    this.fixBlockColor();
    38504542    this.endLayout();
    38514543    CommandBlockMorph.uber.snap.call(this); // align stuck comments
     4544    if (hand) {
     4545        scripts.recordDrop(hand.grabOrigin);
     4546    }
    38524547    if (this.snapSound) {
    38534548        this.snapSound.play();
     
    38584553    return ([
    38594554        'doStopThis',
    3860         'doStop',
    3861         'doStopBlock',
    3862         'doStopAll',
    38634555        'doForever',
    38644556        'doReport',
     
    38704562
    38714563CommandBlockMorph.prototype.userDestroy = function () {
     4564    var target = this.selectForEdit(); // enable copy-on-edit
     4565    if (target !== this) {
     4566        return this.userDestroy.call(target);
     4567    }
    38724568    if (this.nextBlock()) {
    38734569        this.userDestroyJustThis();
    38744570        return;
    38754571    }
    3876     var cslot = this.parentThatIsA(CSlotMorph);
    3877     this.destroy();
     4572
     4573    var scripts = this.parentThatIsA(ScriptsMorph),
     4574        ide = this.parentThatIsA(IDE_Morph),
     4575        parent = this.parentThatIsA(SyntaxElementMorph),
     4576        cslot = this.parentThatIsA(CSlotMorph);
     4577
     4578    // for undrop / redrop
     4579    if (scripts) {
     4580        scripts.clearDropInfo();
     4581        scripts.lastDroppedBlock = this;
     4582        scripts.recordDrop(this.situation());
     4583        scripts.dropRecord.action = 'delete';
     4584    }
     4585
     4586    if (ide) {
     4587        // also stop all active processes hatted by this block
     4588        ide.removeBlock(this);
     4589    } else {
     4590        this.destroy();
     4591    }
    38784592    if (cslot) {
    38794593        cslot.fixLayout();
     4594    }
     4595    if (parent) {
     4596        parent.reactToGrabOf(this); // fix highlight
    38804597    }
    38814598};
     
    38844601    // delete just this one block, reattach next block to the previous one,
    38854602    var scripts = this.parentThatIsA(ScriptsMorph),
     4603        ide = this.parentThatIsA(IDE_Morph),
    38864604        cs = this.parentThatIsA(CommandSlotMorph),
    38874605        pb,
    38884606        nb = this.nextBlock(),
    38894607        above,
     4608        parent = this.parentThatIsA(SyntaxElementMorph),
    38904609        cslot = this.parentThatIsA(CSlotMorph);
     4610
     4611    // for undrop / redrop
     4612    if (scripts) {
     4613        scripts.clearDropInfo();
     4614        scripts.lastDroppedBlock = this;
     4615        scripts.recordDrop(this.situation());
     4616        scripts.dropRecord.lastNextBlock = nb;
     4617        scripts.dropRecord.action = 'delete';
     4618    }
    38914619
    38924620    this.topBlock().fullChanged();
     
    38994627        above = cs;
    39004628    }
    3901     this.destroy();
     4629    if (ide) {
     4630        // also stop all active processes hatted by this block
     4631        ide.removeBlock(this, true); // just this block
     4632    } else {
     4633        this.destroy(true); // just this block
     4634    }
    39024635    if (nb) {
    39034636        if (above instanceof CommandSlotMorph) {
     
    39104643    } else if (cslot) {
    39114644        cslot.fixLayout();
     4645    }
     4646    if (parent) {
     4647        parent.reactToGrabOf(this); // fix highlight
    39124648    }
    39134649};
     
    39464682    }
    39474683
     4684    // draw location pin icon if applicable
     4685    if (this.hasLocationPin()) {
     4686        this.drawMethodIcon(context);
     4687    }
     4688
    39484689    // erase CommandSlots
    39494690    this.eraseHoles(context);
     
    44965237    this.setExtent(new Point(200, 80), silently);
    44975238    this.cachedSlotSpec = null; // don't serialize
     5239    this.isLocalVarTemplate = null; // don't serialize
    44985240};
    44995241
     
    45115253    }
    45125254
    4513     scripts.clearDropHistory();
     5255    scripts.clearDropInfo();
    45145256    scripts.lastDroppedBlock = this;
    45155257
     
    45415283    this.endLayout();
    45425284    ReporterBlockMorph.uber.snap.call(this);
     5285    if (hand) {
     5286        scripts.recordDrop(hand.grabOrigin);
     5287    }
    45435288};
    45445289
     
    46605405// ReporterBlock exporting picture with result bubble
    46615406
    4662 ReporterBlockMorph.prototype.ExportResultPic = function () {
     5407ReporterBlockMorph.prototype.exportResultPic = function () {
    46635408    var top = this.topBlock(),
    4664         receiver = top.receiver(),
     5409        receiver = top.scriptTarget(),
    46655410        stage;
    46665411    if (top !== this) {return; }
     
    46695414        if (stage) {
    46705415            stage.threads.stopProcess(top);
    4671             stage.threads.startProcess(top, false, true);
    4672         }
    4673     }
    4674 };
    4675 
     5416            stage.threads.startProcess(top, receiver, false, true);
     5417        }
     5418    }
     5419};
    46765420
    46775421// ReporterBlockMorph deleting
     
    46795423ReporterBlockMorph.prototype.userDestroy = function () {
    46805424    // make sure to restore default slot of parent block
     5425    var target = this.selectForEdit(); // enable copy-on-edit
     5426    if (target !== this) {
     5427        return this.userDestroy.call(target);
     5428    }
     5429
     5430    // for undrop / redrop
     5431    var scripts = this.parentThatIsA(ScriptsMorph);
     5432    if (scripts) {
     5433        scripts.clearDropInfo();
     5434        scripts.lastDroppedBlock = this;
     5435        scripts.recordDrop(this.situation());
     5436        scripts.dropRecord.action = 'delete';
     5437    }
     5438
    46815439    this.topBlock().fullChanged();
    46825440    this.prepareToBeGrabbed(this.world().hand);
     
    46995457    } else {
    47005458        this.drawRounded(context);
     5459    }
     5460
     5461    // draw location pin icon if applicable
     5462    if (this.hasLocationPin()) {
     5463        this.drawMethodIcon(context);
    47015464    }
    47025465
     
    51455908        return null;
    51465909    }
    5147     if (block.selector === 'reportGetVar' || (block instanceof RingMorph)) {
     5910    if (block.selector === 'reportGetVar' ||
     5911        // block.selector === 'reportListItem' ||
     5912        block.selector === 'reportJSFunction' ||
     5913        block.selector === 'reportAttributeOf' ||
     5914        block.selector === 'reportCompiled' ||
     5915        (block instanceof RingMorph)
     5916    ) {
    51485917        this.parent.silentReplaceInput(this, block);
    51495918    }
     
    52015970ScriptsMorph.prototype.isPreferringEmptySlots = true;
    52025971ScriptsMorph.prototype.enableKeyboard = true;
     5972ScriptsMorph.prototype.enableNestedAutoWrapping = true;
    52035973
    52045974// ScriptsMorph instance creation:
    52055975
    5206 function ScriptsMorph(owner) {
    5207     this.init(owner);
     5976function ScriptsMorph() {
     5977    this.init();
    52085978}
    52095979
    5210 ScriptsMorph.prototype.init = function (owner) {
    5211     this.owner = owner || null;
     5980ScriptsMorph.prototype.init = function () {
    52125981    this.feedbackColor = SyntaxElementMorph.prototype.feedbackColor;
    52135982    this.feedbackMorph = new BoxMorph();
     
    52205989    this.lastPreservedBlocks = null;
    52215990    this.lastNextBlock = null;
     5991    this.lastWrapParent = null;
    52225992
    52235993    // keyboard editing support:
     
    52275997    this.setColor(new Color(70, 70, 70));
    52285998    this.noticesTransparentClick = true;
     5999
     6000    // initialize "undrop" queue
     6001    this.isAnimating = false;
     6002    this.dropRecord = null;
     6003    this.recordDrop();
    52296004};
    52306005
    52316006// ScriptsMorph deep copying:
    52326007
    5233 ScriptsMorph.prototype.fullCopy = function (forClone) {
     6008ScriptsMorph.prototype.fullCopy = function () {
    52346009    var cpy = new ScriptsMorph(),
    52356010        pos = this.position(),
     
    52406015    this.children.forEach(function (morph) {
    52416016        if (!morph.block) { // omit anchored comments
    5242             child = morph.fullCopy(forClone);
     6017            child = morph.fullCopy();
    52436018            cpy.add(child);
    5244             if (!forClone) {
    5245                 child.setPosition(morph.position().subtract(pos));
    5246                 if (child instanceof BlockMorph) {
    5247                     child.allComments().forEach(function (comment) {
    5248                         comment.align(child);
    5249                     });
    5250                 }
     6019            child.setPosition(morph.position().subtract(pos));
     6020            if (child instanceof BlockMorph) {
     6021                child.allComments().forEach(function (comment) {
     6022                    comment.align(child);
     6023                });
    52516024            }
    52526025        }
    52536026    });
    5254     if (!forClone) {
    5255         cpy.adjustBounds();
    5256     }
     6027    cpy.adjustBounds();
    52576028    return cpy;
    52586029};
     
    53326103    if (!target) {
    53336104        return null;
     6105    }
     6106    if (target.loc === 'wrap') {
     6107        this.showCSlotWrapFeedback(block, target.element);
     6108        return;
    53346109    }
    53356110    this.add(this.feedbackMorph);
     
    53896164};
    53906165
     6166ScriptsMorph.prototype.showCSlotWrapFeedback = function (srcBlock, trgBlock) {
     6167    var clr;
     6168    this.feedbackMorph.bounds = trgBlock.fullBounds()
     6169        .expandBy(BlockMorph.prototype.corner);
     6170    this.feedbackMorph.edge = SyntaxElementMorph.prototype.corner;
     6171    this.feedbackMorph.border = Math.max(
     6172        SyntaxElementMorph.prototype.edge,
     6173        3
     6174    );
     6175    this.add(this.feedbackMorph);
     6176    clr = srcBlock.color.lighter(40);
     6177    this.feedbackMorph.color = clr.copy();
     6178    this.feedbackMorph.color.a = 0.1;
     6179    this.feedbackMorph.borderColor = clr;
     6180    this.feedbackMorph.drawNew();
     6181    this.feedbackMorph.changed();
     6182};
     6183
    53916184ScriptsMorph.prototype.closestInput = function (reporter, hand) {
    53926185    // passing the hand is optional (when dragging reporters)
     
    55356328    var menu = new MenuMorph(this),
    55366329        ide = this.parentThatIsA(IDE_Morph),
     6330        shiftClicked = this.world().currentKey === 16,
    55376331        blockEditor,
    55386332        myself = this,
    5539         obj = this.owner,
     6333        obj = this.scriptTarget(),
     6334        hasUndropQueue,
    55406335        stage = obj.parentThatIsA(StageMorph);
     6336
     6337    function addOption(label, toggle, test, onHint, offHint) {
     6338        var on = '\u2611 ',
     6339            off = '\u2610 ';
     6340        menu.addItem(
     6341            (test ? on : off) + localize(label),
     6342            toggle,
     6343            test ? onHint : offHint
     6344        );
     6345    }
    55416346
    55426347    if (!ide) {
     
    55466351        }
    55476352    }
     6353    if (this.dropRecord) {
     6354        if (this.dropRecord.lastRecord) {
     6355            hasUndropQueue = true;
     6356            menu.addPair(
     6357                [
     6358                    new SymbolMorph(
     6359                        'turnBack',
     6360                        MorphicPreferences.menuFontSize
     6361                    ),
     6362                    localize('undrop')
     6363                ],
     6364                'undrop',
     6365                '^Z',
     6366                'undo the last\nblock drop\nin this pane'
     6367            );
     6368        }
     6369        if (this.dropRecord.nextRecord) {
     6370            hasUndropQueue = true;
     6371            menu.addPair(
     6372                [
     6373                    new SymbolMorph(
     6374                        'turnForward',
     6375                        MorphicPreferences.menuFontSize
     6376                    ),
     6377                    localize('redrop')
     6378                ],
     6379                'redrop',
     6380                '^Y',
     6381                'redo the last undone\nblock drop\nin this pane'
     6382            );
     6383        }
     6384        if (hasUndropQueue) {
     6385            if (shiftClicked) {
     6386                menu.addItem(
     6387                    "clear undrop queue",
     6388                    function () {
     6389                        myself.dropRecord = null;
     6390                        myself.clearDropInfo();
     6391                        myself.recordDrop();
     6392                    },
     6393                    'forget recorded block drops\non this pane',
     6394                    new Color(100, 0, 0)
     6395                );
     6396            }
     6397            menu.addLine();
     6398        }
     6399    }
     6400
    55486401    menu.addItem('clean up', 'cleanUp', 'arrange scripts\nvertically');
    55496402    menu.addItem('add comment', 'addComment');
    5550     if (this.lastDroppedBlock) {
    5551         menu.addItem(
    5552             'undrop',
    5553             'undrop',
    5554             'undo the last\nblock drop\nin this pane'
    5555         );
    5556     }
    55576403    menu.addItem(
    55586404        'scripts pic...',
     
    55626408    if (ide) {
    55636409        menu.addLine();
     6410        if (!blockEditor && obj.exemplar) {
     6411                addOption(
     6412                    'inherited',
     6413                    function () {
     6414                        obj.toggleInheritanceForAttribute('scripts');
     6415                    },
     6416                    obj.inheritsAttribute('scripts'),
     6417                    'uncheck to\ndisinherit',
     6418                    localize('check to inherit\nfrom')
     6419                        + ' ' + obj.exemplar.name
     6420                );
     6421        }
    55646422        menu.addItem(
    55656423            'make a block...',
     
    55946452
    55956453ScriptsMorph.prototype.cleanUp = function () {
    5596     var origin = this.topLeft(),
    5597         y = this.cleanUpMargin,
    5598         myself = this;
    5599     this.children.sort(function (a, b) {
     6454    var target = this.selectForEdit(), // enable copy-on-edit
     6455        origin = target.topLeft(),
     6456        y = target.cleanUpMargin;
     6457    target.children.sort(function (a, b) {
    56006458        // make sure the prototype hat block always stays on top
    56016459        return a instanceof PrototypeHatBlockMorph ? 0 : a.top() - b.top();
     
    56046462            return; // skip anchored comments
    56056463        }
    5606         child.setPosition(origin.add(new Point(myself.cleanUpMargin, y)));
     6464        child.setPosition(origin.add(new Point(target.cleanUpMargin, y)));
    56076465        if (child instanceof BlockMorph) {
    56086466            child.allComments().forEach(function (comment) {
     
    56106468            });
    56116469        }
    5612         y += child.stackHeight() + myself.cleanUpSpacing;
     6470        y += child.stackHeight() + target.cleanUpSpacing;
    56136471    });
    5614     if (this.parent) {
    5615         this.setPosition(this.parent.topLeft());
    5616     }
    5617     this.adjustBounds();
     6472    if (target.parent) {
     6473        target.setPosition(target.parent.topLeft());
     6474    }
     6475    target.adjustBounds();
    56186476};
    56196477
     
    56246482        ide.saveCanvasAs(
    56256483            pic,
    5626             ide.projetName || localize('Untitled') + ' ' +
    5627                 localize('script pic'),
    5628             true // request new window
     6484            (ide.projectName || localize('untitled')) + ' ' +
     6485                localize('script pic')
    56296486        );
    56306487    }
     
    56576514
    56586515ScriptsMorph.prototype.addComment = function () {
    5659     new CommentMorph().pickUp(this.world());
    5660 };
     6516    var ide = this.parentThatIsA(IDE_Morph),
     6517        blockEditor = this.parentThatIsA(BlockEditorMorph),
     6518        world = this.world();
     6519    new CommentMorph().pickUp(world);
     6520    // register the drop-origin, so the element can
     6521    // slide back to its former situation if dropped
     6522    // somewhere where it gets rejected
     6523    if (!ide && blockEditor) {
     6524        ide = blockEditor.target.parentThatIsA(IDE_Morph);
     6525    }
     6526    if (ide) {
     6527        world.hand.grabOrigin = {
     6528            origin: ide.palette,
     6529            position: ide.palette.center()
     6530        };
     6531    }
     6532};
     6533
     6534// ScriptsMorph undrop / redrop
    56616535
    56626536ScriptsMorph.prototype.undrop = function () {
    5663     if (!this.lastDroppedBlock) {return; }
    5664     if (this.lastDroppedBlock instanceof CommandBlockMorph) {
    5665         if (this.lastNextBlock) {
    5666             this.add(this.lastNextBlock);
    5667         }
    5668         if (this.lastDropTarget) {
    5669             if (this.lastDropTarget.loc === 'bottom') {
    5670                 if (this.lastDropTarget.type === 'slot') {
    5671                     if (this.lastNextBlock) {
    5672                         this.lastDropTarget.element.nestedBlock(
    5673                             this.lastNextBlock
     6537    var myself = this;
     6538    if (this.isAnimating) {return; }
     6539    if (!this.dropRecord || !this.dropRecord.lastRecord) {return; }
     6540    if (!this.dropRecord.situation) {
     6541        this.dropRecord.situation =
     6542            this.dropRecord.lastDroppedBlock.situation();
     6543    }
     6544    this.isAnimating = true;
     6545    this.dropRecord.lastDroppedBlock.slideBackTo(
     6546        this.dropRecord.lastOrigin,
     6547        null,
     6548        this.recoverLastDrop(),
     6549        function () {
     6550            myself.updateToolbar();
     6551            myself.isAnimating = false;
     6552        }
     6553    );
     6554    this.dropRecord = this.dropRecord.lastRecord;
     6555};
     6556
     6557ScriptsMorph.prototype.redrop = function () {
     6558    var myself = this;
     6559    if (this.isAnimating) {return; }
     6560    if (!this.dropRecord || !this.dropRecord.nextRecord) {return; }
     6561    this.dropRecord = this.dropRecord.nextRecord;
     6562    if (this.dropRecord.action === 'delete') {
     6563        this.recoverLastDrop(true);
     6564        this.dropRecord.lastDroppedBlock.destroy();
     6565        this.updateToolbar();
     6566    } else {
     6567        this.isAnimating = true;
     6568        this.dropRecord.lastDroppedBlock.slideBackTo(
     6569            this.dropRecord.situation,
     6570            null,
     6571            this.recoverLastDrop(true),
     6572            function () {
     6573                myself.updateToolbar();
     6574                myself.isAnimating = false;
     6575            }
     6576        );
     6577    }
     6578};
     6579
     6580ScriptsMorph.prototype.recoverLastDrop = function (forRedrop) {
     6581    // retrieve the block last touched by the user and answer a function
     6582    // to be called after the animation that moves it back right before
     6583    // dropping it into its former situation
     6584    var rec = this.dropRecord,
     6585        dropped,
     6586        onBeforeDrop,
     6587        parent;
     6588
     6589    if (!rec || !rec.lastDroppedBlock) {
     6590        throw new Error('nothing to undrop');
     6591    }
     6592    dropped = rec.lastDroppedBlock;
     6593    parent = dropped.parent;
     6594    if (dropped instanceof CommandBlockMorph) {
     6595        if (rec.lastNextBlock) {
     6596            if (rec.action === 'delete') {
     6597                if (forRedrop) {
     6598                    this.add(rec.lastNextBlock);
     6599                }
     6600            } else {
     6601                this.add(rec.lastNextBlock);
     6602            }
     6603        }
     6604        if (rec.lastDropTarget) {
     6605            if (rec.lastDropTarget.loc === 'bottom') {
     6606                if (rec.lastDropTarget.type === 'slot') {
     6607                    if (rec.lastNextBlock) {
     6608                        rec.lastDropTarget.element.nestedBlock(
     6609                            rec.lastNextBlock
    56746610                        );
    56756611                    }
    56766612                } else { // 'block'
    5677                     if (this.lastNextBlock) {
    5678                         this.lastDropTarget.element.nextBlock(
    5679                             this.lastNextBlock
     6613                    if (rec.lastNextBlock) {
     6614                        rec.lastDropTarget.element.nextBlock(
     6615                            rec.lastNextBlock
    56806616                        );
    56816617                    }
    56826618                }
    5683             } else if (this.lastDropTarget.loc === 'top') {
    5684                 this.add(this.lastDropTarget.element);
     6619            } else if (rec.lastDropTarget.loc === 'top') {
     6620                this.add(rec.lastDropTarget.element);
     6621            } else if (rec.lastDropTarget.loc === 'wrap') {
     6622                var cslot = detect( // could be cached...
     6623                    rec.lastDroppedBlock.inputs(), // ...although these are
     6624                    function (each) {return each instanceof CSlotMorph; }
     6625                );
     6626                if (rec.lastWrapParent instanceof CommandBlockMorph) {
     6627                    if (forRedrop) {
     6628                        onBeforeDrop = function () {
     6629                            cslot.nestedBlock(rec.lastDropTarget.element);
     6630                        };
     6631                    } else {
     6632                        rec.lastWrapParent.nextBlock(
     6633                            rec.lastDropTarget.element
     6634                        );
     6635                    }
     6636                } else if (rec.lastWrapParent instanceof CommandSlotMorph) {
     6637                    if (forRedrop) {
     6638                        onBeforeDrop = function () {
     6639                            cslot.nestedBlock(rec.lastDropTarget.element);
     6640                        };
     6641                    } else {
     6642                        rec.lastWrapParent.nestedBlock(
     6643                            rec.lastDropTarget.element
     6644                        );
     6645                    }
     6646                } else {
     6647                    this.add(rec.lastDropTarget.element);
     6648                }
     6649
     6650                // fix zebra coloring.
     6651                // this could be generalized into the fixBlockColor mechanism
     6652                rec.lastDropTarget.element.blockSequence().forEach(
     6653                    function (cmd) {cmd.fixBlockColor(); }
     6654                );
     6655                cslot.fixLayout();
    56856656            }
    56866657        }
    5687     } else { // ReporterBlockMorph
    5688         if (this.lastDropTarget) {
    5689             this.lastDropTarget.replaceInput(
    5690                 this.lastDroppedBlock,
    5691                 this.lastReplacedInput
     6658    } else if (dropped instanceof ReporterBlockMorph) {
     6659        if (rec.lastDropTarget) {
     6660            rec.lastDropTarget.replaceInput(
     6661                rec.lastDroppedBlock,
     6662                rec.lastReplacedInput
    56926663            );
    5693             this.lastDropTarget.fixBlockColor(null, true);
    5694             if (this.lastPreservedBlocks) {
    5695                 this.lastPreservedBlocks.forEach(function (morph) {
     6664            rec.lastDropTarget.fixBlockColor(null, true);
     6665            if (rec.lastPreservedBlocks) {
     6666                rec.lastPreservedBlocks.forEach(function (morph) {
    56966667                    morph.destroy();
    56976668                });
    56986669            }
    56996670        }
    5700     }
    5701     this.lastDroppedBlock.pickUp(this.world());
    5702     this.clearDropHistory();
    5703 };
    5704 
    5705 
    5706 ScriptsMorph.prototype.clearDropHistory = function () {
     6671    } else if (dropped instanceof CommentMorph) {
     6672        if (forRedrop && rec.lastDropTarget) {
     6673            onBeforeDrop = function () {
     6674                rec.lastDropTarget.element.comment = dropped;
     6675                dropped.block = rec.lastDropTarget.element;
     6676                dropped.align();
     6677            };
     6678        }
     6679    } else {
     6680        throw new Error('unsupported action for ' + dropped);
     6681    }
     6682    this.clearDropInfo();
     6683    dropped.prepareToBeGrabbed(this.world().hand);
     6684    if (dropped instanceof CommentMorph) {
     6685        dropped.removeShadow();
     6686    }
     6687    this.add(dropped);
     6688    parent.reactToGrabOf(dropped);
     6689    if (dropped instanceof ReporterBlockMorph && parent instanceof BlockMorph) {
     6690        parent.changed();
     6691    }
     6692    if (rec.action === 'delete') {
     6693        if (forRedrop && rec.lastNextBlock) {
     6694            if (parent instanceof CommandBlockMorph) {
     6695                parent.nextBlock(rec.lastNextBlock);
     6696            } else if (parent instanceof CommandSlotMorph) {
     6697                parent.nestedBlock(rec.lastNextBlock);
     6698            }
     6699        }
     6700
     6701        // animate "undelete"
     6702        if (!forRedrop) {
     6703            dropped.moveBy(new Point(-100, -20));
     6704        }
     6705    }
     6706    return onBeforeDrop;
     6707};
     6708
     6709ScriptsMorph.prototype.clearDropInfo = function () {
    57076710    this.lastDroppedBlock = null;
    57086711    this.lastReplacedInput = null;
     
    57106713    this.lastPreservedBlocks = null;
    57116714    this.lastNextBlock = null;
     6715    this.lastWrapParent = null;
     6716};
     6717
     6718ScriptsMorph.prototype.recordDrop = function (lastGrabOrigin) {
     6719    // support for "undrop" / "redrop"
     6720     var record = {
     6721        lastDroppedBlock: this.lastDroppedBlock,
     6722        lastReplacedInput: this.lastReplacedInput,
     6723        lastDropTarget: this.lastDropTarget,
     6724        lastPreservedBlocks: this.lastPreservedBlocks,
     6725        lastNextBlock: this.lastNextBlock,
     6726        lastWrapParent: this.lastWrapParent,
     6727        lastOrigin: lastGrabOrigin,
     6728        action: null,
     6729        situation: null,
     6730        lastRecord: this.dropRecord,
     6731        nextRecord: null
     6732    };
     6733    if (this.dropRecord) {
     6734        this.dropRecord.nextRecord = record;
     6735    }
     6736    this.dropRecord = record;
     6737    this.updateToolbar();
     6738};
     6739
     6740ScriptsMorph.prototype.addToolbar = function () {
     6741    var toolBar = new AlignmentMorph(),
     6742        myself = this,
     6743        shade = new Color(140, 140, 140);
     6744
     6745    toolBar.respectHiddens = true;
     6746    toolBar.undoButton = new PushButtonMorph(
     6747        this,
     6748        "undrop",
     6749        new SymbolMorph("turnBack", 12)
     6750    );
     6751    toolBar.undoButton.alpha = 0.2;
     6752    toolBar.undoButton.padding = 2;
     6753    // toolBar.undoButton.hint = 'undo the last\nblock drop\nin this pane';
     6754    toolBar.undoButton.labelShadowColor = shade;
     6755    toolBar.undoButton.drawNew();
     6756    toolBar.undoButton.fixLayout();
     6757    toolBar.add(toolBar.undoButton);
     6758
     6759    toolBar.redoButton = new PushButtonMorph(
     6760        this,
     6761        "redrop",
     6762        new SymbolMorph("turnForward", 12)
     6763    );
     6764    toolBar.redoButton.alpha = 0.2;
     6765    toolBar.redoButton.padding = 2;
     6766    // toolBar.redoButton.hint = 'redo the last undone\nblock drop\nin this pane';
     6767    toolBar.redoButton.labelShadowColor = shade;
     6768    toolBar.redoButton.drawNew();
     6769    toolBar.redoButton.fixLayout();
     6770    toolBar.add(toolBar.redoButton);
     6771
     6772    toolBar.keyboardButton = new ToggleButtonMorph(
     6773        null, // colors
     6774        this, // target
     6775        "toggleKeyboardEntry",
     6776        [
     6777            new SymbolMorph('keyboard', 12),
     6778            new SymbolMorph('keyboardFilled', 12)
     6779        ],
     6780                function () { // query
     6781                        return !isNil(myself.focus);
     6782                }
     6783    );
     6784    toolBar.keyboardButton.alpha = 0.2;
     6785    toolBar.keyboardButton.padding = 2;
     6786    toolBar.keyboardButton.hint = 'use the keyboard\nto enter blocks';
     6787    //toolBar.keyboardButton.pressColor = new Color(40, 40, 40);
     6788    toolBar.keyboardButton.labelShadowColor = shade;
     6789    toolBar.keyboardButton.drawNew();
     6790    toolBar.keyboardButton.fixLayout();
     6791    toolBar.add(toolBar.keyboardButton);
     6792
     6793    return toolBar;
     6794};
     6795
     6796ScriptsMorph.prototype.updateToolbar = function () {
     6797    var sf = this.parentThatIsA(ScrollFrameMorph);
     6798    if (!sf) {return; }
     6799    if (!sf.toolBar) {
     6800        sf.toolBar = this.addToolbar();
     6801        sf.add(sf.toolBar);
     6802    }
     6803    if (this.enableKeyboard) {
     6804        sf.toolBar.keyboardButton.show();
     6805        sf.toolBar.keyboardButton.refresh();
     6806    } else {
     6807        sf.toolBar.keyboardButton.hide();
     6808    }
     6809    if (this.dropRecord) {
     6810        if (this.dropRecord.lastRecord) {
     6811            if (!sf.toolBar.undoButton.isVisible) {
     6812                sf.toolBar.undoButton.show();
     6813            }
     6814        } else {
     6815            if (sf.toolBar.undoButton.isVisible) {
     6816                sf.toolBar.undoButton.hide();
     6817            }
     6818        }
     6819        if (this.dropRecord.nextRecord) {
     6820            if (!sf.toolBar.redoButton.isVisible) {
     6821                sf.toolBar.redoButton.show();
     6822                sf.toolBar.undoButton.mouseLeave();
     6823            }
     6824        } else {
     6825            if (sf.toolBar.redoButton.isVisible) {
     6826                sf.toolBar.redoButton.hide();
     6827            }
     6828        }
     6829    }
     6830        if (detect(
     6831                        sf.toolBar.children,
     6832            function (each) {return each.isVisible; }
     6833    )) {
     6834            sf.toolBar.fixLayout();
     6835            sf.adjustToolBar();
     6836        }
    57126837};
    57136838
     
    57696894};
    57706895
     6896ScriptsMorph.prototype.selectForEdit = function () {
     6897    var ide = this.parentThatIsA(IDE_Morph),
     6898        rcvr = ide ? ide.currentSprite : null;
     6899    if (rcvr && rcvr.inheritsAttribute('scripts')) {
     6900        // copy on write:
     6901        this.feedbackMorph.destroy();
     6902        rcvr.shadowAttribute('scripts');
     6903        return rcvr.scripts;
     6904    }
     6905    return this;
     6906};
     6907
    57716908// ScriptsMorph keyboard support
    57726909
    57736910ScriptsMorph.prototype.edit = function (pos) {
    5774     var world = this.world();
     6911    var target,
     6912                world = this.world();
    57756913    if (this.focus) {this.focus.stopEditing(); }
    57766914    world.stopEditing();
    57776915    if (!ScriptsMorph.prototype.enableKeyboard) {return; }
    5778     this.focus = new ScriptFocusMorph(this, this, pos);
    5779     this.focus.getFocus(world);
    5780 };
     6916    target = this.selectForEdit(); // enable copy-on-edit
     6917    target.focus = new ScriptFocusMorph(target, target, pos);
     6918    target.focus.getFocus(world);
     6919};
     6920
     6921ScriptsMorph.prototype.toggleKeyboardEntry = function () {
     6922        // when the user clicks the keyboard button in the toolbar
     6923    var target, sorted,
     6924        world = this.world();
     6925    if (this.focus) {
     6926        this.focus.stopEditing();
     6927        return;
     6928    }
     6929    world.stopEditing();
     6930    if (!ScriptsMorph.prototype.enableKeyboard) {return; }
     6931    target = this.selectForEdit(); // enable copy-on-edit
     6932    target.focus = new ScriptFocusMorph(target, target, target.position());
     6933    target.focus.getFocus(world);
     6934    sorted = target.focus.sortedScripts();
     6935    if (sorted.length) {
     6936        target.focus.element = sorted[0];
     6937        if (target.focus.element instanceof HatBlockMorph) {
     6938            target.focus.nextCommand();
     6939        }
     6940    } else {
     6941        target.focus.moveBy(new Point(50, 50));
     6942    }
     6943    target.focus.fixLayout();
     6944};
     6945
     6946// ScriptsMorph context - scripts target
     6947
     6948ScriptsMorph.prototype.scriptTarget = function () {
     6949    // answer the sprite or stage that this script editor acts on,
     6950    // if the user clicks on a block.
     6951    // NOTE: since scripts can be shared by more than a single sprite
     6952    // this method only gives the desired result within the context of
     6953    // the user actively clicking on a block inside the IDE
     6954    // there is no direct relationship between a block or a scripts editor
     6955    //  and a sprite.
     6956    var editor = this.parentThatIsA(IDE_Morph);
     6957    if (editor) {
     6958        return editor.currentSprite;
     6959    }
     6960    editor = this.parentThatIsA(BlockEditorMorph);
     6961    if (editor) {
     6962        return editor.target;
     6963    }
     6964    throw new Error('script target bannot be found for orphaned scripts');
     6965};
     6966
    57816967
    57826968// ArgMorph //////////////////////////////////////////////////////////
     
    58307016    if (block) {
    58317017        top = block.topBlock();
    5832         receiver = top.receiver();
     7018        receiver = top.scriptTarget();
    58337019        if (top instanceof PrototypeHatBlockMorph) {
    58347020            return;
     
    58367022        if (receiver) {
    58377023            stage = receiver.parentThatIsA(StageMorph);
    5838             if (stage && stage.isThreadSafe) {
    5839                 stage.threads.startProcess(top, stage.isThreadSafe);
     7024            if (stage && (stage.isThreadSafe ||
     7025                    Process.prototype.enableSingleStepping)) {
     7026                stage.threads.startProcess(top, receiver, stage.isThreadSafe);
    58407027            } else {
    58417028                top.mouseClickLeft();
     
    58447031    }
    58457032};
    5846 
    58477033
    58487034// ArgMorph drag & drop: for demo puposes only
     
    70048190    contents.drawNew();
    70058191
     8192        this.selectedBlock = null;
     8193
    70068194    this.isUnevaluated = false;
    70078195    this.choices = choiceDict || null; // object, function or selector
     
    70498237};
    70508238
    7051 InputSlotMorph.prototype.setContents = function (aStringOrFloat) {
     8239InputSlotMorph.prototype.setContents = function (data) {
     8240        // data can be a String, Float, or "wish" Block
    70528241    var cnts = this.contents(),
    7053         dta = aStringOrFloat,
     8242        dta = data,
    70548243        isConstant = dta instanceof Array;
     8244
     8245        if (this.selectedBlock) {
     8246                this.selectedBlock = null;
     8247        }
     8248
    70558249    if (isConstant) {
    70568250        dta = localize(dta[0]);
    70578251        cnts.isItalic = !this.isReadOnly;
     8252    } else if (dta instanceof BlockMorph) {
     8253        this.selectedBlock = dta;
     8254        dta = ''; // make sure the contents text emptied
    70588255    } else { // assume dta is a localizable choice if it's a key in my choices
    70598256        cnts.isItalic = false;
    7060         if (this.choices !== null && this.choices[dta] instanceof Array) {
     8257        if (!isNil(this.choices) && this.choices[dta] instanceof Array) {
    70618258            return this.setContents(this.choices[dta]);
    70628259        }
     
    70768273
    70778274    // remember the constant, if any
    7078     this.constant = isConstant ? aStringOrFloat : null;
     8275    this.constant = isConstant ? data : null;
     8276};
     8277
     8278InputSlotMorph.prototype.userSetContents = function (aStringOrFloat) {
     8279    // enable copy-on-edit for inherited scripts
     8280    this.selectForEdit().setContents(aStringOrFloat);
    70798281};
    70808282
     
    70828284
    70838285InputSlotMorph.prototype.dropDownMenu = function (enableKeyboard) {
    7084     var choices = this.choices,
    7085         key,
    7086         menu = new MenuMorph(
    7087             this.setContents,
    7088             null,
    7089             this,
    7090             this.fontSize
    7091         );
    7092 
    7093     if (choices instanceof Function) {
    7094         choices = choices.call(this);
    7095     } else if (isString(choices)) {
    7096         choices = this[choices]();
    7097     }
    7098     if (!choices) {
    7099         return null;
    7100     }
    7101     menu.addItem(' ', null);
    7102     for (key in choices) {
    7103         if (Object.prototype.hasOwnProperty.call(choices, key)) {
    7104             if (key[0] === '~') {
    7105                 menu.addLine();
    7106             // } else if (key.indexOf('§_def') === 0) {
    7107             //     menu.addItem(choices[key].blockInstance(), choices[key]);
    7108             } else {
    7109                 menu.addItem(key, choices[key]);
    7110             }
    7111         }
     8286    var menu = this.menuFromDict(this.choices, null, enableKeyboard);
     8287    if (!menu) { // has already happened
     8288        return;
    71128289    }
    71138290    if (menu.items.length > 0) {
     
    71188295            menu.popUpAtHand(this.world());
    71198296        }
    7120     } else {
    7121         return null;
    7122     }
     8297    }
     8298};
     8299
     8300InputSlotMorph.prototype.menuFromDict = function (
     8301    choices,
     8302    noEmptyOption,
     8303    enableKeyboard)
     8304{
     8305    var key, dial,
     8306        myself = this,
     8307        menu = new MenuMorph(
     8308            this.userSetContents,
     8309            null,
     8310            this,
     8311            this.fontSize
     8312        );
     8313
     8314        function update (num) {
     8315        myself.setContents(num);
     8316        myself.reactToSliderEdit();
     8317        }
     8318
     8319    if (choices instanceof Function) {
     8320        choices = choices.call(this);
     8321    } else if (isString(choices)) {
     8322        choices = this[choices](enableKeyboard);
     8323        if (!choices) { // menu has already happened
     8324            return;
     8325        }
     8326    }
     8327    if (!noEmptyOption) {
     8328        menu.addItem(' ', null);
     8329    }
     8330    for (key in choices) {
     8331        if (Object.prototype.hasOwnProperty.call(choices, key)) {
     8332            if (key[0] === '~') {
     8333                menu.addLine();
     8334            } else if (key.indexOf('§_def') === 0) {
     8335                menu.addItem(choices[key], choices[key]);
     8336            } else if (key.indexOf('§_dir') === 0) {
     8337                            dial = new DialMorph();
     8338                        dial.rootForGrab = function () {return this; };
     8339                        dial.target = this;
     8340                        dial.action = update;
     8341                        dial.fillColor = this.parent.color;
     8342                        dial.setRadius(this.fontSize * 3);
     8343                                dial.setValue(this.evaluate(), false, true);
     8344                        menu.addLine();
     8345                            menu.items.push(dial);
     8346                menu.addLine();
     8347            } else if (choices[key] instanceof Object &&
     8348                    !(choices[key] instanceof Array) &&
     8349                    (typeof choices[key] !== 'function')) {
     8350                menu.addMenu(key, this.menuFromDict(choices[key], true));
     8351            } else {
     8352                menu.addItem(key, choices[key]);
     8353            }
     8354        }
     8355    }
     8356    return menu;
    71238357};
    71248358
    71258359InputSlotMorph.prototype.messagesMenu = function () {
    71268360    var dict = {},
    7127         rcvr = this.parentThatIsA(BlockMorph).receiver(),
     8361        rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
    71288362        stage = rcvr.parentThatIsA(StageMorph),
    71298363        myself = this,
     
    71538387        );
    71548388    };
    7155 
    71568389    return dict;
    71578390};
     
    71598392InputSlotMorph.prototype.messagesReceivedMenu = function () {
    71608393    var dict = {'any message': ['any message']},
    7161         rcvr = this.parentThatIsA(BlockMorph).receiver(),
     8394        rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
    71628395        stage = rcvr.parentThatIsA(StageMorph),
    71638396        myself = this,
     
    71858418        );
    71868419    };
    7187 
    71888420    return dict;
    71898421};
     
    71958427            'pen trails' : ['pen trails']
    71968428        },
    7197         rcvr = this.parentThatIsA(BlockMorph).receiver(),
     8429        rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
    71988430        stage = rcvr.parentThatIsA(StageMorph),
    71998431        allNames = [];
    72008432
    72018433    stage.children.forEach(function (morph) {
    7202         if (morph instanceof SpriteMorph && !morph.isClone) {
     8434        if (morph instanceof SpriteMorph && !morph.isTemporary) {
    72038435            if (morph.name !== rcvr.name) {
    72048436                allNames = allNames.concat(morph.name);
     
    72168448
    72178449InputSlotMorph.prototype.distancesMenu = function () {
    7218     var dict = {
    7219             'mouse-pointer' : ['mouse-pointer']
    7220         },
    7221         rcvr = this.parentThatIsA(BlockMorph).receiver(),
     8450        var block = this.parentThatIsA(BlockMorph),
     8451        dict = {},
     8452        rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
    72228453        stage = rcvr.parentThatIsA(StageMorph),
    72238454        allNames = [];
    72248455
     8456        if (block && (block.selector !== 'reportRelationTo')) {
     8457            dict['random position'] = ['random position'];
     8458        }
     8459        dict['mouse-pointer'] = ['mouse-pointer'];
     8460    dict.center = ['center'];
     8461
    72258462    stage.children.forEach(function (morph) {
    7226         if (morph instanceof SpriteMorph) {
     8463        if (morph instanceof SpriteMorph && !morph.isTemporary) {
    72278464            if (morph.name !== rcvr.name) {
    72288465                allNames = allNames.concat(morph.name);
     
    72418478InputSlotMorph.prototype.clonablesMenu = function () {
    72428479    var dict = {},
    7243         rcvr = this.parentThatIsA(BlockMorph).receiver(),
     8480        rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
    72448481        stage = rcvr.parentThatIsA(StageMorph),
    72458482        allNames = [];
     
    72498486    }
    72508487    stage.children.forEach(function (morph) {
    7251         if (morph instanceof SpriteMorph && !morph.isClone) {
     8488        if (morph instanceof SpriteMorph && !morph.isTemporary) {
    72528489            allNames = allNames.concat(morph.name);
    72538490        }
     
    72638500
    72648501InputSlotMorph.prototype.objectsMenu = function () {
    7265     var rcvr = this.parentThatIsA(BlockMorph).receiver(),
     8502    var rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
    72668503        stage = rcvr.parentThatIsA(StageMorph),
    72678504        dict = {},
     
    72708507    dict[stage.name] = stage.name;
    72718508    stage.children.forEach(function (morph) {
    7272         if (morph instanceof SpriteMorph) {
     8509        if (morph instanceof SpriteMorph && !morph.isTemporary) {
    72738510            allNames.push(morph.name);
    72748511        }
     
    72938530        dict.sprite = ['sprite'];
    72948531    }
     8532    dict.costume = ['costume'];
     8533    dict.sound = ['sound'];
    72958534    dict.command = ['command'];
    72968535    dict.reporter = ['reporter'];
     
    73158554        dict.children = ['children'];
    73168555        dict.parent = ['parent'];
     8556        if (this.world().isDevMode) {
     8557            dict['temporary?'] = ['temporary?'];
     8558        }
    73178559    }
    73188560    dict.name = ['name'];
     8561    dict.costumes = ['costumes'];
     8562    dict.sounds = ['sounds'];
    73198563    dict['dangling?'] = ['dangling?'];
    73208564    dict['rotation x'] = ['rotation x'];
     
    73288572    var block = this.parentThatIsA(BlockMorph),
    73298573        objName = block.inputs()[1].evaluate(),
    7330         rcvr = block.receiver(),
     8574        rcvr = block.scriptTarget(),
    73318575        stage = rcvr.parentThatIsA(StageMorph),
    73328576        obj,
     
    73698613        });
    73708614    }
    7371     /*
    7372     obj.customBlocks.forEach(function (def, i) {
    7373         dict['§_def' + i] = def
     8615    obj.allBlocks(true).forEach(function (def, i) {
     8616        dict['§_def' + i] = def.blockInstance(true); // include translations
    73748617    });
    7375     */
    73768618    return dict;
    73778619};
    73788620
    73798621InputSlotMorph.prototype.costumesMenu = function () {
    7380     var rcvr = this.parentThatIsA(BlockMorph).receiver(),
     8622    var rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
    73818623        dict,
    73828624        allNames = [];
     
    73998641
    74008642InputSlotMorph.prototype.soundsMenu = function () {
    7401     var rcvr = this.parentThatIsA(BlockMorph).receiver(),
     8643    var rcvr = this.parentThatIsA(BlockMorph).scriptTarget(),
    74028644        allNames = [],
    74038645        dict = {};
     
    74128654    }
    74138655    return dict;
     8656};
     8657
     8658InputSlotMorph.prototype.shadowedVariablesMenu = function () {
     8659    var block = this.parentThatIsA(BlockMorph),
     8660        vars,
     8661        attribs,
     8662        rcvr,
     8663        dict = {};
     8664
     8665    if (!block) {return dict; }
     8666    rcvr = block.scriptTarget();
     8667    if (this.parentThatIsA(RingMorph)) {
     8668        // show own local vars and attributes, because this is likely to be
     8669        // inside TELL, ASK or OF
     8670        vars = rcvr.variables.names();
     8671        vars.forEach(function (name) {
     8672            dict[name] = name;
     8673        });
     8674        attribs = rcvr.attributes;
     8675        /*
     8676        if (vars.length && attribs.length) {
     8677            dict['~'] = null; // add line
     8678        }
     8679        */
     8680        attribs.forEach(function (name) {
     8681            dict[name] = [name];
     8682        });
     8683    } else if (rcvr && rcvr.exemplar) {
     8684        // only show shadowed vars and attributes
     8685        vars = rcvr.inheritedVariableNames(true);
     8686        vars.forEach(function (name) {
     8687            dict[name] = name;
     8688        });
     8689        attribs = rcvr.shadowedAttributes();
     8690        /*
     8691        if (vars.length && attribs.length) {
     8692            dict['~'] = null; // add line
     8693        }
     8694        */
     8695        attribs.forEach(function (name) {
     8696            dict[name] = [name];
     8697        });
     8698    }
     8699    return dict;
     8700};
     8701
     8702InputSlotMorph.prototype.pianoKeyboardMenu = function () {
     8703    var menu, block, instrument;
     8704    block = this.parentThatIsA(BlockMorph);
     8705    if (block) {
     8706        instrument = block.scriptTarget().instrument;
     8707    }
     8708    menu = new PianoMenuMorph(
     8709        this.setContents,
     8710        this,
     8711        this.fontSize,
     8712        instrument
     8713    );
     8714    menu.popup(this.world(), new Point(
     8715        this.right() - (menu.width() / 2),
     8716        this.bottom()
     8717    ));
     8718    menu.selectKey(this.evaluate());
    74148719};
    74158720
     
    74318736};
    74328737
    7433 InputSlotMorph.prototype.shadowedVariablesMenu = function () {
    7434     var block = this.parentThatIsA(BlockMorph),
    7435         rcvr,
    7436         dict = {};
    7437 
    7438     if (!block) {return dict; }
    7439     rcvr = block.receiver();
    7440     if (rcvr) {
    7441         rcvr.inheritedVariableNames(true).forEach(function (name) {
    7442             dict[name] = name;
    7443         });
    7444     }
    7445     return dict;
    7446 };
    7447 
    74488738// InputSlotMorph layout:
    74498739
     
    74718761    arrowWidth = arrow.isVisible ? arrow.width() : 0;
    74728762
    7473     height = contents.height() + this.edge * 2; // + this.typeInPadding * 2
    7474     if (this.isNumeric) {
    7475         width = contents.width()
    7476             + Math.floor(arrowWidth * 0.5)
    7477             + height
     8763        // determine slot dimensions
     8764    if (this.selectedBlock) { // a "wish" in the OF-block's left slot
     8765        height = this.selectedBlock.height() + this.edge * 2;
     8766         width = this.selectedBlock.width()
     8767            + arrowWidth
     8768            + this.edge * 2
    74788769            + this.typeInPadding * 2;
    7479     } else {
    7480         width = Math.max(
    7481             contents.width()
    7482                 + arrowWidth
    7483                 + this.edge * 2
    7484                 + this.typeInPadding * 2,
    7485             contents.rawHeight ? // single vs. multi-line contents
    7486                         contents.rawHeight() + arrowWidth
    7487                                 : fontHeight(contents.fontSize) / 1.3
    7488                                     + arrowWidth,
    7489             this.minWidth // for text-type slots
    7490         );
     8770     } else {
     8771        height = contents.height() + this.edge * 2; // + this.typeInPadding * 2
     8772        if (this.isNumeric) {
     8773            width = contents.width()
     8774                + Math.floor(arrowWidth * 0.5)
     8775                + height
     8776                + this.typeInPadding * 2;
     8777        } else {
     8778            width = Math.max(
     8779                contents.width()
     8780                    + arrowWidth
     8781                    + this.edge * 2
     8782                    + this.typeInPadding * 2,
     8783                contents.rawHeight ? // single vs. multi-line contents
     8784                            contents.rawHeight() + arrowWidth
     8785                                    : fontHeight(contents.fontSize) / 1.3
     8786                                        + arrowWidth,
     8787                this.minWidth // for text-type slots
     8788            );
     8789        }
    74918790    }
    74928791    this.setExtent(new Point(width, height));
     8792
    74938793    if (this.isNumeric) {
    74948794        contents.setPosition(new Point(
     
    75268826
    75278827InputSlotMorph.prototype.mouseDownLeft = function (pos) {
     8828    var world;
    75288829    if (this.isReadOnly || this.arrow().bounds.containsPoint(pos)) {
    75298830        this.escalateEvent('mouseDownLeft', pos);
    75308831    } else {
    7531         this.contents().edit();
    7532         this.contents().selectAll();
     8832        world = this.world();
     8833        if (world) {
     8834            world.stopEditing();
     8835        }
     8836        this.selectForEdit().contents().edit();
    75338837    }
    75348838};
     
    75418845    } else {
    75428846        this.contents().edit();
    7543         this.contents().selectAll();
    75448847    }
    75458848};
     
    75598862};
    75608863
     8864InputSlotMorph.prototype.freshTextEdit = function (aStringOrTextMorph) {
     8865    this.onNextStep = function () {
     8866        aStringOrTextMorph.selectAll();
     8867    };
     8868};
     8869
    75618870// InputSlotMorph menu:
    75628871
    75638872InputSlotMorph.prototype.userMenu = function () {
    75648873    var menu = new MenuMorph(this);
    7565     if (!StageMorph.prototype.enableCodeMapping || this.isNumeric) {
     8874    if (!StageMorph.prototype.enableCodeMapping) {
    75668875        return this.parent.userMenu();
    75678876    }
    7568     menu.addItem(
    7569         'code string mapping...',
    7570         'mapToCode'
    7571     );
     8877    if (this.isNumeric) {
     8878        menu.addItem(
     8879            'code number mapping...',
     8880            'mapNumberToCode'
     8881        );
     8882    } else {
     8883        menu.addItem(
     8884            'code string mapping...',
     8885            'mapStringToCode'
     8886        );
     8887    }
    75728888    return menu;
    75738889};
     
    75818897*/
    75828898
    7583 InputSlotMorph.prototype.mapToCode = function () {
     8899InputSlotMorph.prototype.mapStringToCode = function () {
    75848900    // private - open a dialog box letting the user map code via the GUI
    75858901    new DialogBoxMorph(
     
    75968912};
    75978913
     8914InputSlotMorph.prototype.mapNumberToCode = function () {
     8915    // private - open a dialog box letting the user map code via the GUI
     8916    new DialogBoxMorph(
     8917        this,
     8918        function (code) {
     8919            StageMorph.prototype.codeMappings.number = code;
     8920        },
     8921        this
     8922    ).promptCode(
     8923        'Code mapping - Number <#1>',
     8924        StageMorph.prototype.codeMappings.number || '',
     8925        this.world()
     8926    );
     8927};
     8928
    75988929InputSlotMorph.prototype.mappedCode = function () {
    7599     var code = StageMorph.prototype.codeMappings.string || '<#1>',
    7600         block = this.parentThatIsA(BlockMorph),
    7601         val = this.evaluate();
    7602 
    7603     if (this.isNumeric) {return val; }
     8930    var block = this.parentThatIsA(BlockMorph),
     8931        val = this.evaluate(),
     8932        code;
     8933
     8934    if (this.isNumeric) {
     8935        code = StageMorph.prototype.codeMappings.number || '<#1>';
     8936        return code.replace(/<#1>/g, val);
     8937    }
    76048938    if (!isNaN(parseFloat(val))) {return val; }
    76058939    if (!isString(val)) {return val; }
     
    76108944        return val;
    76118945    }
     8946    code = StageMorph.prototype.codeMappings.string || '<#1>';
    76128947    return code.replace(/<#1>/g, val);
    76138948};
     
    76178952InputSlotMorph.prototype.evaluate = function () {
    76188953/*
    7619     answer my content's text string. If I am numerical convert that
    7620     string to a number. If the conversion fails answer the string
     8954    answer my contents, which can be a "wish", i.e. a block that refers to
     8955    another sprite's local method, or a text string. If I am numerical convert
     8956    that string to a number. If the conversion fails answer the string
    76218957    (e.g. for special choices like 'any', 'all' or 'last') otherwise
    76228958    the numerical value.
    76238959*/
    7624     var num,
    7625         contents = this.contents();
     8960    var num, contents;
     8961
     8962        if (this.selectedBlock) {
     8963                return this.selectedBlock;
     8964        }
    76268965    if (this.constant) {
    76278966        return this.constant;
    76288967    }
     8968    contents = this.contents();
    76298969    if (this.isNumeric) {
    76308970        num = parseFloat(contents.text || '0');
     
    76408980};
    76418981
     8982// InputSlotMorph single-stepping:
     8983
     8984InputSlotMorph.prototype.flash = function () {
     8985    // don't redraw the label b/c zebra coloring
     8986    if (!this.cachedNormalColor) {
     8987        this.cachedNormalColor = this.color;
     8988        this.color = this.activeHighlight;
     8989        this.drawNew();
     8990        this.changed();
     8991    }
     8992};
     8993
     8994InputSlotMorph.prototype.unflash = function () {
     8995    // don't redraw the label b/c zebra coloring
     8996    if (this.cachedNormalColor) {
     8997        var clr = this.cachedNormalColor;
     8998        this.cachedNormalColor = null;
     8999        this.color = clr;
     9000        this.drawNew();
     9001        this.changed();
     9002    }
     9003};
     9004
    76429005// InputSlotMorph drawing:
    76439006
     
    76489011    this.image = newCanvas(this.extent());
    76499012    context = this.image.getContext('2d');
    7650     if (this.parent) {
     9013    if (this.cachedNormalColor) { // if flashing
     9014        borderColor = this.color;
     9015    } else if (this.parent) {
    76519016        borderColor = this.parent.color;
    76529017    } else {
     
    76549019    }
    76559020    context.fillStyle = this.color.toString();
    7656     if (this.isReadOnly) {
     9021    if (this.isReadOnly && !this.cachedNormalColor) { // unless flashing
    76579022        context.fillStyle = borderColor.darker().toString();
    76589023    }
     
    76999064        }
    77009065    }
     9066
     9067        // draw my "wish" block, if any
     9068        if (this.selectedBlock) {
     9069                context.drawImage(
     9070                this.selectedBlock.fullImageClassic(),
     9071            this.edge + this.typeInPadding,
     9072            this.edge
     9073        );
     9074        }
     9075
    77019076};
    77029077
     
    79769351};
    79779352
     9353// TemplateSlotMorph drop behavior:
     9354
     9355TemplateSlotMorph.prototype.wantsDropOf = function (aMorph) {
     9356    return aMorph.selector === 'reportGetVar';
     9357};
     9358
     9359TemplateSlotMorph.prototype.reactToDropOf = function (droppedMorph) {
     9360    if (droppedMorph.selector === 'reportGetVar') {
     9361        droppedMorph.destroy();
     9362    }
     9363};
     9364
    79789365// TemplateSlotMorph drawing:
    79799366
     
    79959382    .prototype.drawRounded;
    79969383
     9384// TemplateSlotMorph single-stepping
     9385
     9386TemplateSlotMorph.prototype.flash = function () {
     9387    this.template().flash();
     9388};
     9389
     9390TemplateSlotMorph.prototype.unflash = function () {
     9391    this.template().unflash();
     9392};
     9393
    79979394// BooleanSlotMorph ////////////////////////////////////////////////////
    79989395
     
    80019398    My block spec is
    80029399
    8003     %b        - Boolean
     9400    %b         - Boolean
    80049401    %boolUE    - Boolean unevaluated
    80059402
     
    80079404    between <true>, <false> and <null> values.
    80089405
    8009     evaluate returns my value.
     9406    evaluate() returns my value.
    80109407
    80119408    my most important public attributes and accessors are:
    80129409
    8013     value - user editable contents (Boolean or null)
     9410    value                      - user editable contents (Boolean or null)
    80149411    setContents(Boolean/null)  - display the argument (Boolean or null)
    8015 
    80169412*/
    80179413
     
    80219417BooleanSlotMorph.prototype.constructor = BooleanSlotMorph;
    80229418BooleanSlotMorph.uber = ArgMorph.prototype;
     9419
     9420// BooleanSlotMorph preferences settings
     9421
     9422BooleanSlotMorph.prototype.isTernary = false;
    80239423
    80249424// BooleanSlotMorph instance creation:
     
    80469446BooleanSlotMorph.prototype.isEmptySlot = function () {
    80479447    return this.value === null;
     9448};
     9449
     9450BooleanSlotMorph.prototype.isBinary = function () {
     9451    return !this.isTernary &&
     9452        isNil(this.parentThatIsA(RingMorph)) &&
     9453        !isNil(this.parentThatIsA(ScriptsMorph));
    80489454};
    80499455
     
    80569462
    80579463BooleanSlotMorph.prototype.toggleValue = function () {
    8058     var ide = this.parentThatIsA(IDE_Morph);
    8059     if (this.isStatic) {
     9464    var target = this.selectForEdit(),
     9465        ide;
     9466    if (target !== this) {
     9467        return this.toggleValue.call(target);
     9468    }
     9469    ide = this.parentThatIsA(IDE_Morph);
     9470    if (this.isStatic || this.isBinary()) {
    80609471        this.setContents(!this.value, true);
    80619472    } else {
     
    81049515BooleanSlotMorph.prototype.mouseEnter = function () {
    81059516    if (this.isStatic) {return; }
    8106     if (this.value === false) {
     9517    if (this.value === false && !this.isBinary()) {
    81079518        var oldValue = this.value;
    81089519        this.value = null;
     
    81209531    this.drawNew();
    81219532    this.changed();
     9533};
     9534
     9535// BooleanSlotMorph menu:
     9536
     9537BooleanSlotMorph.prototype.userMenu = function () {
     9538    var menu = new MenuMorph(this);
     9539    if (!StageMorph.prototype.enableCodeMapping) {
     9540        return this.parent.userMenu();
     9541    }
     9542    if (this.evaluate() === true) {
     9543        menu.addItem(
     9544            'code true mapping...',
     9545            'mapTrueToCode'
     9546        );
     9547    } else {
     9548        menu.addItem(
     9549            'code false mapping...',
     9550            'mapFalseToCode'
     9551        );
     9552    }
     9553    return menu;
     9554};
     9555
     9556// BooleanSlotMorph code mapping
     9557
     9558/*
     9559    code mapping lets you use blocks to generate arbitrary text-based
     9560    source code that can be exported and compiled / embedded elsewhere,
     9561    it's not part of Snap's evaluator and not needed for Snap itself
     9562*/
     9563
     9564BooleanSlotMorph.prototype.mapTrueToCode = function () {
     9565    // private - open a dialog box letting the user map code via the GUI
     9566    new DialogBoxMorph(
     9567        this,
     9568        function (code) {
     9569            StageMorph.prototype.codeMappings['true'] = code;
     9570        },
     9571        this
     9572    ).promptCode(
     9573        'Code mapping - true',
     9574        StageMorph.prototype.codeMappings['true'] || 'true',
     9575        this.world()
     9576    );
     9577};
     9578
     9579BooleanSlotMorph.prototype.mapFalseToCode = function () {
     9580    // private - open a dialog box letting the user map code via the GUI
     9581    new DialogBoxMorph(
     9582        this,
     9583        function (code) {
     9584            StageMorph.prototype.codeMappings['false'] = code;
     9585        },
     9586        this
     9587    ).promptCode(
     9588        'Code mapping - false',
     9589        StageMorph.prototype.codeMappings['false'] || 'false',
     9590        this.world()
     9591    );
     9592};
     9593
     9594BooleanSlotMorph.prototype.mappedCode = function () {
     9595    if (this.evaluate() === true) {
     9596        return StageMorph.prototype.codeMappings.boolTrue || 'true';
     9597    }
     9598    return StageMorph.prototype.codeMappings.boolFalse || 'false';
    81229599};
    81239600
     
    81439620        ));
    81449621    }
    8145     this.color = this.parent ? this.parent.color : new Color(200, 200, 200);
     9622    if (!(this.cachedNormalColor)) { // unless flashing
     9623        this.color = this.parent ?
     9624                this.parent.color : new Color(200, 200, 200);
     9625    }
    81469626    this.cachedClr = this.color.toString();
    81479627    this.cachedClrBright = this.bright();
     
    81639643
    81649644    // draw the 'flat' shape:
    8165     switch (this.value) {
    8166     case true:
    8167         context.fillStyle = 'rgb(0, 200, 0)';
    8168         break;
    8169     case false:
    8170         context.fillStyle = 'rgb(200, 0, 0)';
    8171         break;
    8172     default:
    8173         context.fillStyle = this.color.darker(25).toString();
     9645    if (this.cachedNormalColor) { // if flashing
     9646        context.fillStyle = this.color.toString();
     9647    } else {
     9648        switch (this.value) {
     9649        case true:
     9650            context.fillStyle = 'rgb(0, 200, 0)';
     9651            break;
     9652        case false:
     9653            context.fillStyle = 'rgb(200, 0, 0)';
     9654            break;
     9655        default:
     9656            context.fillStyle = this.color.darker(25).toString();
     9657        }
    81749658    }
    81759659
     
    865810142};
    865910143
    8660 // SymbolMorph //////////////////////////////////////////////////////////
    8661 
    8662 /*
    8663     I display graphical symbols, such as special letters. I have been
    8664     called into existence out of frustration about not being able to
    8665     consistently use Unicode characters to the same ends.
    8666 
    8667     Symbols can also display costumes, if one is specified in lieu
    8668     of a name property, although this feature is currently not being
    8669     used because of asynchronous image loading issues.
    8670  */
    8671 
    8672 // SymbolMorph inherits from Morph:
    8673 
    8674 SymbolMorph.prototype = new Morph();
    8675 SymbolMorph.prototype.constructor = SymbolMorph;
    8676 SymbolMorph.uber = Morph.prototype;
    8677 
    8678 // SymbolMorph available symbols:
    8679 
    8680 SymbolMorph.prototype.names = [
    8681     'square',
    8682     'pointRight',
    8683     'gears',
    8684     'file',
    8685     'fullScreen',
    8686     'normalScreen',
    8687     'smallStage',
    8688     'normalStage',
    8689     'turtle',
    8690     'stage',
    8691     'turtleOutline',
    8692     'pause',
    8693     'flag',
    8694     'octagon',
    8695     'cloud',
    8696     'cloudOutline',
    8697     'cloudGradient',
    8698     'turnRight',
    8699     'turnLeft',
    8700     'storage',
    8701     'poster',
    8702     'flash',
    8703     'brush',
    8704     'rectangle',
    8705     'rectangleSolid',
    8706     'circle',
    8707     'circleSolid',
    8708     'line',
    8709     'crosshairs',
    8710     'paintbucket',
    8711     'eraser',
    8712     'pipette',
    8713     'speechBubble',
    8714     'speechBubbleOutline',
    8715     'arrowUp',
    8716     'arrowUpOutline',
    8717     'arrowLeft',
    8718     'arrowLeftOutline',
    8719     'arrowDown',
    8720     'arrowDownOutline',
    8721     'arrowRight',
    8722     'arrowRightOutline',
    8723     'robot'
    8724 ];
    8725 
    8726 // SymbolMorph instance creation:
    8727 
    8728 function SymbolMorph(name, size, color, shadowOffset, shadowColor) {
    8729     this.init(name, size, color, shadowOffset, shadowColor);
    8730 }
    8731 
    8732 SymbolMorph.prototype.init = function (
    8733     name, // or costume
    8734     size,
    8735     color,
    8736     shadowOffset,
    8737     shadowColor
    8738 ) {
    8739     this.isProtectedLabel = false; // participate in zebraing
    8740     this.isReadOnly = true;
    8741     this.name = name || 'square'; // can also be a costume
    8742     this.size = size || ((size === 0) ? 0 : 50);
    8743     this.shadowOffset = shadowOffset || new Point(0, 0);
    8744     this.shadowColor = shadowColor || null;
    8745 
    8746     SymbolMorph.uber.init.call(this, true); // silently
    8747     this.color = color || new Color(0, 0, 0);
    8748     this.drawNew();
    8749 };
    8750 
    8751 // SymbolMorph zebra coloring:
    8752 
    8753 SymbolMorph.prototype.setLabelColor = function (
    8754     textColor,
    8755     shadowColor,
    8756     shadowOffset
    8757 ) {
    8758     this.shadowOffset = shadowOffset;
    8759     this.shadowColor = shadowColor;
    8760     this.setColor(textColor);
    8761 };
    8762 
    8763 // SymbolMorph displaying:
    8764 
    8765 SymbolMorph.prototype.drawNew = function () {
    8766     var ctx, x, y, sx, sy;
    8767     this.image = newCanvas(new Point(
    8768         this.symbolWidth() + Math.abs(this.shadowOffset.x),
    8769         this.size + Math.abs(this.shadowOffset.y)
    8770     ));
    8771     this.silentSetWidth(this.image.width);
    8772     this.silentSetHeight(this.image.height);
    8773     ctx = this.image.getContext('2d');
    8774     sx = this.shadowOffset.x < 0 ? 0 : this.shadowOffset.x;
    8775     sy = this.shadowOffset.y < 0 ? 0 : this.shadowOffset.y;
    8776     x = this.shadowOffset.x < 0 ? Math.abs(this.shadowOffset.x) : 0;
    8777     y = this.shadowOffset.y < 0 ? Math.abs(this.shadowOffset.y) : 0;
    8778     if (this.shadowColor) {
    8779         ctx.drawImage(
    8780             this.symbolCanvasColored(this.shadowColor),
    8781             sx,
    8782             sy
    8783         );
    8784     }
    8785     ctx.drawImage(
    8786         this.symbolCanvasColored(this.color),
    8787         x,
    8788         y
    8789     );
    8790 };
    8791 
    8792 SymbolMorph.prototype.symbolCanvasColored = function (aColor) {
    8793     // private
    8794     if (this.name instanceof Costume) {
    8795         return this.name.thumbnail(new Point(this.symbolWidth(), this.size));
    8796     }
    8797 
    8798     var canvas = newCanvas(new Point(this.symbolWidth(), this.size));
    8799 
    8800     switch (this.name) {
    8801     case 'square':
    8802         return this.drawSymbolStop(canvas, aColor);
    8803     case 'pointRight':
    8804         return this.drawSymbolPointRight(canvas, aColor);
    8805     case 'gears':
    8806         return this.drawSymbolGears(canvas, aColor);
    8807     case 'file':
    8808         return this.drawSymbolFile(canvas, aColor);
    8809     case 'fullScreen':
    8810         return this.drawSymbolFullScreen(canvas, aColor);
    8811     case 'normalScreen':
    8812         return this.drawSymbolNormalScreen(canvas, aColor);
    8813     case 'smallStage':
    8814         return this.drawSymbolSmallStage(canvas, aColor);
    8815     case 'normalStage':
    8816         return this.drawSymbolNormalStage(canvas, aColor);
    8817     case 'turtle':
    8818         return this.drawSymbolTurtle(canvas, aColor);
    8819     case 'stage':
    8820         return this.drawSymbolStop(canvas, aColor);
    8821     case 'turtleOutline':
    8822         return this.drawSymbolTurtleOutline(canvas, aColor);
    8823     case 'pause':
    8824         return this.drawSymbolPause(canvas, aColor);
    8825     case 'flag':
    8826         return this.drawSymbolFlag(canvas, aColor);
    8827     case 'octagon':
    8828         return this.drawSymbolOctagon(canvas, aColor);
    8829     case 'cloud':
    8830         return this.drawSymbolCloud(canvas, aColor);
    8831     case 'cloudOutline':
    8832         return this.drawSymbolCloudOutline(canvas, aColor);
    8833     case 'cloudGradient':
    8834         return this.drawSymbolCloudGradient(canvas, aColor);
    8835     case 'turnRight':
    8836         return this.drawSymbolTurnRight(canvas, aColor);
    8837     case 'turnLeft':
    8838         return this.drawSymbolTurnLeft(canvas, aColor);
    8839     case 'storage':
    8840         return this.drawSymbolStorage(canvas, aColor);
    8841     case 'poster':
    8842         return this.drawSymbolPoster(canvas, aColor);
    8843     case 'flash':
    8844         return this.drawSymbolFlash(canvas, aColor);
    8845     case 'brush':
    8846         return this.drawSymbolBrush(canvas, aColor);
    8847     case 'rectangle':
    8848         return this.drawSymbolRectangle(canvas, aColor);
    8849     case 'rectangleSolid':
    8850         return this.drawSymbolRectangleSolid(canvas, aColor);
    8851     case 'circle':
    8852         return this.drawSymbolCircle(canvas, aColor);
    8853     case 'circleSolid':
    8854         return this.drawSymbolCircleSolid(canvas, aColor);
    8855     case 'line':
    8856         return this.drawSymbolLine(canvas, aColor);
    8857     case 'crosshairs':
    8858         return this.drawSymbolCrosshairs(canvas, aColor);
    8859     case 'paintbucket':
    8860         return this.drawSymbolPaintbucket(canvas, aColor);
    8861     case 'eraser':
    8862         return this.drawSymbolEraser(canvas, aColor);
    8863     case 'pipette':
    8864         return this.drawSymbolPipette(canvas, aColor);
    8865     case 'speechBubble':
    8866         return this.drawSymbolSpeechBubble(canvas, aColor);
    8867     case 'speechBubbleOutline':
    8868         return this.drawSymbolSpeechBubbleOutline(canvas, aColor);
    8869     case 'arrowUp':
    8870         return this.drawSymbolArrowUp(canvas, aColor);
    8871     case 'arrowUpOutline':
    8872         return this.drawSymbolArrowUpOutline(canvas, aColor);
    8873     case 'arrowLeft':
    8874         return this.drawSymbolArrowLeft(canvas, aColor);
    8875     case 'arrowLeftOutline':
    8876         return this.drawSymbolArrowLeftOutline(canvas, aColor);
    8877     case 'arrowDown':
    8878         return this.drawSymbolArrowDown(canvas, aColor);
    8879     case 'arrowDownOutline':
    8880         return this.drawSymbolArrowDownOutline(canvas, aColor);
    8881     case 'arrowRight':
    8882         return this.drawSymbolArrowRight(canvas, aColor);
    8883     case 'arrowRightOutline':
    8884         return this.drawSymbolArrowRightOutline(canvas, aColor);
    8885     case 'robot':
    8886         return this.drawSymbolRobot(canvas, aColor);
    8887     default:
    8888         return canvas;
    8889     }
    8890 };
    8891 
    8892 SymbolMorph.prototype.symbolWidth = function () {
    8893     // private
    8894     var size = this.size;
    8895 
    8896     if (this.name instanceof Costume) {
    8897         return (size / this.name.height()) * this.name.width();
    8898     }
    8899     switch (this.name) {
    8900     case 'pointRight':
    8901         return Math.sqrt(size * size - Math.pow(size / 2, 2));
    8902     case 'flash':
    8903     case 'file':
    8904         return size * 0.8;
    8905     case 'smallStage':
    8906     case 'normalStage':
    8907         return size * 1.2;
    8908     case 'turtle':
    8909     case 'turtleOutline':
    8910     case 'stage':
    8911         return size * 1.3;
    8912     case 'cloud':
    8913     case 'cloudGradient':
    8914     case 'cloudOutline':
    8915         return size * 1.6;
    8916     case 'turnRight':
    8917     case 'turnLeft':
    8918         return size / 3 * 2;
    8919     default:
    8920         return size;
    8921     }
    8922 };
    8923 
    8924 SymbolMorph.prototype.drawSymbolStop = function (canvas, color) {
    8925     // answer a canvas showing a vertically centered square
    8926     var ctx = canvas.getContext('2d');
    8927 
    8928     ctx.fillStyle = color.toString();
    8929     ctx.fillRect(0, 0, canvas.width, canvas.height);
    8930     return canvas;
    8931 };
    8932 
    8933 SymbolMorph.prototype.drawSymbolPointRight = function (canvas, color) {
    8934     // answer a canvas showing a right-pointing, equilateral triangle
    8935     var ctx = canvas.getContext('2d');
    8936 
    8937     ctx.fillStyle = color.toString();
    8938     ctx.beginPath();
    8939     ctx.moveTo(0, 0);
    8940     ctx.lineTo(canvas.width, Math.round(canvas.height / 2));
    8941     ctx.lineTo(0, canvas.height);
    8942     ctx.lineTo(0, 0);
    8943     ctx.closePath();
    8944     ctx.fill();
    8945     return canvas;
    8946 };
    8947 
    8948 SymbolMorph.prototype.drawSymbolGears = function (canvas, color) {
    8949     // answer a canvas showing gears
    8950     var ctx = canvas.getContext('2d'),
    8951         w = canvas.width,
    8952         r = w / 2,
    8953         e = w / 6;
    8954 
    8955     ctx.strokeStyle = color.toString();
    8956     ctx.lineWidth = canvas.width / 7;
    8957 
    8958     ctx.beginPath();
    8959     ctx.arc(r, r, w, radians(0), radians(360), true);
    8960     ctx.arc(r, r, e * 1.5, radians(0), radians(360), false);
    8961     ctx.closePath();
    8962     ctx.clip();
    8963 
    8964     ctx.moveTo(0, r);
    8965     ctx.lineTo(w, r);
    8966     ctx.stroke();
    8967 
    8968     ctx.moveTo(r, 0);
    8969     ctx.lineTo(r, w);
    8970     ctx.stroke();
    8971 
    8972     ctx.moveTo(e, e);
    8973     ctx.lineTo(w - e, w - e);
    8974     ctx.stroke();
    8975 
    8976     ctx.moveTo(w - e, e);
    8977     ctx.lineTo(e, w - e);
    8978     ctx.stroke();
    8979 
    8980     return canvas;
    8981 };
    8982 
    8983 SymbolMorph.prototype.drawSymbolFile = function (canvas, color) {
    8984     // answer a canvas showing a page symbol
    8985     var ctx = canvas.getContext('2d'),
    8986         w = Math.min(canvas.width, canvas.height) / 2;
    8987 
    8988     ctx.fillStyle = color.toString();
    8989     ctx.beginPath();
    8990     ctx.moveTo(0, 0);
    8991     ctx.lineTo(w, 0);
    8992     ctx.lineTo(w, w);
    8993     ctx.lineTo(canvas.width, w);
    8994     ctx.lineTo(canvas.width, canvas.height);
    8995     ctx.lineTo(0, canvas.height);
    8996     ctx.closePath();
    8997     ctx.fill();
    8998 
    8999     ctx.fillStyle = color.darker(25).toString();
    9000     ctx.beginPath();
    9001     ctx.moveTo(w, 0);
    9002     ctx.lineTo(canvas.width, w);
    9003     ctx.lineTo(w, w);
    9004     ctx.lineTo(w, 0);
    9005     ctx.closePath();
    9006     ctx.fill();
    9007 
    9008     return canvas;
    9009 };
    9010 
    9011 SymbolMorph.prototype.drawSymbolFullScreen = function (canvas, color) {
    9012     // answer a canvas showing two arrows pointing diagonally outwards
    9013     var ctx = canvas.getContext('2d'),
    9014         h = canvas.height,
    9015         c = canvas.width / 2,
    9016         off = canvas.width / 20,
    9017         w = canvas.width / 2;
    9018 
    9019     ctx.strokeStyle = color.toString();
    9020     ctx.lineWidth = canvas.width / 5;
    9021     ctx.moveTo(c - off, c + off);
    9022     ctx.lineTo(0, h);
    9023     ctx.stroke();
    9024 
    9025     ctx.strokeStyle = color.toString();
    9026     ctx.lineWidth = canvas.width / 5;
    9027     ctx.moveTo(c + off, c - off);
    9028     ctx.lineTo(h, 0);
    9029     ctx.stroke();
    9030 
    9031     ctx.fillStyle = color.toString();
    9032     ctx.beginPath();
    9033     ctx.moveTo(0, h);
    9034     ctx.lineTo(0, h - w);
    9035     ctx.lineTo(w, h);
    9036     ctx.closePath();
    9037     ctx.fill();
    9038 
    9039     ctx.fillStyle = color.toString();
    9040     ctx.beginPath();
    9041     ctx.moveTo(h, 0);
    9042     ctx.lineTo(h - w, 0);
    9043     ctx.lineTo(h, w);
    9044     ctx.closePath();
    9045     ctx.fill();
    9046 
    9047     return canvas;
    9048 };
    9049 
    9050 SymbolMorph.prototype.drawSymbolNormalScreen = function (canvas, color) {
    9051     // answer a canvas showing two arrows pointing diagonally inwards
    9052     var ctx = canvas.getContext('2d'),
    9053         h = canvas.height,
    9054         c = canvas.width / 2,
    9055         off = canvas.width / 20,
    9056         w = canvas.width;
    9057 
    9058     ctx.strokeStyle = color.toString();
    9059     ctx.lineWidth = canvas.width / 5;
    9060     ctx.moveTo(c - off * 3, c + off * 3);
    9061     ctx.lineTo(0, h);
    9062     ctx.stroke();
    9063 
    9064     ctx.strokeStyle = color.toString();
    9065     ctx.lineWidth = canvas.width / 5;
    9066     ctx.moveTo(c + off * 3, c - off * 3);
    9067     ctx.lineTo(h, 0);
    9068     ctx.stroke();
    9069 
    9070     ctx.fillStyle = color.toString();
    9071     ctx.beginPath();
    9072     ctx.moveTo(c + off, c - off);
    9073     ctx.lineTo(w, c - off);
    9074     ctx.lineTo(c + off, 0);
    9075     ctx.closePath();
    9076     ctx.fill();
    9077 
    9078     ctx.fillStyle = color.toString();
    9079     ctx.beginPath();
    9080     ctx.moveTo(c - off, c + off);
    9081     ctx.lineTo(0, c + off);
    9082     ctx.lineTo(c - off, w);
    9083     ctx.closePath();
    9084     ctx.fill();
    9085 
    9086     return canvas;
    9087 };
    9088 
    9089 SymbolMorph.prototype.drawSymbolSmallStage = function (canvas, color) {
    9090     // answer a canvas showing a stage toggling symbol
    9091     var ctx = canvas.getContext('2d'),
    9092         w = canvas.width,
    9093         h = canvas.height,
    9094         w2 = w / 2,
    9095         h2 = h / 2;
    9096 
    9097     ctx.fillStyle = color.darker(40).toString();
    9098     ctx.fillRect(0, 0, w, h);
    9099 
    9100     ctx.fillStyle = color.toString();
    9101     ctx.fillRect(w2, 0, w2, h2);
    9102 
    9103     return canvas;
    9104 };
    9105 
    9106 SymbolMorph.prototype.drawSymbolNormalStage = function (canvas, color) {
    9107     // answer a canvas showing a stage toggling symbol
    9108     var ctx = canvas.getContext('2d'),
    9109         w = canvas.width,
    9110         h = canvas.height,
    9111         w2 = w / 2,
    9112         h2 = h / 2;
    9113 
    9114     ctx.fillStyle = color.toString();
    9115     ctx.fillRect(0, 0, w, h);
    9116 
    9117     ctx.fillStyle = color.darker(25).toString();
    9118     ctx.fillRect(w2, 0, w2, h2);
    9119 
    9120     return canvas;
    9121 };
    9122 
    9123 SymbolMorph.prototype.drawSymbolTurtle = function (canvas, color) {
    9124     // answer a canvas showing a turtle
    9125     var ctx = canvas.getContext('2d');
    9126 
    9127     ctx.fillStyle = color.toString();
    9128     ctx.beginPath();
    9129     ctx.moveTo(0, 0);
    9130     ctx.lineTo(canvas.width, canvas.height / 2);
    9131     ctx.lineTo(0, canvas.height);
    9132     ctx.lineTo(canvas.height / 2, canvas.height / 2);
    9133     ctx.closePath();
    9134     ctx.fill();
    9135     return canvas;
    9136 };
    9137 
    9138 SymbolMorph.prototype.drawSymbolTurtleOutline = function (canvas, color) {
    9139     // answer a canvas showing a turtle
    9140     var ctx = canvas.getContext('2d');
    9141 
    9142     ctx.strokeStyle = color.toString();
    9143     ctx.beginPath();
    9144     ctx.moveTo(0, 0);
    9145     ctx.lineTo(canvas.width, canvas.height / 2);
    9146     ctx.lineTo(0, canvas.height);
    9147     ctx.lineTo(canvas.height / 2, canvas.height / 2);
    9148     ctx.closePath();
    9149     ctx.stroke();
    9150 
    9151     return canvas;
    9152 };
    9153 
    9154 SymbolMorph.prototype.drawSymbolPause = function (canvas, color) {
    9155     // answer a canvas showing two parallel rectangles
    9156     var ctx = canvas.getContext('2d'),
    9157         w = canvas.width / 5;
    9158 
    9159     ctx.fillStyle = color.toString();
    9160     ctx.fillRect(0, 0, w * 2, canvas.height);
    9161     ctx.fillRect(w * 3, 0, w * 2, canvas.height);
    9162     return canvas;
    9163 };
    9164 
    9165 SymbolMorph.prototype.drawSymbolFlag = function (canvas, color) {
    9166     // answer a canvas showing a flag
    9167     var ctx = canvas.getContext('2d'),
    9168         w = canvas.width,
    9169         l = Math.max(w / 12, 1),
    9170         h = canvas.height;
    9171 
    9172     ctx.lineWidth = l;
    9173     ctx.strokeStyle = color.toString();
    9174     ctx.beginPath();
    9175     ctx.moveTo(l / 2, 0);
    9176     ctx.lineTo(l / 2, canvas.height);
    9177     ctx.stroke();
    9178 
    9179     ctx.lineWidth = h / 2;
    9180     ctx.beginPath();
    9181     ctx.moveTo(0, h / 4);
    9182     ctx.bezierCurveTo(
    9183         w * 0.8,
    9184         h / 4,
    9185         w * 0.1,
    9186         h * 0.5,
    9187         w,
    9188         h * 0.5
    9189     );
    9190     ctx.stroke();
    9191 
    9192     return canvas;
    9193 };
    9194 
    9195 SymbolMorph.prototype.drawSymbolOctagon = function (canvas, color) {
    9196     // answer a canvas showing an octagon
    9197     var ctx = canvas.getContext('2d'),
    9198         side = canvas.width,
    9199         vert = (side - (side * 0.383)) / 2;
    9200 
    9201     ctx.fillStyle = color.toString();
    9202     ctx.beginPath();
    9203     ctx.moveTo(vert, 0);
    9204     ctx.lineTo(side - vert, 0);
    9205     ctx.lineTo(side, vert);
    9206     ctx.lineTo(side, side - vert);
    9207     ctx.lineTo(side - vert, side);
    9208     ctx.lineTo(vert, side);
    9209     ctx.lineTo(0, side - vert);
    9210     ctx.lineTo(0, vert);
    9211     ctx.closePath();
    9212     ctx.fill();
    9213 
    9214     return canvas;
    9215 };
    9216 
    9217 SymbolMorph.prototype.drawSymbolCloud = function (canvas, color) {
    9218     // answer a canvas showing an cloud
    9219     var ctx = canvas.getContext('2d'),
    9220         w = canvas.width,
    9221         h = canvas.height,
    9222         r1 = h * 2 / 5,
    9223         r2 = h / 4,
    9224         r3 = h * 3 / 10,
    9225         r4 = h / 5;
    9226 
    9227     ctx.fillStyle = color.toString();
    9228     ctx.beginPath();
    9229     ctx.arc(r2, h - r2, r2, radians(90), radians(259), false);
    9230     ctx.arc(w / 20 * 5, h / 9 * 4, r4, radians(165), radians(300), false);
    9231     ctx.arc(w / 20 * 11, r1, r1, radians(200), radians(357), false);
    9232     ctx.arc(w - r3, h - r3, r3, radians(269), radians(90), false);
    9233     ctx.closePath();
    9234     ctx.fill();
    9235 
    9236     return canvas;
    9237 };
    9238 
    9239 SymbolMorph.prototype.drawSymbolCloudGradient = function (canvas, color) {
    9240     // answer a canvas showing an cloud
    9241     var ctx = canvas.getContext('2d'),
    9242         gradient,
    9243         w = canvas.width,
    9244         h = canvas.height,
    9245         r1 = h * 2 / 5,
    9246         r2 = h / 4,
    9247         r3 = h * 3 / 10,
    9248         r4 = h / 5;
    9249 
    9250     gradient = ctx.createRadialGradient(
    9251         0,
    9252         0,
    9253         0,
    9254         0,
    9255         0,
    9256         w
    9257     );
    9258     gradient.addColorStop(0, color.lighter(25).toString());
    9259     gradient.addColorStop(1, color.darker(25).toString());
    9260     ctx.fillStyle = gradient;
    9261     ctx.beginPath();
    9262     ctx.arc(r2, h - r2, r2, radians(90), radians(259), false);
    9263     ctx.arc(w / 20 * 5, h / 9 * 4, r4, radians(165), radians(300), false);
    9264     ctx.arc(w / 20 * 11, r1, r1, radians(200), radians(357), false);
    9265     ctx.arc(w - r3, h - r3, r3, radians(269), radians(90), false);
    9266     ctx.closePath();
    9267     ctx.fill();
    9268 
    9269     return canvas;
    9270 };
    9271 
    9272 SymbolMorph.prototype.drawSymbolCloudOutline = function (canvas, color) {
    9273     // answer a canvas showing an cloud
    9274     var ctx = canvas.getContext('2d'),
    9275         w = canvas.width,
    9276         h = canvas.height,
    9277         r1 = h * 2 / 5,
    9278         r2 = h / 4,
    9279         r3 = h * 3 / 10,
    9280         r4 = h / 5;
    9281 
    9282     ctx.strokeStyle = color.toString();
    9283     ctx.beginPath();
    9284     ctx.arc(r2 + 1, h - r2 - 1, r2, radians(90), radians(259), false);
    9285     ctx.arc(w / 20 * 5, h / 9 * 4, r4, radians(165), radians(300), false);
    9286     ctx.arc(w / 20 * 11, r1 + 1, r1, radians(200), radians(357), false);
    9287     ctx.arc(w - r3 - 1, h - r3 - 1, r3, radians(269), radians(90), false);
    9288     ctx.closePath();
    9289     ctx.stroke();
    9290 
    9291     return canvas;
    9292 };
    9293 
    9294 SymbolMorph.prototype.drawSymbolTurnRight = function (canvas, color) {
    9295     // answer a canvas showing a right-turning arrow
    9296     var ctx = canvas.getContext('2d'),
    9297         w = canvas.width,
    9298         l = Math.max(w / 10, 1),
    9299         r = w / 2;
    9300 
    9301     ctx.lineWidth = l;
    9302     ctx.strokeStyle = color.toString();
    9303     ctx.beginPath();
    9304     ctx.arc(r, r * 2, r - l / 2, radians(0), radians(-90), false);
    9305     ctx.stroke();
    9306 
    9307     ctx.fillStyle = color.toString();
    9308     ctx.beginPath();
    9309     ctx.moveTo(w, r);
    9310     ctx.lineTo(r, 0);
    9311     ctx.lineTo(r, r * 2);
    9312     ctx.closePath();
    9313     ctx.fill();
    9314 
    9315     return canvas;
    9316 };
    9317 
    9318 SymbolMorph.prototype.drawSymbolTurnLeft = function (canvas, color) {
    9319     // answer a canvas showing a left-turning arrow
    9320     var ctx = canvas.getContext('2d'),
    9321         w = canvas.width,
    9322         l = Math.max(w / 10, 1),
    9323         r = w / 2;
    9324 
    9325     ctx.lineWidth = l;
    9326     ctx.strokeStyle = color.toString();
    9327     ctx.beginPath();
    9328     ctx.arc(r, r * 2, r - l / 2, radians(180), radians(-90), true);
    9329     ctx.stroke();
    9330 
    9331     ctx.fillStyle = color.toString();
    9332     ctx.beginPath();
    9333     ctx.moveTo(0, r);
    9334     ctx.lineTo(r, 0);
    9335     ctx.lineTo(r, r * 2);
    9336     ctx.closePath();
    9337     ctx.fill();
    9338 
    9339     return canvas;
    9340 };
    9341 
    9342 SymbolMorph.prototype.drawSymbolStorage = function (canvas, color) {
    9343     // answer a canvas showing a stack of three disks
    9344     var ctx = canvas.getContext('2d'),
    9345         w = canvas.width,
    9346         h = canvas.height,
    9347         r = canvas.height,
    9348         unit = canvas.height / 11;
    9349 
    9350     function drawDisk(bottom, fillTop) {
    9351         ctx.fillStyle = color.toString();
    9352         ctx.beginPath();
    9353         ctx.arc(w / 2, bottom - h, r, radians(60), radians(120), false);
    9354         ctx.lineTo(0, bottom - unit * 2);
    9355         ctx.arc(
    9356             w / 2,
    9357             bottom - h - unit * 2,
    9358             r,
    9359             radians(120),
    9360             radians(60),
    9361             true
    9362         );
    9363         ctx.closePath();
    9364         ctx.fill();
    9365 
    9366         ctx.fillStyle = color.darker(25).toString();
    9367         ctx.beginPath();
    9368 
    9369         if (fillTop) {
    9370             ctx.arc(
    9371                 w / 2,
    9372                 bottom - h - unit * 2,
    9373                 r,
    9374                 radians(120),
    9375                 radians(60),
    9376                 true
    9377             );
    9378         }
    9379 
    9380         ctx.arc(
    9381             w / 2,
    9382             bottom + unit * 6 + 1,
    9383             r,
    9384             radians(60),
    9385             radians(120),
    9386             true
    9387         );
    9388         ctx.closePath();
    9389 
    9390         if (fillTop) {
    9391             ctx.fill();
    9392         } else {
    9393             ctx.stroke();
    9394         }
    9395     }
    9396 
    9397     ctx.strokeStyle = color.toString();
    9398     drawDisk(h);
    9399     drawDisk(h - unit * 3);
    9400     drawDisk(h - unit * 6, false);
    9401     return canvas;
    9402 };
    9403 
    9404 SymbolMorph.prototype.drawSymbolPoster = function (canvas, color) {
    9405     // answer a canvas showing a poster stand
    9406     var ctx = canvas.getContext('2d'),
    9407         w = canvas.width,
    9408         h = canvas.height,
    9409         bottom = h * 0.75,
    9410         edge = canvas.height / 5;
    9411 
    9412     ctx.fillStyle = color.toString();
    9413     ctx.strokeStyle = color.toString();
    9414 
    9415     ctx.lineWidth = w / 15;
    9416     ctx.moveTo(w / 2, h / 3);
    9417     ctx.lineTo(w / 6, h);
    9418     ctx.stroke();
    9419 
    9420     ctx.moveTo(w / 2, h / 3);
    9421     ctx.lineTo(w / 2, h);
    9422     ctx.stroke();
    9423 
    9424     ctx.moveTo(w / 2, h / 3);
    9425     ctx.lineTo(w * 5 / 6, h);
    9426     ctx.stroke();
    9427 
    9428     ctx.fillRect(0, 0, w, bottom);
    9429     ctx.clearRect(0, bottom, w, w / 20);
    9430 
    9431     ctx.clearRect(w - edge, bottom - edge, edge + 1, edge + 1);
    9432 
    9433     ctx.fillStyle = color.darker(25).toString();
    9434     ctx.beginPath();
    9435     ctx.moveTo(w, bottom - edge);
    9436     ctx.lineTo(w - edge, bottom - edge);
    9437     ctx.lineTo(w - edge, bottom);
    9438     ctx.closePath();
    9439     ctx.fill();
    9440 
    9441     return canvas;
    9442 };
    9443 
    9444 SymbolMorph.prototype.drawSymbolFlash = function (canvas, color) {
    9445     // answer a canvas showing a flash
    9446     var ctx = canvas.getContext('2d'),
    9447         w = canvas.width,
    9448         w3 = w / 3,
    9449         h = canvas.height,
    9450         h3 = h / 3,
    9451         off = h3 / 3;
    9452 
    9453     ctx.fillStyle = color.toString();
    9454     ctx.beginPath();
    9455     ctx.moveTo(w3, 0);
    9456     ctx.lineTo(0, h3);
    9457     ctx.lineTo(w3, h3);
    9458     ctx.lineTo(0, h3 * 2);
    9459     ctx.lineTo(w3, h3 * 2);
    9460     ctx.lineTo(0, h);
    9461     ctx.lineTo(w, h3 * 2 - off);
    9462     ctx.lineTo(w3 * 2, h3 * 2 - off);
    9463     ctx.lineTo(w, h3 - off);
    9464     ctx.lineTo(w3 * 2, h3 - off);
    9465     ctx.lineTo(w, 0);
    9466     ctx.closePath();
    9467     ctx.fill();
    9468     return canvas;
    9469 };
    9470 
    9471 SymbolMorph.prototype.drawSymbolBrush = function (canvas, color) {
    9472     // answer a canvas showing a paintbrush
    9473     var ctx = canvas.getContext('2d'),
    9474         w = canvas.width,
    9475         h = canvas.height,
    9476         l = Math.max(w / 30, 0.5);
    9477 
    9478     ctx.fillStyle = color.toString();
    9479     ctx.lineWidth = l * 2;
    9480     ctx.beginPath();
    9481     ctx.moveTo(w / 8 * 3, h / 2);
    9482     ctx.quadraticCurveTo(0, h / 2, l, h - l);
    9483     ctx.quadraticCurveTo(w / 2, h, w / 2, h / 8 * 5);
    9484     ctx.closePath();
    9485     ctx.fill();
    9486 
    9487     ctx.lineJoin = 'round';
    9488     ctx.lineCap = 'round';
    9489     ctx.strokeStyle = color.toString();
    9490 
    9491     ctx.moveTo(w / 8 * 3, h / 2);
    9492     ctx.lineTo(w * 0.75, l);
    9493     ctx.quadraticCurveTo(w, 0, w - l, h * 0.25);
    9494     ctx.stroke();
    9495 
    9496     ctx.moveTo(w / 2, h / 8 * 5);
    9497     ctx.lineTo(w - l, h * 0.25);
    9498     ctx.stroke();
    9499 
    9500     return canvas;
    9501 };
    9502 
    9503 SymbolMorph.prototype.drawSymbolRectangle = function (canvas, color) {
    9504     // answer a canvas showing a rectangle
    9505     var ctx = canvas.getContext('2d'),
    9506         w = canvas.width,
    9507         h = canvas.width,
    9508         l = Math.max(w / 20, 0.5);
    9509 
    9510     ctx.strokeStyle = color.toString();
    9511     ctx.lineWidth = l * 2;
    9512     ctx.beginPath();
    9513     ctx.moveTo(l, l);
    9514     ctx.lineTo(w - l, l);
    9515     ctx.lineTo(w - l, h - l);
    9516     ctx.lineTo(l, h - l);
    9517     ctx.closePath();
    9518     ctx.stroke();
    9519     return canvas;
    9520 };
    9521 
    9522 SymbolMorph.prototype.drawSymbolRectangleSolid = function (canvas, color) {
    9523     // answer a canvas showing a solid rectangle
    9524     var ctx = canvas.getContext('2d'),
    9525         w = canvas.width,
    9526         h = canvas.width;
    9527 
    9528     ctx.fillStyle = color.toString();
    9529     ctx.beginPath();
    9530     ctx.moveTo(0, 0);
    9531     ctx.lineTo(w, 0);
    9532     ctx.lineTo(w, h);
    9533     ctx.lineTo(0, h);
    9534     ctx.closePath();
    9535     ctx.fill();
    9536     return canvas;
    9537 };
    9538 
    9539 SymbolMorph.prototype.drawSymbolCircle = function (canvas, color) {
    9540     // answer a canvas showing a circle
    9541     var ctx = canvas.getContext('2d'),
    9542         w = canvas.width,
    9543         l = Math.max(w / 20, 0.5);
    9544 
    9545     ctx.strokeStyle = color.toString();
    9546     ctx.lineWidth = l * 2;
    9547     ctx.arc(w / 2, w / 2, w / 2 - l, radians(0), radians(360), false);
    9548     ctx.stroke();
    9549     return canvas;
    9550 };
    9551 
    9552 SymbolMorph.prototype.drawSymbolCircleSolid = function (canvas, color) {
    9553     // answer a canvas showing a solid circle
    9554     var ctx = canvas.getContext('2d'),
    9555         w = canvas.width;
    9556 
    9557     ctx.fillStyle = color.toString();
    9558     ctx.arc(w / 2, w / 2, w / 2, radians(0), radians(360), false);
    9559     ctx.fill();
    9560     return canvas;
    9561 };
    9562 
    9563 SymbolMorph.prototype.drawSymbolLine = function (canvas, color) {
    9564     // answer a canvas showing a diagonal line
    9565     var ctx = canvas.getContext('2d'),
    9566         w = canvas.width,
    9567         h = canvas.height,
    9568         l = Math.max(w / 20, 0.5);
    9569 
    9570     ctx.strokeStyle = color.toString();
    9571     ctx.lineWidth = l * 2;
    9572     ctx.lineCap = 'round';
    9573     ctx.moveTo(l, l);
    9574     ctx.lineTo(w - l, h - l);
    9575     ctx.stroke();
    9576     return canvas;
    9577 };
    9578 
    9579 SymbolMorph.prototype.drawSymbolCrosshairs = function (canvas, color) {
    9580     // answer a canvas showing a crosshairs
    9581     var ctx = canvas.getContext('2d'),
    9582         w = canvas.width,
    9583         h = canvas.height,
    9584         l = 0.5;
    9585 
    9586     ctx.strokeStyle = color.toString();
    9587     ctx.lineWidth = l * 2;
    9588     ctx.moveTo(l, h / 2);
    9589     ctx.lineTo(w - l, h / 2);
    9590     ctx.stroke();
    9591     ctx.moveTo(w / 2, l);
    9592     ctx.lineTo(w / 2, h - l);
    9593     ctx.stroke();
    9594     ctx.moveTo(w / 2, h / 2);
    9595     ctx.arc(w / 2, w / 2, w / 3 - l, radians(0), radians(360), false);
    9596     ctx.stroke();
    9597     return canvas;
    9598 };
    9599 
    9600 SymbolMorph.prototype.drawSymbolPaintbucket = function (canvas, color) {
    9601     // answer a canvas showing a paint bucket
    9602     var ctx = canvas.getContext('2d'),
    9603         w = canvas.width,
    9604         h = canvas.height,
    9605         n = canvas.width / 5,
    9606         l = Math.max(w / 30, 0.5);
    9607 
    9608     ctx.strokeStyle = color.toString();
    9609     ctx.lineWidth = l * 2;
    9610     ctx.beginPath();
    9611     ctx.moveTo(n * 2, n);
    9612     ctx.lineTo(n * 4, n * 3);
    9613     ctx.lineTo(n * 3, n * 4);
    9614     ctx.quadraticCurveTo(n * 2, h, n, n * 4);
    9615     ctx.quadraticCurveTo(0, n * 3, n, n * 2);
    9616     ctx.closePath();
    9617     ctx.stroke();
    9618 
    9619     ctx.lineWidth = l;
    9620     ctx.moveTo(n * 2, n * 2.5);
    9621     ctx.arc(n * 2, n * 2.5, l, radians(0), radians(360), false);
    9622     ctx.stroke();
    9623 
    9624     ctx.moveTo(n * 2, n * 2.5);
    9625     ctx.lineTo(n * 2, n / 2 + l);
    9626     ctx.stroke();
    9627 
    9628     ctx.arc(n * 1.5, n / 2 + l, n / 2, radians(0), radians(180), true);
    9629     ctx.stroke();
    9630 
    9631     ctx.moveTo(n, n / 2 + l);
    9632     ctx.lineTo(n, n * 2);
    9633     ctx.stroke();
    9634 
    9635     ctx.fillStyle = color.toString();
    9636     ctx.beginPath();
    9637     ctx.moveTo(n * 3.5, n * 3.5);
    9638     ctx.quadraticCurveTo(w, n * 3.5, w - l, h);
    9639     ctx.lineTo(w, h);
    9640     ctx.quadraticCurveTo(w, n * 2, n * 2.5, n * 1.5);
    9641     ctx.lineTo(n * 4, n * 3);
    9642     ctx.closePath();
    9643     ctx.fill();
    9644 
    9645     return canvas;
    9646 };
    9647 
    9648 SymbolMorph.prototype.drawSymbolEraser = function (canvas, color) {
    9649     // answer a canvas showing an eraser
    9650     var ctx = canvas.getContext('2d'),
    9651         w = canvas.width,
    9652         h = canvas.height,
    9653         n = canvas.width / 4,
    9654         l = Math.max(w / 20, 0.5);
    9655 
    9656     ctx.strokeStyle = color.toString();
    9657     ctx.lineWidth = l * 2;
    9658     ctx.beginPath();
    9659     ctx.moveTo(n * 3, l);
    9660     ctx.lineTo(l, n * 3);
    9661     ctx.quadraticCurveTo(n, h, n * 2, n * 3);
    9662     ctx.lineTo(w - l, n);
    9663     ctx.closePath();
    9664     ctx.stroke();
    9665 
    9666     ctx.fillStyle = color.toString();
    9667     ctx.beginPath();
    9668     ctx.moveTo(n * 3, 0);
    9669     ctx.lineTo(n * 1.5, n * 1.5);
    9670     ctx.lineTo(n * 2.5, n * 2.5);
    9671     ctx.lineTo(w, n);
    9672     ctx.closePath();
    9673     ctx.fill();
    9674 
    9675     return canvas;
    9676 };
    9677 
    9678 SymbolMorph.prototype.drawSymbolPipette = function (canvas, color) {
    9679     // answer a canvas showing an eyedropper
    9680     var ctx = canvas.getContext('2d'),
    9681         w = canvas.width,
    9682         h = canvas.height,
    9683         n = canvas.width / 4,
    9684         n2 = n / 2,
    9685         l = Math.max(w / 20, 0.5);
    9686 
    9687     ctx.strokeStyle = color.toString();
    9688     ctx.lineWidth = l * 2;
    9689     ctx.beginPath();
    9690     ctx.moveTo(l, h - l);
    9691     ctx.quadraticCurveTo(n2, h - n2, n2, h - n);
    9692     ctx.lineTo(n * 2, n * 1.5);
    9693     ctx.stroke();
    9694 
    9695     ctx.beginPath();
    9696     ctx.moveTo(l, h - l);
    9697     ctx.quadraticCurveTo(n2, h - n2, n, h - n2);
    9698     ctx.lineTo(n * 2.5, n * 2);
    9699     ctx.stroke();
    9700 
    9701     ctx.fillStyle = color.toString();
    9702     ctx.arc(n * 3, n, n - l, radians(0), radians(360), false);
    9703     ctx.fill();
    9704 
    9705     ctx.beginPath();
    9706     ctx.moveTo(n * 2, n);
    9707     ctx.lineTo(n * 3, n * 2);
    9708     ctx.stroke();
    9709 
    9710     return canvas;
    9711 };
    9712 
    9713 SymbolMorph.prototype.drawSymbolSpeechBubble = function (canvas, color) {
    9714     // answer a canvas showing a speech bubble
    9715     var ctx = canvas.getContext('2d'),
    9716         w = canvas.width,
    9717         h = canvas.height,
    9718         n = canvas.width / 3,
    9719         l = Math.max(w / 20, 0.5);
    9720 
    9721     ctx.fillStyle = color.toString();
    9722     ctx.lineWidth = l * 2;
    9723     ctx.beginPath();
    9724     ctx.moveTo(n, n * 2);
    9725     ctx.quadraticCurveTo(l, n * 2, l, n);
    9726     ctx.quadraticCurveTo(l, l, n, l);
    9727     ctx.lineTo(n * 2, l);
    9728     ctx.quadraticCurveTo(w - l, l, w - l, n);
    9729     ctx.quadraticCurveTo(w - l, n * 2, n * 2, n * 2);
    9730     ctx.lineTo(n / 2, h - l);
    9731     ctx.closePath();
    9732     ctx.fill();
    9733     return canvas;
    9734 };
    9735 
    9736 SymbolMorph.prototype.drawSymbolSpeechBubbleOutline = function (
    9737     canvas,
    9738     color
    9739 ) {
    9740     // answer a canvas showing a speech bubble
    9741     var ctx = canvas.getContext('2d'),
    9742         w = canvas.width,
    9743         h = canvas.height,
    9744         n = canvas.width / 3,
    9745         l = Math.max(w / 20, 0.5);
    9746 
    9747     ctx.strokeStyle = color.toString();
    9748     ctx.lineWidth = l * 2;
    9749     ctx.beginPath();
    9750     ctx.moveTo(n, n * 2);
    9751     ctx.quadraticCurveTo(l, n * 2, l, n);
    9752     ctx.quadraticCurveTo(l, l, n, l);
    9753     ctx.lineTo(n * 2, l);
    9754     ctx.quadraticCurveTo(w - l, l, w - l, n);
    9755     ctx.quadraticCurveTo(w - l, n * 2, n * 2, n * 2);
    9756     ctx.lineTo(n / 2, h - l);
    9757     ctx.closePath();
    9758     ctx.stroke();
    9759     return canvas;
    9760 };
    9761 
    9762 SymbolMorph.prototype.drawSymbolArrowUp = function (canvas, color) {
    9763     // answer a canvas showing an up arrow
    9764     var ctx = canvas.getContext('2d'),
    9765         w = canvas.width,
    9766         h = canvas.height,
    9767         n = canvas.width / 2,
    9768         l = Math.max(w / 20, 0.5);
    9769 
    9770     ctx.fillStyle = color.toString();
    9771     ctx.lineWidth = l * 2;
    9772     ctx.beginPath();
    9773     ctx.moveTo(l, n);
    9774     ctx.lineTo(n, l);
    9775     ctx.lineTo(w - l, n);
    9776     ctx.lineTo(w * 0.65, n);
    9777     ctx.lineTo(w * 0.65, h - l);
    9778     ctx.lineTo(w * 0.35, h - l);
    9779     ctx.lineTo(w * 0.35, n);
    9780     ctx.closePath();
    9781     ctx.fill();
    9782     return canvas;
    9783 };
    9784 
    9785 SymbolMorph.prototype.drawSymbolArrowUpOutline = function (canvas, color) {
    9786     // answer a canvas showing an up arrow
    9787     var ctx = canvas.getContext('2d'),
    9788         w = canvas.width,
    9789         h = canvas.height,
    9790         n = canvas.width / 2,
    9791         l = Math.max(w / 20, 0.5);
    9792 
    9793     ctx.strokeStyle = color.toString();
    9794     ctx.lineWidth = l * 2;
    9795     ctx.beginPath();
    9796     ctx.moveTo(l, n);
    9797     ctx.lineTo(n, l);
    9798     ctx.lineTo(w - l, n);
    9799     ctx.lineTo(w * 0.65, n);
    9800     ctx.lineTo(w * 0.65, h - l);
    9801     ctx.lineTo(w * 0.35, h - l);
    9802     ctx.lineTo(w * 0.35, n);
    9803     ctx.closePath();
    9804     ctx.stroke();
    9805     return canvas;
    9806 };
    9807 
    9808 SymbolMorph.prototype.drawSymbolArrowDown = function (canvas, color) {
    9809     // answer a canvas showing a down arrow
    9810     var ctx = canvas.getContext('2d'),
    9811         w = canvas.width;
    9812     ctx.save();
    9813     ctx.translate(w, w);
    9814     ctx.rotate(radians(180));
    9815     this.drawSymbolArrowUp(canvas, color);
    9816     ctx.restore();
    9817     return canvas;
    9818 };
    9819 
    9820 SymbolMorph.prototype.drawSymbolArrowDownOutline = function (canvas, color) {
    9821     // answer a canvas showing a down arrow
    9822     var ctx = canvas.getContext('2d'),
    9823         w = canvas.width;
    9824     ctx.save();
    9825     ctx.translate(w, w);
    9826     ctx.rotate(radians(180));
    9827     this.drawSymbolArrowUpOutline(canvas, color);
    9828     ctx.restore();
    9829     return canvas;
    9830 };
    9831 
    9832 SymbolMorph.prototype.drawSymbolArrowLeft = function (canvas, color) {
    9833     // answer a canvas showing a left arrow
    9834     var ctx = canvas.getContext('2d'),
    9835         w = canvas.width;
    9836     ctx.save();
    9837     ctx.translate(0, w);
    9838     ctx.rotate(radians(-90));
    9839     this.drawSymbolArrowUp(canvas, color);
    9840     ctx.restore();
    9841     return canvas;
    9842 };
    9843 
    9844 SymbolMorph.prototype.drawSymbolArrowLeftOutline = function (canvas, color) {
    9845     // answer a canvas showing a left arrow
    9846     var ctx = canvas.getContext('2d'),
    9847         w = canvas.width;
    9848     ctx.save();
    9849     ctx.translate(0, w);
    9850     ctx.rotate(radians(-90));
    9851     this.drawSymbolArrowUpOutline(canvas, color);
    9852     ctx.restore();
    9853     return canvas;
    9854 };
    9855 
    9856 SymbolMorph.prototype.drawSymbolArrowRight = function (canvas, color) {
    9857     // answer a canvas showing a right arrow
    9858     var ctx = canvas.getContext('2d'),
    9859         w = canvas.width;
    9860     ctx.save();
    9861     ctx.translate(w, 0);
    9862     ctx.rotate(radians(90));
    9863     this.drawSymbolArrowUp(canvas, color);
    9864     ctx.restore();
    9865     return canvas;
    9866 };
    9867 
    9868 SymbolMorph.prototype.drawSymbolArrowRightOutline = function (canvas, color) {
    9869     // answer a canvas showing a right arrow
    9870     var ctx = canvas.getContext('2d'),
    9871         w = canvas.width;
    9872     ctx.save();
    9873     ctx.translate(w, 0);
    9874     ctx.rotate(radians(90));
    9875     this.drawSymbolArrowUpOutline(canvas, color);
    9876     ctx.restore();
    9877     return canvas;
    9878 };
    9879 
    9880 SymbolMorph.prototype.drawSymbolRobot = function (canvas, color) {
    9881     // answer a canvas showing a humanoid robot
    9882     var ctx = canvas.getContext('2d'),
    9883         w = canvas.width,
    9884         h = canvas.height,
    9885         n = canvas.width / 6,
    9886         n2 = n / 2,
    9887         l = Math.max(w / 20, 0.5);
    9888 
    9889     ctx.fillStyle = color.toString();
    9890     //ctx.lineWidth = l * 2;
    9891 
    9892     ctx.beginPath();
    9893     ctx.moveTo(n + l, n);
    9894     ctx.lineTo(n * 2, n);
    9895     ctx.lineTo(n * 2.5, n * 1.5);
    9896     ctx.lineTo(n * 3.5, n * 1.5);
    9897     ctx.lineTo(n * 4, n);
    9898     ctx.lineTo(n * 5 - l, n);
    9899     ctx.lineTo(n * 4, n * 3);
    9900     ctx.lineTo(n * 4, n * 4 - l);
    9901     ctx.lineTo(n * 2, n * 4 - l);
    9902     ctx.lineTo(n * 2, n * 3);
    9903     ctx.closePath();
    9904     ctx.fill();
    9905 
    9906     ctx.beginPath();
    9907     ctx.moveTo(n * 2.75, n + l);
    9908     ctx.lineTo(n * 2.4, n);
    9909     ctx.lineTo(n * 2.2, 0);
    9910     ctx.lineTo(n * 3.8, 0);
    9911     ctx.lineTo(n * 3.6, n);
    9912     ctx.lineTo(n * 3.25, n + l);
    9913     ctx.closePath();
    9914     ctx.fill();
    9915 
    9916     ctx.beginPath();
    9917     ctx.moveTo(n * 2.5, n * 4);
    9918     ctx.lineTo(n, n * 4);
    9919     ctx.lineTo(n2 + l, h);
    9920     ctx.lineTo(n * 2, h);
    9921     ctx.closePath();
    9922     ctx.fill();
    9923 
    9924     ctx.beginPath();
    9925     ctx.moveTo(n * 3.5, n * 4);
    9926     ctx.lineTo(n * 5, n * 4);
    9927     ctx.lineTo(w - (n2 + l), h);
    9928     ctx.lineTo(n * 4, h);
    9929     ctx.closePath();
    9930     ctx.fill();
    9931 
    9932     ctx.beginPath();
    9933     ctx.moveTo(n, n);
    9934     ctx.lineTo(l, n * 1.5);
    9935     ctx.lineTo(l, n * 3.25);
    9936     ctx.lineTo(n * 1.5, n * 3.5);
    9937     ctx.closePath();
    9938     ctx.fill();
    9939 
    9940     ctx.beginPath();
    9941     ctx.moveTo(n * 5, n);
    9942     ctx.lineTo(w - l, n * 1.5);
    9943     ctx.lineTo(w - l, n * 3.25);
    9944     ctx.lineTo(n * 4.5, n * 3.5);
    9945     ctx.closePath();
    9946     ctx.fill();
    9947 
    9948     return canvas;
    9949 };
    9950 
    995110144// ColorSlotMorph //////////////////////////////////////////////////////
    995210145
     
    1000210195
    1000310196    hand.processMouseMove = function (event) {
     10197        var clr = world.getGlobalPixelColor(hand.position());
    1000410198        hand.setPosition(new Point(
    1000510199            event.pageX - posInDocument.x,
    1000610200            event.pageY - posInDocument.y
    1000710201        ));
    10008         myself.setColor(world.getGlobalPixelColor(hand.position()));
     10202        if (!clr.a) {
     10203            // ignore transparent,
     10204            // needed for retina-display support
     10205            return;
     10206        }
     10207        myself.setColor(clr);
    1000910208    };
    1001010209
     
    1002210221
    1002310222ColorSlotMorph.prototype.mouseClickLeft = function () {
    10024     this.getUserColor();
     10223    this.selectForEdit().getUserColor();
    1002510224};
    1002610225
     
    1007110270// BlockHighlightMorph /////////////////////////////////////////////////
    1007210271
     10272/*
     10273    I am a glowing halo around a block or stack of blocks indicating that
     10274    a script is currently active or has encountered an error.
     10275    I halso have an optional readout that can display a thread count
     10276    if more than one process shares the same script
     10277*/
     10278
    1007310279// BlockHighlightMorph inherits from Morph:
    1007410280
     
    1008010286
    1008110287function BlockHighlightMorph() {
     10288    this.threadCount = 0;
    1008210289    this.init();
    1008310290}
    1008410291
     10292// BlockHighlightMorph thread count readout
     10293
     10294BlockHighlightMorph.prototype.readout = function () {
     10295    return this.children.length ? this.children[0] : null;
     10296};
     10297
     10298BlockHighlightMorph.prototype.updateReadout = function () {
     10299    var readout = this.readout(),
     10300        inset = useBlurredShadows && !MorphicPreferences.isFlat ?
     10301            SyntaxElementMorph.prototype.activeBlur * 0.4
     10302                : SyntaxElementMorph.prototype.activeBorder * -2;
     10303    if (this.threadCount < 2) {
     10304        if (readout) {
     10305            readout.destroy();
     10306        }
     10307        return;
     10308    }
     10309    if (readout) {
     10310        readout.contents = this.threadCount.toString();
     10311        readout.fullChanged();
     10312        readout.drawNew();
     10313        readout.fullChanged();
     10314    } else {
     10315        readout = new SpeechBubbleMorph(
     10316            this.threadCount.toString(),
     10317            this.color, // color,
     10318            null, // edge,
     10319            null, // border,
     10320            this.color.darker(), // borderColor,
     10321            null, // padding,
     10322            1 // isThought - don't draw a hook
     10323        );
     10324        this.add(readout);
     10325    }
     10326    readout.setPosition(this.position().add(inset));
     10327};
     10328
    1008510329// MultiArgMorph ///////////////////////////////////////////////////////
    1008610330
     
    1009610340*/
    1009710341
    10098 // MultiArgMorph  inherits from ArgMorph:
     10342// MultiArgMorph inherits from ArgMorph:
    1009910343
    1010010344MultiArgMorph.prototype = new ArgMorph();
     
    1035310597        oldPart = this.children[this.children.length - 2];
    1035410598        this.removeChild(oldPart);
    10355         if (oldPart instanceof BlockMorph) {
     10599        if (oldPart instanceof BlockMorph &&
     10600                !(oldPart instanceof RingMorph && !oldPart.contents())) {
    1035610601            scripts = this.parentThatIsA(ScriptsMorph);
    1035710602            if (scripts) {
     
    1037210617        return;
    1037310618    }
    10374     // if the <shift> key is pressed, repeat action 5 times
    10375     var arrows = this.arrows(),
     10619    // if the <shift> key is pressed, repeat action 3 times
     10620    var target = this.selectForEdit(),
     10621        arrows = target.arrows(),
    1037610622        leftArrow = arrows.children[0],
    1037710623        rightArrow = arrows.children[1],
    10378         repetition = this.world().currentKey === 16 ? 3 : 1,
     10624        repetition = target.world().currentKey === 16 ? 3 : 1,
    1037910625        i;
    1038010626
    10381     this.startLayout();
     10627    target.startLayout();
    1038210628    if (rightArrow.bounds.containsPoint(pos)) {
    1038310629        for (i = 0; i < repetition; i += 1) {
    1038410630            if (rightArrow.isVisible) {
    10385                 this.addInput();
     10631                target.addInput();
    1038610632            }
    1038710633        }
     
    1038910635        for (i = 0; i < repetition; i += 1) {
    1039010636            if (leftArrow.isVisible) {
    10391                 this.removeInput();
     10637                target.removeInput();
    1039210638            }
    1039310639        }
    1039410640    } else {
    10395         this.escalateEvent('mouseClickLeft', pos);
    10396     }
    10397     this.endLayout();
     10641        target.escalateEvent('mouseClickLeft', pos);
     10642    }
     10643    target.endLayout();
    1039810644};
    1039910645
     
    1050410750
    1050510751MultiArgMorph.prototype.evaluate = function () {
    10506 /*
    10507     this is usually overridden by the interpreter. This method is only
    10508     called (and needed) for the variables menu.
    10509 */
     10752    // this is usually overridden by the interpreter. This method is only
     10753    // called (and needed) for the variables menu.
     10754
    1051010755    var result = [];
    1051110756    this.inputs().forEach(function (slot) {
     
    1053310778*/
    1053410779
    10535 // ArgLabelMorph  inherits from ArgMorph:
     10780// ArgLabelMorph inherits from ArgMorph:
    1053610781
    1053710782ArgLabelMorph.prototype = new ArgMorph();
     
    1064310888
    1064410889ArgLabelMorph.prototype.evaluate = function () {
    10645 /*
    10646     this is usually overridden by the interpreter. This method is only
    10647     called (and needed) for the variables menu.
    10648 */
     10890    // this is usually overridden by the interpreter. This method is only
     10891    // called (and needed) for the variables menu.
     10892
    1064910893    return this.argMorph().evaluate();
    1065010894};
     
    1065810902/*
    1065910903    I am an unevaluated, non-editable, rf-colored, rounded or diamond
    10660     input slot.    My current (only) use is in the THE BLOCK block.
     10904    input slot. My current (only) use is in the THE BLOCK block.
    1066110905
    1066210906    My command spec is %f
     
    1160011844    cpy.isCollapsed = this.isCollapsed;
    1160111845    cpy.setTextWidth(this.textWidth());
     11846    if (this.selectionID) { // for copy on write
     11847        cpy.selectionID = true;
     11848    }
    1160211849    return cpy;
    1160311850};
     
    1170611953        'make a copy\nand pick it up'
    1170711954    );
    11708     menu.addItem("delete", 'destroy');
     11955    menu.addItem("delete", 'userDestroy');
    1170911956    menu.addItem(
    1171011957        "comment pic...",
     
    1171311960            ide.saveCanvasAs(
    1171411961                myself.fullImageClassic(),
    11715                 ide.projetName || localize('Untitled') + ' ' +
    11716                     localize('comment pic'),
    11717                 true // request new window
     11962                (ide.projectName || localize('untitled')) + ' ' +
     11963                    localize('comment pic')
    1171811964            );
    1171911965        },
     
    1172111967    );
    1172211968    return menu;
     11969};
     11970
     11971CommentMorph.prototype.userDestroy = function () {
     11972    this.selectForEdit().destroy(); // enable copy-on-edit
    1172311973};
    1172411974
     
    1174311993// CommentMorph dragging & dropping
    1174411994
    11745 CommentMorph.prototype.prepareToBeGrabbed = function () {
     11995CommentMorph.prototype.prepareToBeGrabbed = function (hand) {
    1174611996    // disassociate from the block I'm posted to
    1174711997    if (this.block) {
     
    1175812008};
    1175912009
     12010CommentMorph.prototype.selectForEdit =
     12011    SyntaxElementMorph.prototype.selectForEdit;
     12012
    1176012013CommentMorph.prototype.snap = function (hand) {
    1176112014    // passing the hand is optional (for when blocks are dragged & dropped)
     
    1176612019        return null;
    1176712020    }
    11768 
    11769     scripts.clearDropHistory();
    11770     scripts.lastDroppedBlock = this;
     12021    scripts.clearDropInfo();
    1177112022    target = scripts.closestBlock(this, hand);
    11772 
    1177312023    if (target !== null) {
    1177412024        target.comment = this;
     
    1177712027            this.snapSound.play();
    1177812028        }
     12029        scripts.lastDropTarget = {element: target};
    1177912030    }
    1178012031    this.align();
     12032    scripts.lastDroppedBlock = this;
     12033    if (hand) {
     12034        scripts.recordDrop(hand.grabOrigin);
     12035    }
     12036
    1178112037};
    1178212038
     
    1183312089    this.stickyOffset = this.position().subtract(this.block.position());
    1183412090    this.step = function () {
     12091        if (!this.block) { // kludge - only needed for "redo"
     12092            this.stopFollowing();
     12093            return;
     12094        }
    1183512095        this.setPosition(this.block.position().add(this.stickyOffset));
    1183612096    };
     
    1195312213    world.keyboardReceiver = this;
    1195412214    this.fixLayout();
     12215    this.editor.updateToolbar();
    1195512216};
    1195612217
     
    1211112372
    1211212373ScriptFocusMorph.prototype.insertBlock = function (block) {
    12113     var pb, stage, ide;
     12374    var pb, stage, ide, rcvr;
    1211412375    block.isTemplate = false;
    1211512376    block.isDraggable = true;
     
    1217712438    // register generic hat blocks
    1217812439    if (block.selector === 'receiveCondition') {
    12179         if (this.editor.owner) {
    12180             stage = this.editor.owner.parentThatIsA(StageMorph);
     12440        rcvr = this.editor.scriptTarget();
     12441        if (rcvr) {
     12442            stage = rcvr.parentThatIsA(StageMorph);
    1218112443            if (stage) {
    1218212444                stage.enableCustomHatBlocks = true;
     
    1218812450            }
    1218912451        }
     12452    }
     12453
     12454    // experimental: if the inserted block has inputs, go to the first one
     12455    if (block.inputs && block.inputs().length) {
     12456        this.element = block;
     12457        this.atEnd = false;
     12458        this.nextElement();
    1219012459    }
    1219112460};
     
    1221912488ScriptFocusMorph.prototype.stopEditing = function () {
    1222012489    this.editor.focus = null;
     12490    this.editor.updateToolbar();
    1222112491    this.world().keyboardReceiver = null;
    1222212492    this.destroy();
     
    1244712717    });
    1244812718    return scripts;
     12719};
     12720
     12721// ScriptFocusMorph undo / redo
     12722
     12723ScriptFocusMorph.prototype.undrop = function () {
     12724    this.editor.undrop();
     12725};
     12726
     12727ScriptFocusMorph.prototype.redrop = function () {
     12728    this.editor.redrop();
    1244912729};
    1245012730
     
    1259212872    case 'backspace':
    1259312873        return this.deleteLastElement();
     12874    case 'ctrl z':
     12875        return this.undrop();
     12876    case 'ctrl y':
     12877    case 'ctrl shift z':
     12878        return this.redrop();
     12879    case 'ctrl [': // ignore the first press of the Mac cmd key
     12880        return;
    1259412881    default:
    1259512882        types = this.blockTypes();
     
    1260212889            delete this.step;
    1260312890            this.show();
    12604             this.editor.owner.searchBlocks(
     12891            this.editor.scriptTarget().searchBlocks(
    1260512892                key,
    1260612893                types,
  • byob-snap/trunk/fuentes/install/usr/share/byob-snap/byob.js

    r2503 r8036  
    33    byob.js
    44
    5     "build your own blocks" for SNAP!
     5    "build your own blocks" for Snap!
    66    based on morphic.js, widgets.js blocks.js, threads.js and objects.js
    77    inspired by Scratch
     
    1010    jens@moenig.org
    1111
    12     Copyright (C) 2016 by Jens Mönig
     12    Copyright (C) 2018 by Jens Mönig
    1313
    1414    This file is part of Snap!.
     
    9696*/
    9797
    98 /*global modules, CommandBlockMorph, SpriteMorph, TemplateSlotMorph,
     98/*global modules, CommandBlockMorph, SpriteMorph, TemplateSlotMorph, Map,
    9999StringMorph, Color, DialogBoxMorph, ScriptsMorph, ScrollFrameMorph,
    100100Point, HandleMorph, HatBlockMorph, BlockMorph, detect, List, Process,
     
    103103contains, InputSlotMorph, ToggleButtonMorph, IDE_Morph, MenuMorph, copy,
    104104ToggleElementMorph, Morph, fontHeight, StageMorph, SyntaxElementMorph,
    105 SnapSerializer, CommentMorph, localize, CSlotMorph, SpeechBubbleMorph,
    106 MorphicPreferences, SymbolMorph, isNil, CursorMorph, VariableFrame,
    107 WatcherMorph, Variable*/
     105SnapSerializer, CommentMorph, localize, CSlotMorph, MorphicPreferences,