kopia lustrzana https://github.com/backface/turtlestitch
				
				
				
			towards v4.0.4 - under construction -
* Show result bubble when the user clicks on a command script that uses REPORT (You can now click on REPORT and it actually does something) * New generic “When” hat block, enhances red stop button behavior * New block (instance) variables feature (experimental) * evaluator performance optimizations * Morphic grab-threshold fix for scroll frames * fixed several block rendering glitches * List category LENGTH reporter now also works on text * Changed “any” to “random” (in English only) * new FILL primitive in the Pen category * switched to animation frame scheduling, please use TURBO for music * Updated German translationdev
							rodzic
							
								
									b4af8f52e3
								
							
						
					
					
						commit
						f24b65f673
					
				
							
								
								
									
										120
									
								
								blocks.js
								
								
								
								
							
							
						
						
									
										120
									
								
								blocks.js
								
								
								
								
							| 
						 | 
				
			
			@ -156,7 +156,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
 | 
			
		|||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.blocks = '2015-November-17';
 | 
			
		||||
modules.blocks = '2015-December-15';
 | 
			
		||||
 | 
			
		||||
var SyntaxElementMorph;
 | 
			
		||||
var BlockMorph;
 | 
			
		||||
| 
						 | 
				
			
			@ -485,6 +485,10 @@ SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) {
 | 
			
		|||
    if ((idx === -1) || (scripts === null)) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (oldArg.cachedSlotSpec) {oldArg.cachedSlotSpec = null; }
 | 
			
		||||
    if (newArg.cachedSlotSpec) {newArg.cachedSlotSpec = null; }
 | 
			
		||||
 | 
			
		||||
    this.startLayout();
 | 
			
		||||
    if (newArg.parent) {
 | 
			
		||||
        newArg.parent.removeChild(newArg);
 | 
			
		||||
| 
						 | 
				
			
			@ -532,6 +536,9 @@ SyntaxElementMorph.prototype.silentReplaceInput = function (oldArg, newArg) {
 | 
			
		|||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (oldArg.cachedSlotSpec) {oldArg.cachedSlotSpec = null; }
 | 
			
		||||
    if (newArg.cachedSlotSpec) {newArg.cachedSlotSpec = null; }
 | 
			
		||||
 | 
			
		||||
    if (newArg.parent) {
 | 
			
		||||
        newArg.parent.removeChild(newArg);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -628,6 +635,10 @@ SyntaxElementMorph.prototype.getVarNamesDict = function () {
 | 
			
		|||
    rcvr = block.receiver();
 | 
			
		||||
    block.allParents().forEach(function (morph) {
 | 
			
		||||
        if (morph instanceof PrototypeHatBlockMorph) {
 | 
			
		||||
            tempVars.push.apply(
 | 
			
		||||
                tempVars,
 | 
			
		||||
                morph.variableNames()
 | 
			
		||||
            );
 | 
			
		||||
            tempVars.push.apply(
 | 
			
		||||
                tempVars,
 | 
			
		||||
                morph.inputs()[0].inputFragmentNames()
 | 
			
		||||
| 
						 | 
				
			
			@ -780,6 +791,10 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
 | 
			
		|||
            part = new MultiArgMorph('%t', null, 1, spec);
 | 
			
		||||
            part.canBeEmpty = false;
 | 
			
		||||
            break;
 | 
			
		||||
        case '%blockVars':
 | 
			
		||||
            part = new MultiArgMorph('%t', 'block variables', 0, spec);
 | 
			
		||||
            part.canBeEmpty = false;
 | 
			
		||||
            break;
 | 
			
		||||
        case '%parms':
 | 
			
		||||
            part = new MultiArgMorph('%t', 'Input Names:', 0, spec);
 | 
			
		||||
            part.canBeEmpty = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -2125,11 +2140,9 @@ BlockMorph.prototype.setSpec = function (spec, silently) {
 | 
			
		|||
        if (part instanceof RingMorph) {
 | 
			
		||||
            part.fixBlockColor();
 | 
			
		||||
        }
 | 
			
		||||
        if (part instanceof MultiArgMorph
 | 
			
		||||
                || contains(
 | 
			
		||||
                    [CommandSlotMorph, RingCommandSlotMorph],
 | 
			
		||||
                    part.constructor
 | 
			
		||||
                )) {
 | 
			
		||||
        if (part instanceof MultiArgMorph ||
 | 
			
		||||
                part.constructor === CommandSlotMorph ||
 | 
			
		||||
                part.constructor === RingCommandSlotMorph) {
 | 
			
		||||
            part.fixLayout();
 | 
			
		||||
        }
 | 
			
		||||
        if (myself.isPrototype) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2374,7 +2387,7 @@ BlockMorph.prototype.developersMenu = function () {
 | 
			
		|||
 | 
			
		||||
        new DialogBoxMorph(
 | 
			
		||||
            this,
 | 
			
		||||
            this.setSpec,
 | 
			
		||||
            this.userSetSpec,
 | 
			
		||||
            this
 | 
			
		||||
        ).prompt(
 | 
			
		||||
            menu.title + '\nspec',
 | 
			
		||||
| 
						 | 
				
			
			@ -2861,7 +2874,7 @@ BlockMorph.prototype.codeMappingHeader = function () {
 | 
			
		|||
 | 
			
		||||
BlockMorph.prototype.eraseHoles = function (context) {
 | 
			
		||||
    var myself = this,
 | 
			
		||||
        isReporter = this instanceof ReporterBlockMorph,
 | 
			
		||||
        isRing = this instanceof RingMorph,
 | 
			
		||||
        shift = this.edge * 0.5,
 | 
			
		||||
        gradient,
 | 
			
		||||
        rightX,
 | 
			
		||||
| 
						 | 
				
			
			@ -2902,7 +2915,7 @@ BlockMorph.prototype.eraseHoles = function (context) {
 | 
			
		|||
        context.clearRect(
 | 
			
		||||
            hole.bounds.origin.x - myself.bounds.origin.x + 1,
 | 
			
		||||
            hole.bounds.origin.y - myself.bounds.origin.y + 1,
 | 
			
		||||
            isReporter ? w - 2 : w + 1,
 | 
			
		||||
            isRing ? w - 2 : w + 1,
 | 
			
		||||
            h
 | 
			
		||||
        );
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -3181,6 +3194,8 @@ BlockMorph.prototype.fullCopy = function () {
 | 
			
		|||
            block.cachedInputs = null;
 | 
			
		||||
            if (block instanceof InputSlotMorph) {
 | 
			
		||||
                block.contents().clearSelection();
 | 
			
		||||
            } else if (block.definition) {
 | 
			
		||||
                block.initializeVariables();
 | 
			
		||||
            }
 | 
			
		||||
        } else if (block instanceof CursorMorph) {
 | 
			
		||||
            block.destroy();
 | 
			
		||||
| 
						 | 
				
			
			@ -3420,7 +3435,10 @@ BlockMorph.prototype.stackWidth = function () {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
BlockMorph.prototype.snap = function () {
 | 
			
		||||
    var top = this.topBlock();
 | 
			
		||||
    var top = this.topBlock(),
 | 
			
		||||
        receiver,
 | 
			
		||||
        stage,
 | 
			
		||||
        ide;
 | 
			
		||||
    top.allComments().forEach(function (comment) {
 | 
			
		||||
        comment.align(top);
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -3431,6 +3449,21 @@ BlockMorph.prototype.snap = function () {
 | 
			
		|||
    if (top.getHighlight()) {
 | 
			
		||||
        top.addHighlight(top.removeHighlight());
 | 
			
		||||
    }
 | 
			
		||||
    // register generic hat blocks
 | 
			
		||||
    if (this.selector === 'receiveCondition') {
 | 
			
		||||
        receiver = top.receiver();
 | 
			
		||||
        if (receiver) {
 | 
			
		||||
            stage = receiver.parentThatIsA(StageMorph);
 | 
			
		||||
            if (stage) {
 | 
			
		||||
                stage.enableCustomHatBlocks = true;
 | 
			
		||||
                stage.threads.pauseCustomHatBlocks = false;
 | 
			
		||||
                ide = stage.parentThatIsA(IDE_Morph);
 | 
			
		||||
                if (ide) {
 | 
			
		||||
                    ide.controlBar.stopButton.refresh();
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// CommandBlockMorph ///////////////////////////////////////////////////
 | 
			
		||||
| 
						 | 
				
			
			@ -3468,6 +3501,7 @@ CommandBlockMorph.prototype.init = function (silently) {
 | 
			
		|||
    this.setExtent(new Point(200, 100), silently);
 | 
			
		||||
    this.partOfCustomCommand = false;
 | 
			
		||||
    this.exitTag = null;
 | 
			
		||||
    // this.cachedNextBlock = null; // don't serialize
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// CommandBlockMorph enumerating:
 | 
			
		||||
| 
						 | 
				
			
			@ -3495,6 +3529,7 @@ CommandBlockMorph.prototype.nextBlock = function (block) {
 | 
			
		|||
        var nb = this.nextBlock(),
 | 
			
		||||
            affected = this.parentThatIsA(CommandSlotMorph);
 | 
			
		||||
        this.add(block);
 | 
			
		||||
        // this.cachedNextBlock = block;
 | 
			
		||||
        if (nb) {
 | 
			
		||||
            block.bottomBlock().nextBlock(nb);
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -3508,6 +3543,18 @@ CommandBlockMorph.prototype.nextBlock = function (block) {
 | 
			
		|||
            affected.fixLayout();
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        /* cachedNextBlock - has issues, disabled for now
 | 
			
		||||
        if (!this.cachedNextBlock) {
 | 
			
		||||
            this.cachedNextBlock = detect(
 | 
			
		||||
                this.children,
 | 
			
		||||
                function (child) {
 | 
			
		||||
                    return child instanceof CommandBlockMorph
 | 
			
		||||
                        && !child.isPrototype;
 | 
			
		||||
                }
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return this.cachedNextBlock;
 | 
			
		||||
        */
 | 
			
		||||
        return detect(
 | 
			
		||||
            this.children,
 | 
			
		||||
            function (child) {
 | 
			
		||||
| 
						 | 
				
			
			@ -4330,6 +4377,7 @@ ReporterBlockMorph.prototype.init = function (isPredicate, silently) {
 | 
			
		|||
    ReporterBlockMorph.uber.init.call(this, silently);
 | 
			
		||||
    this.isPredicate = isPredicate || false;
 | 
			
		||||
    this.setExtent(new Point(200, 80), silently);
 | 
			
		||||
    this.cachedSlotSpec = null; // don't serialize
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// ReporterBlockMorph drag & drop:
 | 
			
		||||
| 
						 | 
				
			
			@ -4340,6 +4388,7 @@ ReporterBlockMorph.prototype.snap = function (hand) {
 | 
			
		|||
        nb,
 | 
			
		||||
        target;
 | 
			
		||||
 | 
			
		||||
    this.cachedSlotSpec = null;
 | 
			
		||||
    if (!scripts instanceof ScriptsMorph) {
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -4388,6 +4437,7 @@ ReporterBlockMorph.prototype.prepareToBeGrabbed = function (handMorph) {
 | 
			
		|||
    }
 | 
			
		||||
    ReporterBlockMorph.uber.prepareToBeGrabbed.call(this, handMorph);
 | 
			
		||||
    this.alpha = 0.85;
 | 
			
		||||
    this.cachedSlotSpec = null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// ReporterBlockMorph enumerating
 | 
			
		||||
| 
						 | 
				
			
			@ -4400,11 +4450,12 @@ ReporterBlockMorph.prototype.blockSequence = function () {
 | 
			
		|||
// ReporterBlockMorph evaluating
 | 
			
		||||
 | 
			
		||||
ReporterBlockMorph.prototype.isUnevaluated = function () {
 | 
			
		||||
/*
 | 
			
		||||
    answer whether my parent block's slot is designated to be of an
 | 
			
		||||
    'unevaluated' kind, denoting a spedial form
 | 
			
		||||
*/
 | 
			
		||||
    return contains(['%anyUE', '%boolUE', '%f'], this.getSlotSpec());
 | 
			
		||||
    // answer whether my parent block's slot is designated to be of an
 | 
			
		||||
    // 'unevaluated' kind, denoting a spedial form
 | 
			
		||||
    var spec = this.getSlotSpec();
 | 
			
		||||
    return spec === '%anyUE' ||
 | 
			
		||||
        spec === '%boolUE' ||
 | 
			
		||||
        spec === '%f';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ReporterBlockMorph.prototype.isLocked = function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -4414,6 +4465,28 @@ ReporterBlockMorph.prototype.isLocked = function () {
 | 
			
		|||
 | 
			
		||||
ReporterBlockMorph.prototype.getSlotSpec = function () {
 | 
			
		||||
    // answer the spec of the slot I'm in, if any
 | 
			
		||||
    // cached for performance
 | 
			
		||||
    if (!this.cachedSlotSpec) {
 | 
			
		||||
        this.cachedSlotSpec = this.determineSlotSpec();
 | 
			
		||||
    /*
 | 
			
		||||
    } else {
 | 
			
		||||
        // debug slot spec caching
 | 
			
		||||
        var real = this.determineSlotSpec();
 | 
			
		||||
        if (real !== this.cachedSlotSpec) {
 | 
			
		||||
            throw new Error(
 | 
			
		||||
                'cached slot spec ' +
 | 
			
		||||
                this.cachedSlotSpec +
 | 
			
		||||
                ' does not match: ' +
 | 
			
		||||
                real
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    */
 | 
			
		||||
    }
 | 
			
		||||
    return this.cachedSlotSpec;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ReporterBlockMorph.prototype.determineSlotSpec = function () {
 | 
			
		||||
    // private - answer the spec of the slot I'm in, if any
 | 
			
		||||
    var parts, idx;
 | 
			
		||||
    if (this.parent instanceof BlockMorph) {
 | 
			
		||||
        parts = this.parent.parts().filter(
 | 
			
		||||
| 
						 | 
				
			
			@ -4440,19 +4513,25 @@ ReporterBlockMorph.prototype.getSlotSpec = function () {
 | 
			
		|||
// ReporterBlockMorph events
 | 
			
		||||
 | 
			
		||||
ReporterBlockMorph.prototype.mouseClickLeft = function (pos) {
 | 
			
		||||
    var isRing;
 | 
			
		||||
    var label;
 | 
			
		||||
    if (this.parent instanceof BlockInputFragmentMorph) {
 | 
			
		||||
        return this.parent.mouseClickLeft();
 | 
			
		||||
    }
 | 
			
		||||
    if (this.parent instanceof TemplateSlotMorph) {
 | 
			
		||||
        isRing = this.parent.parent && this.parent.parent.parent &&
 | 
			
		||||
            this.parent.parent.parent instanceof RingMorph;
 | 
			
		||||
        if (this.parent.parent && this.parent.parent.parent &&
 | 
			
		||||
                this.parent.parent.parent instanceof RingMorph) {
 | 
			
		||||
            label = "Input name";
 | 
			
		||||
        } else if (this.parent.parent.elementSpec === '%blockVars') {
 | 
			
		||||
            label = "Block variable name";
 | 
			
		||||
        } else {
 | 
			
		||||
            label = "Script variable name";
 | 
			
		||||
        }
 | 
			
		||||
        new DialogBoxMorph(
 | 
			
		||||
            this,
 | 
			
		||||
            this.userSetSpec,
 | 
			
		||||
            this
 | 
			
		||||
        ).prompt(
 | 
			
		||||
            isRing ? "Input name" : "Script variable name",
 | 
			
		||||
            label,
 | 
			
		||||
            this.blockSpec,
 | 
			
		||||
            this.world()
 | 
			
		||||
        );
 | 
			
		||||
| 
						 | 
				
			
			@ -9713,7 +9792,8 @@ MultiArgMorph.prototype.addInput = function (contents) {
 | 
			
		|||
    // newPart.alpha = this.alpha ? 1 : (1 - this.alpha) / 2;
 | 
			
		||||
    if (contents) {
 | 
			
		||||
        newPart.setContents(contents);
 | 
			
		||||
    } else if (this.elementSpec === '%scriptVars') {
 | 
			
		||||
    } else if (this.elementSpec === '%scriptVars' ||
 | 
			
		||||
            this.elementSpec === '%blockVars') {
 | 
			
		||||
        name = '';
 | 
			
		||||
        i = idx;
 | 
			
		||||
        while (i > 0) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										166
									
								
								byob.js
								
								
								
								
							
							
						
						
									
										166
									
								
								byob.js
								
								
								
								
							| 
						 | 
				
			
			@ -104,11 +104,11 @@ ArrowMorph, PushButtonMorph, contains, InputSlotMorph, ShadowMorph,
 | 
			
		|||
ToggleButtonMorph, IDE_Morph, MenuMorph, copy, ToggleElementMorph,
 | 
			
		||||
Morph, fontHeight, StageMorph, SyntaxElementMorph, SnapSerializer,
 | 
			
		||||
CommentMorph, localize, CSlotMorph, SpeechBubbleMorph, MorphicPreferences,
 | 
			
		||||
SymbolMorph, isNil, CursorMorph*/
 | 
			
		||||
SymbolMorph, isNil, CursorMorph, VariableFrame*/
 | 
			
		||||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.byob = '2015-November-16';
 | 
			
		||||
modules.byob = '2015-December-15';
 | 
			
		||||
 | 
			
		||||
// Declarations
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -142,6 +142,7 @@ function CustomBlockDefinition(spec, receiver) {
 | 
			
		|||
    this.spec = spec || '';
 | 
			
		||||
    // format: {'inputName' : [type, default, options, readonly]}
 | 
			
		||||
    this.declarations = {};
 | 
			
		||||
    this.variableNames = [];
 | 
			
		||||
    this.comment = null;
 | 
			
		||||
    this.codeMapping = null; // experimental, generate text code
 | 
			
		||||
    this.codeHeader = null; // experimental, generate text code
 | 
			
		||||
| 
						 | 
				
			
			@ -384,7 +385,7 @@ CustomBlockDefinition.prototype.scriptsModel = function () {
 | 
			
		|||
    proto.allComments().forEach(function (comment) {
 | 
			
		||||
        comment.align(proto);
 | 
			
		||||
    });
 | 
			
		||||
    proto.children[0].fixLayout();
 | 
			
		||||
    proto.parts()[0].fixLayout();
 | 
			
		||||
    scripts.fixMultiArgs();
 | 
			
		||||
    return scripts;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -409,11 +410,21 @@ CustomCommandBlockMorph.prototype.init = function (definition, isProto) {
 | 
			
		|||
    CustomCommandBlockMorph.uber.init.call(this, true); // silently
 | 
			
		||||
    this.category = definition.category;
 | 
			
		||||
    this.selector = 'evaluateCustomBlock';
 | 
			
		||||
    this.variables = null;
 | 
			
		||||
    this.initializeVariables();
 | 
			
		||||
    if (definition) { // needed for de-serializing
 | 
			
		||||
        this.refresh();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CustomCommandBlockMorph.prototype.initializeVariables = function () {
 | 
			
		||||
    var myself = this;
 | 
			
		||||
    this.variables = new VariableFrame();
 | 
			
		||||
    this.definition.variableNames.forEach(function (name) {
 | 
			
		||||
        myself.variables.addVar(name);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CustomCommandBlockMorph.prototype.refresh = function (silently) {
 | 
			
		||||
    var def = this.definition,
 | 
			
		||||
        newSpec = this.isPrototype ?
 | 
			
		||||
| 
						 | 
				
			
			@ -447,6 +458,10 @@ CustomCommandBlockMorph.prototype.refresh = function (silently) {
 | 
			
		|||
            inp.setContents(def.inputNames()[idx]);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    // initialize block vars
 | 
			
		||||
    // to do: preserve values of unchanged variable names
 | 
			
		||||
    this.initializeVariables();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CustomCommandBlockMorph.prototype.restoreInputs = function (oldInputs) {
 | 
			
		||||
| 
						 | 
				
			
			@ -759,7 +774,47 @@ CustomCommandBlockMorph.prototype.isInUse = function () {
 | 
			
		|||
// CustomCommandBlockMorph menu:
 | 
			
		||||
 | 
			
		||||
CustomCommandBlockMorph.prototype.userMenu = function () {
 | 
			
		||||
    var menu;
 | 
			
		||||
    var hat = this.parentThatIsA(PrototypeHatBlockMorph),
 | 
			
		||||
        rcvr = this.receiver(),
 | 
			
		||||
        myself = this,
 | 
			
		||||
        menu;
 | 
			
		||||
 | 
			
		||||
   function monitor(vName) {
 | 
			
		||||
        var stage = rcvr.parentThatIsA(StageMorph),
 | 
			
		||||
            varFrame = myself.variables;
 | 
			
		||||
        menu.addItem(
 | 
			
		||||
            vName + '...',
 | 
			
		||||
            function () {
 | 
			
		||||
                var watcher = detect(
 | 
			
		||||
                    stage.children,
 | 
			
		||||
                    function (morph) {
 | 
			
		||||
                        return morph instanceof WatcherMorph
 | 
			
		||||
                            && morph.target === varFrame
 | 
			
		||||
                            && morph.getter === vName;
 | 
			
		||||
                    }
 | 
			
		||||
                ),
 | 
			
		||||
                    others;
 | 
			
		||||
                if (watcher !== null) {
 | 
			
		||||
                    watcher.show();
 | 
			
		||||
                    watcher.fixLayout(); // re-hide hidden parts
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                watcher = new WatcherMorph(
 | 
			
		||||
                    vName + ' ' + localize('(temporary)'),
 | 
			
		||||
                    SpriteMorph.prototype.blockColor.variables,
 | 
			
		||||
                    varFrame,
 | 
			
		||||
                    vName
 | 
			
		||||
                );
 | 
			
		||||
                watcher.setPosition(stage.position().add(10));
 | 
			
		||||
                others = stage.watchers(watcher.left());
 | 
			
		||||
                if (others.length > 0) {
 | 
			
		||||
                    watcher.setTop(others[others.length - 1].bottom());
 | 
			
		||||
                }
 | 
			
		||||
                stage.add(watcher);
 | 
			
		||||
                watcher.fixLayout();
 | 
			
		||||
            }
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.isPrototype) {
 | 
			
		||||
        menu = new MenuMorph(this);
 | 
			
		||||
| 
						 | 
				
			
			@ -770,6 +825,23 @@ CustomCommandBlockMorph.prototype.userMenu = function () {
 | 
			
		|||
            },
 | 
			
		||||
            'open a new window\nwith a picture of this script'
 | 
			
		||||
        );
 | 
			
		||||
        if (hat.inputs().length < 2) {
 | 
			
		||||
            menu.addItem(
 | 
			
		||||
                "block variables...",
 | 
			
		||||
                function () {
 | 
			
		||||
                    hat.enableBlockVars();
 | 
			
		||||
                },
 | 
			
		||||
                'experimental -\nunder construction'
 | 
			
		||||
            );
 | 
			
		||||
        } else {
 | 
			
		||||
            menu.addItem(
 | 
			
		||||
                "remove block variables...",
 | 
			
		||||
                function () {
 | 
			
		||||
                    hat.enableBlockVars(false);
 | 
			
		||||
                },
 | 
			
		||||
                'experimental -\nunder construction'
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        menu = this.constructor.uber.userMenu.call(this);
 | 
			
		||||
        if (!menu) {
 | 
			
		||||
| 
						 | 
				
			
			@ -780,6 +852,10 @@ CustomCommandBlockMorph.prototype.userMenu = function () {
 | 
			
		|||
 | 
			
		||||
        // menu.addItem("export definition...", 'exportBlockDefinition');
 | 
			
		||||
        menu.addItem("delete block definition...", 'deleteBlockDefinition');
 | 
			
		||||
 | 
			
		||||
        this.variables.names().forEach(function (vName) {
 | 
			
		||||
            monitor(vName);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    menu.addItem("edit...", 'edit'); // works also for prototypes
 | 
			
		||||
    return menu;
 | 
			
		||||
| 
						 | 
				
			
			@ -938,16 +1014,19 @@ CustomReporterBlockMorph.prototype.init = function (
 | 
			
		|||
) {
 | 
			
		||||
    this.definition = definition; // mandatory
 | 
			
		||||
    this.isPrototype = isProto || false; // optional
 | 
			
		||||
 | 
			
		||||
    CustomReporterBlockMorph.uber.init.call(this, isPredicate, true); // sil.
 | 
			
		||||
 | 
			
		||||
    this.category = definition.category;
 | 
			
		||||
    this.variables = new VariableFrame();
 | 
			
		||||
    this.initializeVariables();
 | 
			
		||||
    this.selector = 'evaluateCustomBlock';
 | 
			
		||||
    if (definition) { // needed for de-serializing
 | 
			
		||||
        this.refresh();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CustomReporterBlockMorph.prototype.initializeVariables =
 | 
			
		||||
    CustomCommandBlockMorph.prototype.initializeVariables;
 | 
			
		||||
 | 
			
		||||
CustomReporterBlockMorph.prototype.refresh = function () {
 | 
			
		||||
    CustomCommandBlockMorph.prototype.refresh.call(this, true);
 | 
			
		||||
    if (!this.isPrototype) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1732,7 +1811,7 @@ BlockEditorMorph.prototype.init = function (definition, target) {
 | 
			
		|||
 | 
			
		||||
    this.setExtent(new Point(375, 300)); // normal initial extent
 | 
			
		||||
    this.fixLayout();
 | 
			
		||||
    proto.children[0].fixLayout();
 | 
			
		||||
    proto.parts()[0].fixLayout();
 | 
			
		||||
    scripts.fixMultiArgs();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1864,6 +1943,7 @@ BlockEditorMorph.prototype.updateDefinition = function () {
 | 
			
		|||
    this.definition.receiver = this.target; // only for serialization
 | 
			
		||||
    this.definition.spec = this.prototypeSpec();
 | 
			
		||||
    this.definition.declarations = this.prototypeSlots();
 | 
			
		||||
    this.definition.variableNames = this.variableNames();
 | 
			
		||||
    this.definition.scripts = [];
 | 
			
		||||
    this.definition.editorDimensions = this.bounds.copy();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1940,6 +2020,14 @@ BlockEditorMorph.prototype.prototypeSlots = function () {
 | 
			
		|||
    ).parts()[0].declarationsFromFragments();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
BlockEditorMorph.prototype.variableNames = function () {
 | 
			
		||||
    // answer the variable declarations from my prototype hat
 | 
			
		||||
    return detect(
 | 
			
		||||
        this.body.contents.children,
 | 
			
		||||
        function (c) {return c instanceof PrototypeHatBlockMorph; }
 | 
			
		||||
    ).variableNames();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// BlockEditorMorph layout
 | 
			
		||||
 | 
			
		||||
BlockEditorMorph.prototype.setInitialDimensions = function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -2006,7 +2094,8 @@ function PrototypeHatBlockMorph(definition) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
PrototypeHatBlockMorph.prototype.init = function (definition) {
 | 
			
		||||
    var proto = definition.prototypeInstance();
 | 
			
		||||
    var proto = definition.prototypeInstance(),
 | 
			
		||||
        vars;
 | 
			
		||||
 | 
			
		||||
    this.definition = definition;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2019,6 +2108,14 @@ PrototypeHatBlockMorph.prototype.init = function (definition) {
 | 
			
		|||
    this.color = SpriteMorph.prototype.blockColor.control;
 | 
			
		||||
    this.category = 'control';
 | 
			
		||||
    this.add(proto);
 | 
			
		||||
    if (definition.variableNames.length) {
 | 
			
		||||
        vars = this.labelPart('%blockVars');
 | 
			
		||||
        this.add(this.labelPart('%br'));
 | 
			
		||||
        this.add(vars);
 | 
			
		||||
        definition.variableNames.forEach(function (name) {
 | 
			
		||||
            vars.addInput(name);
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
    proto.refreshPrototypeSlotTypes(); // show slot type indicators
 | 
			
		||||
    this.fixLayout();
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -2032,11 +2129,11 @@ PrototypeHatBlockMorph.prototype.mouseClickLeft = function () {
 | 
			
		|||
    if (this.world().currentKey === 16) { // shift-clicked
 | 
			
		||||
        return this.focus();
 | 
			
		||||
    }
 | 
			
		||||
    this.children[0].mouseClickLeft();
 | 
			
		||||
    this.parts()[0].mouseClickLeft();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
PrototypeHatBlockMorph.prototype.userMenu = function () {
 | 
			
		||||
    return this.children[0].userMenu();
 | 
			
		||||
    return this.parts()[0].userMenu();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// PrototypeHatBlockMorph zebra coloring
 | 
			
		||||
| 
						 | 
				
			
			@ -2045,7 +2142,7 @@ PrototypeHatBlockMorph.prototype.fixBlockColor = function (
 | 
			
		|||
    nearestBlock,
 | 
			
		||||
    isForced
 | 
			
		||||
) {
 | 
			
		||||
    var nearest = this.children[0] || nearestBlock;
 | 
			
		||||
    var nearest = this.parts()[0] || nearestBlock;
 | 
			
		||||
 | 
			
		||||
    if (!this.zebraContrast && !isForced) {
 | 
			
		||||
        return;
 | 
			
		||||
| 
						 | 
				
			
			@ -2068,6 +2165,25 @@ PrototypeHatBlockMorph.prototype.fixBlockColor = function (
 | 
			
		|||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// PrototypeHatBlockMorph block instance variables
 | 
			
		||||
 | 
			
		||||
PrototypeHatBlockMorph.prototype.variableNames = function (choice) {
 | 
			
		||||
    var parts = this.parts();
 | 
			
		||||
    if (parts.length < 3) {return []; }
 | 
			
		||||
    return parts[2].evaluate();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
PrototypeHatBlockMorph.prototype.enableBlockVars = function (choice) {
 | 
			
		||||
    var prot = this.parts()[0];
 | 
			
		||||
    if (choice === false) {
 | 
			
		||||
        this.setSpec('%s', true);
 | 
			
		||||
    } else {
 | 
			
		||||
        this.setSpec('%s %br %blockVars', true);
 | 
			
		||||
    }
 | 
			
		||||
    this.replaceInput(this.parts()[0], prot);
 | 
			
		||||
    this.spec = null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// BlockLabelFragment //////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// BlockLabelFragment instance creation:
 | 
			
		||||
| 
						 | 
				
			
			@ -2306,8 +2422,11 @@ BlockLabelFragmentMorph.prototype.userMenu = function () {
 | 
			
		|||
                tuple[0] = '$' + string;
 | 
			
		||||
                myself.text = tuple.join('-');
 | 
			
		||||
                myself.fragment.labelString = myself.text;
 | 
			
		||||
                myself.parent.parent.changed();
 | 
			
		||||
                myself.drawNew();
 | 
			
		||||
                myself.changed();
 | 
			
		||||
                myself.parent.parent.fixLayout();
 | 
			
		||||
                myself.parent.parent.changed();
 | 
			
		||||
            },
 | 
			
		||||
            null,
 | 
			
		||||
            this,
 | 
			
		||||
| 
						 | 
				
			
			@ -2428,21 +2547,38 @@ BlockLabelPlaceHolderMorph.prototype.drawNew = function () {
 | 
			
		|||
        if (this.parent.fixLayout) {
 | 
			
		||||
            this.parent.fixLayout();
 | 
			
		||||
        }
 | 
			
		||||
        if (this.parent.parent instanceof PrototypeHatBlockMorph) {
 | 
			
		||||
            this.parent.parent.fixLayout();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// BlockLabelPlaceHolderMorph events:
 | 
			
		||||
 | 
			
		||||
BlockLabelPlaceHolderMorph.prototype.mouseEnter = function () {
 | 
			
		||||
    var hat = this.parentThatIsA(PrototypeHatBlockMorph);
 | 
			
		||||
    this.isHighlighted = true;
 | 
			
		||||
    this.drawNew();
 | 
			
		||||
    this.changed();
 | 
			
		||||
    if (this.plainLabel && hat) {
 | 
			
		||||
        hat.changed();
 | 
			
		||||
        this.drawNew();
 | 
			
		||||
        hat.changed();
 | 
			
		||||
    } else {
 | 
			
		||||
        this.drawNew();
 | 
			
		||||
        this.changed();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
BlockLabelPlaceHolderMorph.prototype.mouseLeave = function () {
 | 
			
		||||
    var hat = this.parentThatIsA(PrototypeHatBlockMorph);
 | 
			
		||||
    this.isHighlighted = false;
 | 
			
		||||
    this.drawNew();
 | 
			
		||||
    this.changed();
 | 
			
		||||
    if (this.plainLabel && hat) {
 | 
			
		||||
        hat.changed();
 | 
			
		||||
        this.drawNew();
 | 
			
		||||
        hat.changed();
 | 
			
		||||
    } else {
 | 
			
		||||
        this.drawNew();
 | 
			
		||||
        this.changed();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
BlockLabelPlaceHolderMorph.prototype.mouseClickLeft
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										21
									
								
								cloud.js
								
								
								
								
							
							
						
						
									
										21
									
								
								cloud.js
								
								
								
								
							| 
						 | 
				
			
			@ -30,7 +30,7 @@
 | 
			
		|||
/*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop,
 | 
			
		||||
localize*/
 | 
			
		||||
 | 
			
		||||
modules.cloud = '2015-December-04';
 | 
			
		||||
modules.cloud = '2015-December-15';
 | 
			
		||||
 | 
			
		||||
// Global stuff
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -326,7 +326,8 @@ Cloud.prototype.reconnect = function (
 | 
			
		|||
Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
 | 
			
		||||
    var myself = this,
 | 
			
		||||
        pdata,
 | 
			
		||||
        media;
 | 
			
		||||
        media,
 | 
			
		||||
        size;
 | 
			
		||||
 | 
			
		||||
    ide.serializer.isCollectingMedia = true;
 | 
			
		||||
    pdata = ide.serializer.serialize(ide.stage);
 | 
			
		||||
| 
						 | 
				
			
			@ -335,6 +336,19 @@ Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
 | 
			
		|||
    ide.serializer.isCollectingMedia = false;
 | 
			
		||||
    ide.serializer.flushMedia();
 | 
			
		||||
 | 
			
		||||
    size = pdata.length + (media ? media.length : 0);
 | 
			
		||||
    /*
 | 
			
		||||
    if (size > 5242880) {
 | 
			
		||||
        new DialogBoxMorph().inform(
 | 
			
		||||
            'Snap!Cloud',
 | 
			
		||||
            'Project exceeds 5 MB size limit',
 | 
			
		||||
            ide.world(),
 | 
			
		||||
            ide.cloudIcon(null, new Color(180, 0, 0))
 | 
			
		||||
        );
 | 
			
		||||
        throw new Error('Project exceeds 5 MB size limit');
 | 
			
		||||
    }
 | 
			
		||||
    */
 | 
			
		||||
 | 
			
		||||
    // check if serialized data can be parsed back again
 | 
			
		||||
    try {
 | 
			
		||||
        ide.serializer.parse(pdata);
 | 
			
		||||
| 
						 | 
				
			
			@ -353,6 +367,7 @@ Cloud.prototype.saveProject = function (ide, callBack, errorCall) {
 | 
			
		|||
    ide.serializer.isCollectingMedia = false;
 | 
			
		||||
    ide.serializer.flushMedia();
 | 
			
		||||
 | 
			
		||||
    ide.showMessage('Uploading ' + Math.round(size / 1024) + ' KB...');
 | 
			
		||||
    myself.reconnect(
 | 
			
		||||
        function () {
 | 
			
		||||
            myself.callService(
 | 
			
		||||
| 
						 | 
				
			
			@ -545,7 +560,7 @@ Cloud.prototype.callService = function (
 | 
			
		|||
                } else {
 | 
			
		||||
                    responseList = myself.parseResponse(
 | 
			
		||||
                        request.responseText
 | 
			
		||||
                    )
 | 
			
		||||
                    );
 | 
			
		||||
                }
 | 
			
		||||
                callBack.call(null, responseList, service.url);
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										90
									
								
								gui.js
								
								
								
								
							
							
						
						
									
										90
									
								
								gui.js
								
								
								
								
							| 
						 | 
				
			
			@ -71,7 +71,7 @@ BlockRemovalDialogMorph*/
 | 
			
		|||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.gui = '2015-December-04';
 | 
			
		||||
modules.gui = '2015-December-15';
 | 
			
		||||
 | 
			
		||||
// Declarations
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -624,11 +624,22 @@ IDE_Morph.prototype.createControlBar = function () {
 | 
			
		|||
    this.controlBar.appModeButton = appModeButton; // for refreshing
 | 
			
		||||
 | 
			
		||||
    // stopButton
 | 
			
		||||
    button = new PushButtonMorph(
 | 
			
		||||
        this,
 | 
			
		||||
    button = new ToggleButtonMorph(
 | 
			
		||||
        null, // colors
 | 
			
		||||
        this, // the IDE is the target
 | 
			
		||||
        'stopAllScripts',
 | 
			
		||||
        new SymbolMorph('octagon', 14)
 | 
			
		||||
        [
 | 
			
		||||
            new SymbolMorph('octagon', 14),
 | 
			
		||||
            new SymbolMorph('square', 14)
 | 
			
		||||
        ],
 | 
			
		||||
        function () {  // query
 | 
			
		||||
            return myself.stage ?
 | 
			
		||||
                    myself.stage.enableCustomHatBlocks &&
 | 
			
		||||
                        myself.stage.threads.pauseCustomHatBlocks
 | 
			
		||||
                        : true;
 | 
			
		||||
        }
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    button.corner = 12;
 | 
			
		||||
    button.color = colors[0];
 | 
			
		||||
    button.highlightColor = colors[1];
 | 
			
		||||
| 
						 | 
				
			
			@ -642,13 +653,15 @@ IDE_Morph.prototype.createControlBar = function () {
 | 
			
		|||
    button.drawNew();
 | 
			
		||||
    // button.hint = 'stop\nevery-\nthing';
 | 
			
		||||
    button.fixLayout();
 | 
			
		||||
    button.refresh();
 | 
			
		||||
    stopButton = button;
 | 
			
		||||
    this.controlBar.add(stopButton);
 | 
			
		||||
    this.controlBar.stopButton = stopButton; // for refreshing
 | 
			
		||||
 | 
			
		||||
    //pauseButton
 | 
			
		||||
    button = new ToggleButtonMorph(
 | 
			
		||||
        null, //colors,
 | 
			
		||||
        myself, // the IDE is the target
 | 
			
		||||
        this, // the IDE is the target
 | 
			
		||||
        'togglePauseResume',
 | 
			
		||||
        [
 | 
			
		||||
            new SymbolMorph('pause', 12),
 | 
			
		||||
| 
						 | 
				
			
			@ -1706,6 +1719,8 @@ IDE_Morph.prototype.pressStart = function () {
 | 
			
		|||
    if (this.world().currentKey === 16) { // shiftClicked
 | 
			
		||||
        this.toggleFastTracking();
 | 
			
		||||
    } else {
 | 
			
		||||
        this.stage.threads.pauseCustomHatBlocks = false;
 | 
			
		||||
        this.controlBar.stopButton.refresh();
 | 
			
		||||
        this.runScripts();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1763,6 +1778,13 @@ IDE_Morph.prototype.isPaused = function () {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
IDE_Morph.prototype.stopAllScripts = function () {
 | 
			
		||||
    if (this.stage.enableCustomHatBlocks) {
 | 
			
		||||
        this.stage.threads.pauseCustomHatBlocks =
 | 
			
		||||
            !this.stage.threads.pauseCustomHatBlocks;
 | 
			
		||||
    } else {
 | 
			
		||||
        this.stage.threads.pauseCustomHatBlocks = false;
 | 
			
		||||
    }
 | 
			
		||||
    this.controlBar.stopButton.refresh();
 | 
			
		||||
    this.stage.fireStopAllEvent();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2719,7 +2741,7 @@ IDE_Morph.prototype.aboutSnap = function () {
 | 
			
		|||
        module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn,
 | 
			
		||||
        world = this.world();
 | 
			
		||||
 | 
			
		||||
    aboutTxt = 'Snap! 4.0.3\nBuild Your Own Blocks\n\n'
 | 
			
		||||
    aboutTxt = 'Snap! 4.0.4\nBuild Your Own Blocks\n\n'
 | 
			
		||||
        + 'Copyright \u24B8 2015 Jens M\u00F6nig and '
 | 
			
		||||
        + 'Brian Harvey\n'
 | 
			
		||||
        + 'jens@moenig.org, bh@cs.berkeley.edu\n\n'
 | 
			
		||||
| 
						 | 
				
			
			@ -3022,6 +3044,51 @@ IDE_Morph.prototype.saveProjectToDisk = function () {
 | 
			
		|||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/* alternative download mechanism, commented out in favor of blob-uris
 | 
			
		||||
IDE_Morph.prototype.saveProjectToDisk = function () {
 | 
			
		||||
    var data;
 | 
			
		||||
    if (Process.prototype.isCatchingErrors) {
 | 
			
		||||
        try {
 | 
			
		||||
            data = this.serializer.serialize(this.stage);
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            this.showMessage('Serialization failed: ' + err);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        data = this.serializer.serialize(this.stage);
 | 
			
		||||
    }
 | 
			
		||||
    this.download(data, this.projectName);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
IDE_Morph.prototype.download = function (data, fileName, mime, suffix) {
 | 
			
		||||
    var link = document.createElement('a'),
 | 
			
		||||
        myself = this;
 | 
			
		||||
 | 
			
		||||
    mime = mime || 'data:text/xml';
 | 
			
		||||
    fileName = fileName || 'download';
 | 
			
		||||
    suffix = suffix || 'xml';
 | 
			
		||||
 | 
			
		||||
    function saveToDisk() {
 | 
			
		||||
        var msg =  myself.showMessage('Downloading to disk...');
 | 
			
		||||
        link.setAttribute('href', mime + ',' + data);
 | 
			
		||||
        link.setAttribute('download', fileName + '.' + suffix);
 | 
			
		||||
        document.body.appendChild(link);
 | 
			
		||||
        link.click();
 | 
			
		||||
        document.body.removeChild(link);
 | 
			
		||||
        msg.destroy();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (Process.prototype.isCatchingErrors) {
 | 
			
		||||
        try {
 | 
			
		||||
            saveToDisk();
 | 
			
		||||
        } catch (err) {
 | 
			
		||||
            this.showMessage('Saving failed: ' + err);
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        saveToDisk();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
IDE_Morph.prototype.exportProject = function (name, plain) {
 | 
			
		||||
    var menu, str;
 | 
			
		||||
    if (name) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3494,10 +3561,11 @@ IDE_Morph.prototype.rawOpenProjectString = function (str) {
 | 
			
		|||
 | 
			
		||||
IDE_Morph.prototype.openCloudDataString = function (str) {
 | 
			
		||||
    var msg,
 | 
			
		||||
        myself = this;
 | 
			
		||||
        myself = this,
 | 
			
		||||
        size = Math.round(str.length / 1024);
 | 
			
		||||
    this.nextSteps([
 | 
			
		||||
        function () {
 | 
			
		||||
            msg = myself.showMessage('Opening project...');
 | 
			
		||||
            msg = myself.showMessage('Opening project\n' + size + ' KB...');
 | 
			
		||||
        },
 | 
			
		||||
        function () {nop(); }, // yield (bug in Chrome)
 | 
			
		||||
        function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -5275,6 +5343,12 @@ ProjectDialogMorph.prototype.rawOpenCloudProject = function (proj) {
 | 
			
		|||
                'getRawProject',
 | 
			
		||||
                function (response) {
 | 
			
		||||
                    SnapCloud.disconnect();
 | 
			
		||||
                    /*
 | 
			
		||||
                    if (myself.world().currentKey === 16) {
 | 
			
		||||
                        myself.ide.download(response);
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    */
 | 
			
		||||
                    myself.ide.source = 'cloud';
 | 
			
		||||
                    myself.ide.droppedText(response);
 | 
			
		||||
                    if (proj.Public === 'true') {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										102
									
								
								history.txt
								
								
								
								
							
							
						
						
									
										102
									
								
								history.txt
								
								
								
								
							| 
						 | 
				
			
			@ -2695,3 +2695,105 @@ end - bulk of 151116
 | 
			
		|||
* Cloud: doubled the number of supported backend slices
 | 
			
		||||
* Cloud, GUI: support new “raw” cloud project services
 | 
			
		||||
 | 
			
		||||
++++++++++++++++++++++++++
 | 
			
		||||
new stuff - bulk of 151215
 | 
			
		||||
++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
151121
 | 
			
		||||
------
 | 
			
		||||
* Threads: Show result bubble when the user clicks on a command script that uses REPORT (You can now click on REPORT and it actually does something)
 | 
			
		||||
 | 
			
		||||
151124
 | 
			
		||||
------
 | 
			
		||||
* Blocks: fix a re-rendering glitch when changing block specs in dev mode
 | 
			
		||||
* Threads: add optional receiver (environment) to invoke() function
 | 
			
		||||
 | 
			
		||||
151125
 | 
			
		||||
------
 | 
			
		||||
* Threads, Objects, GUI, Store: Generic “When” hat block
 | 
			
		||||
* BYOB: fixed a rendering bug when using plain prototype labels
 | 
			
		||||
 | 
			
		||||
151126
 | 
			
		||||
------
 | 
			
		||||
* Threads, Blocks: Performance optimizations (replace “contains” with chained tests)
 | 
			
		||||
* German translation update (for custom hat blocks)
 | 
			
		||||
 | 
			
		||||
151127
 | 
			
		||||
------
 | 
			
		||||
* Blocks, BYOB, Store: new experimental block variables feature
 | 
			
		||||
* BYOB: more prototype label rendering fixes
 | 
			
		||||
 | 
			
		||||
151128
 | 
			
		||||
------
 | 
			
		||||
* BYOB, Store: Fix some bugs related to block vars (zebra coloring etc.)
 | 
			
		||||
 | 
			
		||||
151201
 | 
			
		||||
------
 | 
			
		||||
* BYOB, Blocks: Fix BlockMorph.fullCopy() for block vars
 | 
			
		||||
 | 
			
		||||
151202
 | 
			
		||||
------
 | 
			
		||||
* Threads: Only support block vars for blocks that actually define any, to avoid race conditions among parallel global blocks with the same definition that also access sprite-local variables
 | 
			
		||||
 | 
			
		||||
151207
 | 
			
		||||
------
 | 
			
		||||
* Threads, GUI: Stop button stops / restarts custom hat blocks, green flag starts custom hat blocks
 | 
			
		||||
 | 
			
		||||
151208
 | 
			
		||||
------
 | 
			
		||||
* Objects, Blocks, Threads, GUI, Store, Locale: Automatically enable/disable custom hat blocks when they’re used in a project
 | 
			
		||||
* BYOB: initialize custom block vars on every definition-refresh
 | 
			
		||||
 | 
			
		||||
151209
 | 
			
		||||
------
 | 
			
		||||
* Threads: allow invoke() to operate on both blocks and rings with arguments
 | 
			
		||||
* Blocks: cache reporter slot specs for evaluation performance (30% speedup)
 | 
			
		||||
 | 
			
		||||
151210
 | 
			
		||||
------
 | 
			
		||||
* Store: persist block (instance) vars
 | 
			
		||||
* Threads: only show result bubble on user-clicked scripts if “Report” is in the lexical script (not inside a reporter block definition)
 | 
			
		||||
* Morphic: obey grab threshold when dragging inside scroll frames
 | 
			
		||||
 | 
			
		||||
151211
 | 
			
		||||
------
 | 
			
		||||
* Threads: extend red LENGTH reporter to also work on Text
 | 
			
		||||
* GUI, Objects, Blocks: extend the red stop button to reflect whether custom hat blocks are paused (indicated by a red square instead of the stop sign)
 | 
			
		||||
* Blocks: Tweak C-Slots to better fit inside reporters
 | 
			
		||||
 | 
			
		||||
151212
 | 
			
		||||
------
 | 
			
		||||
* Locale: change English ‘any’ (in “item of”) to ‘random’ because teachers
 | 
			
		||||
 | 
			
		||||
151214
 | 
			
		||||
------
 | 
			
		||||
* Objects: added “fill” primitive to the Pen category
 | 
			
		||||
* Updated German translation
 | 
			
		||||
* GUI: Directly download projects from cloud by holding shift while opening - commented out
 | 
			
		||||
* GUI, Cloud: show size of uploaded / downloaded projects
 | 
			
		||||
* GUI, Cloud: upload size limit of 5 MB - commented out
 | 
			
		||||
 | 
			
		||||
151215
 | 
			
		||||
------
 | 
			
		||||
* snap.html: switch to animation frame scheduling because Chrome sucks sooooo much!!!!
 | 
			
		||||
* GUI: pushed version to 4.0.4
 | 
			
		||||
 | 
			
		||||
++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
v4.0.4 draft features:
 | 
			
		||||
* Show result bubble when the user clicks on a command script that uses REPORT (You can now click on REPORT and it actually does something)
 | 
			
		||||
* New generic “When” hat block, enhances red stop button behavior
 | 
			
		||||
* New block (instance) variables feature (experimental)
 | 
			
		||||
* evaluator performance optimizations
 | 
			
		||||
* Morphic grab-threshold fix for scroll frames
 | 
			
		||||
* fixed several block rendering glitches
 | 
			
		||||
* List category LENGTH reporter now also works on text
 | 
			
		||||
* Changed “any” to “random” (in English only)
 | 
			
		||||
* new FILL primitive in the Pen category
 | 
			
		||||
* switched to animation frame scheduling, please use TURBO for music
 | 
			
		||||
* Updated German translation
 | 
			
		||||
 | 
			
		||||
++++++++++++++++++++++++++
 | 
			
		||||
end - bulk of 151215
 | 
			
		||||
++++++++++++++++++++++++++
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -185,7 +185,7 @@ SnapTranslator.dict.de = {
 | 
			
		|||
    'translator_e-mail':
 | 
			
		||||
        'jens@moenig.org', // optional
 | 
			
		||||
    'last_changed':
 | 
			
		||||
        '2015-10-07', // this, too, will appear in the Translators tab
 | 
			
		||||
        '2015-12-14', // this, too, will appear in the Translators tab
 | 
			
		||||
 | 
			
		||||
    // GUI
 | 
			
		||||
    // control bar:
 | 
			
		||||
| 
						 | 
				
			
			@ -411,6 +411,8 @@ SnapTranslator.dict.de = {
 | 
			
		|||
        'setze Stiftdicke auf %n',
 | 
			
		||||
    'stamp':
 | 
			
		||||
        'stemple',
 | 
			
		||||
    'fill':
 | 
			
		||||
        'male aus',
 | 
			
		||||
 | 
			
		||||
    // control:
 | 
			
		||||
    'when %greenflag clicked':
 | 
			
		||||
| 
						 | 
				
			
			@ -429,6 +431,8 @@ SnapTranslator.dict.de = {
 | 
			
		|||
        'vom Mauszeiger betreten',
 | 
			
		||||
    'mouse-departed':
 | 
			
		||||
        'vom Mauszeiger verlassen',
 | 
			
		||||
    'when %b':
 | 
			
		||||
        'Wenn %b',
 | 
			
		||||
    'when I receive %msgHat':
 | 
			
		||||
        'Wenn ich %msgHat empfange',
 | 
			
		||||
    'broadcast %msg':
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										10
									
								
								locale.js
								
								
								
								
							
							
						
						
									
										10
									
								
								locale.js
								
								
								
								
							| 
						 | 
				
			
			@ -42,7 +42,7 @@
 | 
			
		|||
 | 
			
		||||
/*global modules, contains*/
 | 
			
		||||
 | 
			
		||||
modules.locale = '2015-November-16';
 | 
			
		||||
modules.locale = '2015-December-15';
 | 
			
		||||
 | 
			
		||||
// Global stuff
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +123,11 @@ SnapTranslator.dict.en = {
 | 
			
		|||
    'translator_e-mail':
 | 
			
		||||
        'jens@moenig.org',
 | 
			
		||||
    'last_changed':
 | 
			
		||||
        '2012-10-16',
 | 
			
		||||
        '2015-12-12',
 | 
			
		||||
 | 
			
		||||
    // rewordings in English avoiding having to adjust all other translations
 | 
			
		||||
    'any':
 | 
			
		||||
        'random',
 | 
			
		||||
 | 
			
		||||
    // long strings look-up only
 | 
			
		||||
    'file menu import hint':
 | 
			
		||||
| 
						 | 
				
			
			@ -149,7 +153,7 @@ SnapTranslator.dict.de = {
 | 
			
		|||
    'translator_e-mail':
 | 
			
		||||
        'jens@moenig.org',
 | 
			
		||||
    'last_changed':
 | 
			
		||||
        '2015-10-07'
 | 
			
		||||
        '2015-12-14'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SnapTranslator.dict.it = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										19
									
								
								morphic.js
								
								
								
								
							
							
						
						
									
										19
									
								
								morphic.js
								
								
								
								
							| 
						 | 
				
			
			@ -1052,7 +1052,7 @@
 | 
			
		|||
/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio,
 | 
			
		||||
FileList, getBlurredShadowSupport*/
 | 
			
		||||
 | 
			
		||||
var morphicVersion = '2015-November-16';
 | 
			
		||||
var morphicVersion = '2015-December-15';
 | 
			
		||||
var modules = {}; // keep track of additional loaded modules
 | 
			
		||||
var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -8896,6 +8896,7 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) {
 | 
			
		|||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
    var world = this.root(),
 | 
			
		||||
        hand = world.hand,
 | 
			
		||||
        oldPos = pos,
 | 
			
		||||
        myself = this,
 | 
			
		||||
        deltaX = 0,
 | 
			
		||||
| 
						 | 
				
			
			@ -8904,10 +8905,18 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) {
 | 
			
		|||
 | 
			
		||||
    this.step = function () {
 | 
			
		||||
        var newPos;
 | 
			
		||||
        if (world.hand.mouseButton &&
 | 
			
		||||
                (world.hand.children.length === 0) &&
 | 
			
		||||
                (myself.bounds.containsPoint(world.hand.position()))) {
 | 
			
		||||
            newPos = world.hand.bounds.origin;
 | 
			
		||||
        if (hand.mouseButton &&
 | 
			
		||||
                (hand.children.length === 0) &&
 | 
			
		||||
                (myself.bounds.containsPoint(hand.bounds.origin))) {
 | 
			
		||||
 | 
			
		||||
            if (hand.grabPosition &&
 | 
			
		||||
                (hand.grabPosition.distanceTo(hand.position()) <=
 | 
			
		||||
                    MorphicPreferences.grabThreshold)) {
 | 
			
		||||
                // still within the grab threshold
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            newPos = hand.bounds.origin;
 | 
			
		||||
            deltaX = newPos.x - oldPos.x;
 | 
			
		||||
            if (deltaX !== 0) {
 | 
			
		||||
                myself.scrollX(deltaX);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										109
									
								
								objects.js
								
								
								
								
							
							
						
						
									
										109
									
								
								objects.js
								
								
								
								
							| 
						 | 
				
			
			@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/
 | 
			
		|||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.objects = '2015-November-16';
 | 
			
		||||
modules.objects = '2015-December-15';
 | 
			
		||||
 | 
			
		||||
var SpriteMorph;
 | 
			
		||||
var StageMorph;
 | 
			
		||||
| 
						 | 
				
			
			@ -569,6 +569,12 @@ SpriteMorph.prototype.initBlocks = function () {
 | 
			
		|||
            category: 'pen',
 | 
			
		||||
            spec: 'stamp'
 | 
			
		||||
        },
 | 
			
		||||
        floodFill: {
 | 
			
		||||
            only: SpriteMorph,
 | 
			
		||||
            type: 'command',
 | 
			
		||||
            category: 'pen',
 | 
			
		||||
            spec: 'fill'
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        // Control
 | 
			
		||||
        receiveGo: {
 | 
			
		||||
| 
						 | 
				
			
			@ -602,6 +608,11 @@ SpriteMorph.prototype.initBlocks = function () {
 | 
			
		|||
            category: 'control',
 | 
			
		||||
            spec: 'when I receive %msgHat'
 | 
			
		||||
        },
 | 
			
		||||
        receiveCondition: {
 | 
			
		||||
            type: 'hat',
 | 
			
		||||
            category: 'control',
 | 
			
		||||
            spec: 'when %b'
 | 
			
		||||
        },
 | 
			
		||||
        doBroadcast: {
 | 
			
		||||
            type: 'command',
 | 
			
		||||
            category: 'control',
 | 
			
		||||
| 
						 | 
				
			
			@ -1861,12 +1872,14 @@ SpriteMorph.prototype.blockTemplates = function (category) {
 | 
			
		|||
        blocks.push(block('setSize'));
 | 
			
		||||
        blocks.push('-');
 | 
			
		||||
        blocks.push(block('doStamp'));
 | 
			
		||||
        blocks.push(block('floodFill'));
 | 
			
		||||
 | 
			
		||||
    } else if (cat === 'control') {
 | 
			
		||||
 | 
			
		||||
        blocks.push(block('receiveGo'));
 | 
			
		||||
        blocks.push(block('receiveKey'));
 | 
			
		||||
        blocks.push(block('receiveInteraction'));
 | 
			
		||||
        blocks.push(block('receiveCondition'));
 | 
			
		||||
        blocks.push(block('receiveMessage'));
 | 
			
		||||
        blocks.push('-');
 | 
			
		||||
        blocks.push(block('doBroadcast'));
 | 
			
		||||
| 
						 | 
				
			
			@ -3350,6 +3363,60 @@ SpriteMorph.prototype.drawLine = function (start, dest) {
 | 
			
		|||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.floodFill = function () {
 | 
			
		||||
    var layer = this.parent.penTrails(),
 | 
			
		||||
        width = layer.width,
 | 
			
		||||
        height = layer.height,
 | 
			
		||||
        ctx = layer.getContext('2d'),
 | 
			
		||||
        img = ctx.getImageData(0, 0, width, height),
 | 
			
		||||
        dta = img.data,
 | 
			
		||||
        stack = [
 | 
			
		||||
            ((height / 2) - Math.round(this.yPosition())) * width +
 | 
			
		||||
            Math.round(this.xPosition() + (width / 2))
 | 
			
		||||
        ],
 | 
			
		||||
        current,
 | 
			
		||||
        src;
 | 
			
		||||
 | 
			
		||||
    function read(p) {
 | 
			
		||||
        var d = p * 4;
 | 
			
		||||
        return [dta[d], dta[d + 1], dta[d + 2], dta[d + 3]];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function check(p) {
 | 
			
		||||
        return p[0] === src[0] &&
 | 
			
		||||
            p[1] === src[1] &&
 | 
			
		||||
            p[2] === src[2] &&
 | 
			
		||||
            p[3] === src[3];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    src = read(stack[0]);
 | 
			
		||||
    if (src[0] === Math.round(this.color.r) &&
 | 
			
		||||
            src[1] === Math.round(this.color.g) &&
 | 
			
		||||
            src[2] === Math.round(this.color.b) &&
 | 
			
		||||
            src[3] === Math.round(this.color.a * 255)) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    while (stack.length > 0) {
 | 
			
		||||
        current = stack.pop();
 | 
			
		||||
        if (check(read(current))) {
 | 
			
		||||
            if (current % width > 1) {
 | 
			
		||||
                stack.push(current + 1);
 | 
			
		||||
                stack.push(current - 1);
 | 
			
		||||
            }
 | 
			
		||||
            if (current > 0 && current < height * width) {
 | 
			
		||||
                stack.push(current + width);
 | 
			
		||||
                stack.push(current - width);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        dta[current * 4] = Math.round(this.color.r);
 | 
			
		||||
        dta[current * 4 + 1] = Math.round(this.color.g);
 | 
			
		||||
        dta[current * 4 + 2] = Math.round(this.color.b);
 | 
			
		||||
        dta[current * 4 + 3] = Math.round(this.color.a * 255);
 | 
			
		||||
    }
 | 
			
		||||
    ctx.putImageData(img, 0, 0);
 | 
			
		||||
    this.parent.changed();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// SpriteMorph motion - adjustments due to nesting
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.moveBy = function (delta, justMe) {
 | 
			
		||||
| 
						 | 
				
			
			@ -3693,6 +3760,15 @@ SpriteMorph.prototype.allHatBlocksForInteraction = function (interaction) {
 | 
			
		|||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.allGenericHatBlocks = function () {
 | 
			
		||||
    return this.scripts.children.filter(function (morph) {
 | 
			
		||||
        if (morph.selector) {
 | 
			
		||||
            return morph.selector === 'receiveCondition';
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// SpriteMorph events
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.mouseClickLeft = function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -4674,7 +4750,6 @@ StageMorph.prototype.hiddenPrimitives = {};
 | 
			
		|||
StageMorph.prototype.codeMappings = {};
 | 
			
		||||
StageMorph.prototype.codeHeaders = {};
 | 
			
		||||
StageMorph.prototype.enableCodeMapping = false;
 | 
			
		||||
 | 
			
		||||
StageMorph.prototype.enableInheritance = false;
 | 
			
		||||
 | 
			
		||||
// StageMorph instance creation
 | 
			
		||||
| 
						 | 
				
			
			@ -4695,6 +4770,7 @@ StageMorph.prototype.init = function (globals) {
 | 
			
		|||
    this.sounds = new List();
 | 
			
		||||
    this.version = Date.now(); // for observers
 | 
			
		||||
    this.isFastTracked = false;
 | 
			
		||||
    this.enableCustomHatBlocks = true;
 | 
			
		||||
    this.cloneCount = 0;
 | 
			
		||||
 | 
			
		||||
    this.timerStart = Date.now();
 | 
			
		||||
| 
						 | 
				
			
			@ -5036,6 +5112,9 @@ StageMorph.prototype.step = function () {
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    // manage threads
 | 
			
		||||
    if (this.enableCustomHatBlocks) {
 | 
			
		||||
        this.stepGenericConditions();
 | 
			
		||||
    }
 | 
			
		||||
    if (this.isFastTracked && this.threads.processes.length) {
 | 
			
		||||
        this.children.forEach(function (morph) {
 | 
			
		||||
            if (morph instanceof SpriteMorph) {
 | 
			
		||||
| 
						 | 
				
			
			@ -5072,6 +5151,28 @@ StageMorph.prototype.step = function () {
 | 
			
		|||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
StageMorph.prototype.stepGenericConditions = function (stopAll) {
 | 
			
		||||
    var hats = [],
 | 
			
		||||
        myself = this,
 | 
			
		||||
        ide;
 | 
			
		||||
    this.children.concat(this).forEach(function (morph) {
 | 
			
		||||
        if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
 | 
			
		||||
            hats = hats.concat(morph.allGenericHatBlocks());
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
    if (!hats.length) {
 | 
			
		||||
        this.enableCustomHatBlocks = false;
 | 
			
		||||
        ide = this.parentThatIsA(IDE_Morph);
 | 
			
		||||
        if (ide) {
 | 
			
		||||
            ide.controlBar.stopButton.refresh();
 | 
			
		||||
        }
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    hats.forEach(function (block) {
 | 
			
		||||
        myself.threads.doWhen(block, stopAll);
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
StageMorph.prototype.developersMenu = function () {
 | 
			
		||||
    var myself = this,
 | 
			
		||||
        menu = StageMorph.uber.developersMenu.call(this);
 | 
			
		||||
| 
						 | 
				
			
			@ -5430,6 +5531,7 @@ StageMorph.prototype.blockTemplates = function (category) {
 | 
			
		|||
        blocks.push(block('receiveGo'));
 | 
			
		||||
        blocks.push(block('receiveKey'));
 | 
			
		||||
        blocks.push(block('receiveInteraction'));
 | 
			
		||||
        blocks.push(block('receiveCondition'));
 | 
			
		||||
        blocks.push(block('receiveMessage'));
 | 
			
		||||
        blocks.push('-');
 | 
			
		||||
        blocks.push(block('doBroadcast'));
 | 
			
		||||
| 
						 | 
				
			
			@ -5984,6 +6086,9 @@ StageMorph.prototype.allHatBlocksForKey
 | 
			
		|||
StageMorph.prototype.allHatBlocksForInteraction
 | 
			
		||||
    = SpriteMorph.prototype.allHatBlocksForInteraction;
 | 
			
		||||
 | 
			
		||||
StageMorph.prototype.allGenericHatBlocks
 | 
			
		||||
    = SpriteMorph.prototype.allGenericHatBlocks;
 | 
			
		||||
 | 
			
		||||
// StageMorph events
 | 
			
		||||
 | 
			
		||||
StageMorph.prototype.mouseClickLeft
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,9 +24,10 @@
 | 
			
		|||
				world = new WorldMorph(document.getElementById('world'));
 | 
			
		||||
                world.worldCanvas.focus();
 | 
			
		||||
				new IDE_Morph().openIn(world);
 | 
			
		||||
				setInterval(loop, 1);
 | 
			
		||||
        loop();
 | 
			
		||||
			};
 | 
			
		||||
			function loop() {
 | 
			
		||||
        requestAnimationFrame(loop);
 | 
			
		||||
				world.doOneCycle();
 | 
			
		||||
			}
 | 
			
		||||
		</script>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,37 @@
 | 
			
		|||
<!DOCTYPE html>
 | 
			
		||||
<html>
 | 
			
		||||
	<head>
 | 
			
		||||
		<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 | 
			
		||||
		<title>Snap! Build Your Own Blocks</title>
 | 
			
		||||
		<link rel="shortcut icon" href="favicon.ico">
 | 
			
		||||
		<script type="text/javascript" src="morphic.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="widgets.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="blocks.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="threads.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="objects.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="gui.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="paint.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="lists.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="byob.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="xml.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="store.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="locale.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="cloud.js"></script>
 | 
			
		||||
		<script type="text/javascript" src="sha512.js"></script>
 | 
			
		||||
		<script type="text/javascript">
 | 
			
		||||
			var world;
 | 
			
		||||
			window.onload = function () {
 | 
			
		||||
				world = new WorldMorph(document.getElementById('world'));
 | 
			
		||||
                world.worldCanvas.focus();
 | 
			
		||||
				new IDE_Morph().openIn(world);
 | 
			
		||||
				setInterval(loop, 1);
 | 
			
		||||
			};
 | 
			
		||||
			function loop() {
 | 
			
		||||
				world.doOneCycle();
 | 
			
		||||
			}
 | 
			
		||||
		</script>
 | 
			
		||||
	</head>
 | 
			
		||||
	<body style="margin: 0;">
 | 
			
		||||
		<canvas id="world" tabindex="1" style="position: absolute;" />
 | 
			
		||||
	</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										26
									
								
								store.js
								
								
								
								
							
							
						
						
									
										26
									
								
								store.js
								
								
								
								
							| 
						 | 
				
			
			@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/
 | 
			
		|||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.store = '2015-October-07';
 | 
			
		||||
modules.store = '2015-December-15';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// XML_Serializer ///////////////////////////////////////////////////////
 | 
			
		||||
| 
						 | 
				
			
			@ -792,7 +792,7 @@ SnapSerializer.prototype.loadCustomBlocks = function (
 | 
			
		|||
    // private
 | 
			
		||||
    var myself = this;
 | 
			
		||||
    element.children.forEach(function (child) {
 | 
			
		||||
        var definition, names, inputs, header, code, comment, i;
 | 
			
		||||
        var definition, names, inputs, vars, header, code, comment, i;
 | 
			
		||||
        if (child.tag !== 'block-definition') {
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -836,6 +836,13 @@ SnapSerializer.prototype.loadCustomBlocks = function (
 | 
			
		|||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        vars = child.childNamed('variables');
 | 
			
		||||
        if (vars) {
 | 
			
		||||
            definition.variableNames = myself.loadValue(
 | 
			
		||||
                vars.require('list')
 | 
			
		||||
            ).asArray();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        header = child.childNamed('header');
 | 
			
		||||
        if (header) {
 | 
			
		||||
            definition.codeHeader = header.contents;
 | 
			
		||||
| 
						 | 
				
			
			@ -1047,7 +1054,9 @@ SnapSerializer.prototype.loadBlock = function (model, isReporter) {
 | 
			
		|||
    block.isDraggable = true;
 | 
			
		||||
    inputs = block.inputs();
 | 
			
		||||
    model.children.forEach(function (child, i) {
 | 
			
		||||
        if (child.tag === 'comment') {
 | 
			
		||||
        if (child.tag === 'variables') {
 | 
			
		||||
            this.loadVariables(block.variables, child);
 | 
			
		||||
        } else if (child.tag === 'comment') {
 | 
			
		||||
            block.comment = this.loadComment(child);
 | 
			
		||||
            block.comment.block = block;
 | 
			
		||||
        } else if (child.tag === 'receiver') {
 | 
			
		||||
| 
						 | 
				
			
			@ -1728,11 +1737,16 @@ CustomCommandBlockMorph.prototype.toBlockXML = function (serializer) {
 | 
			
		|||
    var scope = this.definition.isGlobal ? undefined
 | 
			
		||||
        : this.definition.receiver.name;
 | 
			
		||||
    return serializer.format(
 | 
			
		||||
        '<custom-block s="@"%>%%%</custom-block>',
 | 
			
		||||
        '<custom-block s="@"%>%%%%</custom-block>',
 | 
			
		||||
        this.blockSpec,
 | 
			
		||||
        this.definition.isGlobal ?
 | 
			
		||||
                '' : serializer.format(' scope="@"', scope),
 | 
			
		||||
        serializer.store(this.inputs()),
 | 
			
		||||
        this.definition.variableNames.length ?
 | 
			
		||||
                '<variables>' +
 | 
			
		||||
                    this.variables.toXML(serializer) +
 | 
			
		||||
                    '</variables>'
 | 
			
		||||
                        : '',
 | 
			
		||||
        this.comment ? this.comment.toXML(serializer) : '',
 | 
			
		||||
        scope && !this.definition.receiver[serializer.idProperty] ?
 | 
			
		||||
                '<receiver>' +
 | 
			
		||||
| 
						 | 
				
			
			@ -1748,6 +1762,7 @@ CustomReporterBlockMorph.prototype.toBlockXML
 | 
			
		|||
CustomBlockDefinition.prototype.toXML = function (serializer) {
 | 
			
		||||
    var myself = this;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    function encodeScripts(array) {
 | 
			
		||||
        return array.reduce(function (xml, element) {
 | 
			
		||||
            if (element instanceof BlockMorph) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1763,6 +1778,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
 | 
			
		|||
    return serializer.format(
 | 
			
		||||
        '<block-definition s="@" type="@" category="@">' +
 | 
			
		||||
            '%' +
 | 
			
		||||
            (this.variableNames.length ? '<variables>%</variables>' : '@') +
 | 
			
		||||
            '<header>@</header>' +
 | 
			
		||||
            '<code>@</code>' +
 | 
			
		||||
            '<inputs>%</inputs>%%' +
 | 
			
		||||
| 
						 | 
				
			
			@ -1771,6 +1787,8 @@ CustomBlockDefinition.prototype.toXML = function (serializer) {
 | 
			
		|||
        this.type,
 | 
			
		||||
        this.category || 'other',
 | 
			
		||||
        this.comment ? this.comment.toXML(serializer) : '',
 | 
			
		||||
        (this.variableNames.length ?
 | 
			
		||||
                serializer.store(new List(this.variableNames)) : ''),
 | 
			
		||||
        this.codeHeader || '',
 | 
			
		||||
        this.codeMapping || '',
 | 
			
		||||
        Object.keys(this.declarations).reduce(function (xml, decl) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										221
									
								
								threads.js
								
								
								
								
							
							
						
						
									
										221
									
								
								threads.js
								
								
								
								
							| 
						 | 
				
			
			@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
 | 
			
		|||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.threads = '2015-November-16';
 | 
			
		||||
modules.threads = '2015-December-15';
 | 
			
		||||
 | 
			
		||||
var ThreadManager;
 | 
			
		||||
var Process;
 | 
			
		||||
| 
						 | 
				
			
			@ -128,24 +128,67 @@ function snapEquals(a, b) {
 | 
			
		|||
    return x === y;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function invoke(block, timeout) {
 | 
			
		||||
    // exectue the given block synchronously, i.e. without yielding.
 | 
			
		||||
    // if a timeout (in milliseconds) is specified, abort execution
 | 
			
		||||
function invoke(
 | 
			
		||||
    action, // a BlockMorph or a Context, a reified ("ringified") block
 | 
			
		||||
    contextArgs, // optional List of arguments for the context, or null
 | 
			
		||||
    receiver, // optional sprite or environment
 | 
			
		||||
    timeout, // msecs
 | 
			
		||||
    timeoutErrorMsg, // string
 | 
			
		||||
    suppressErrors // bool
 | 
			
		||||
) {
 | 
			
		||||
    // execute the given block or context synchronously without yielding.
 | 
			
		||||
    // Apply context (not a block) to a list of optional arguments.
 | 
			
		||||
    // Receiver (sprite, stage or  environment), timeout etc. are optional.
 | 
			
		||||
    // If a timeout (in milliseconds) is specified, abort execution
 | 
			
		||||
    // after the timeout has been reached and throw an error.
 | 
			
		||||
    // For debugging purposes only.
 | 
			
		||||
    // SuppressErrors (bool) if non-timeout errors occurring in the
 | 
			
		||||
    // block are handled elsewhere.
 | 
			
		||||
    // This is highly experimental.
 | 
			
		||||
    // Caution: Kids, do not try this at home!
 | 
			
		||||
    // use ThreadManager::startProcess instead
 | 
			
		||||
    var proc = new Process(block),
 | 
			
		||||
        startTime = Date.now();
 | 
			
		||||
    while (proc.isRunning()) {
 | 
			
		||||
        if (timeout && ((Date.now() - startTime) > timeout)) {
 | 
			
		||||
            throw (new Error("a synchronous Snap! script has timed out"));
 | 
			
		||||
    // Use ThreadManager::startProcess with a callback instead
 | 
			
		||||
 | 
			
		||||
    var proc = new Process(),
 | 
			
		||||
        deadline = (timeout ? Date.now() + timeout : null),
 | 
			
		||||
        rcvr;
 | 
			
		||||
 | 
			
		||||
    if (action instanceof Context) {
 | 
			
		||||
        if (receiver) {
 | 
			
		||||
            action = proc.reportContextFor(receiver);
 | 
			
		||||
        }
 | 
			
		||||
        proc.runStep();
 | 
			
		||||
        proc.initializeFor(action, contextArgs || new List());
 | 
			
		||||
    } else if (action instanceof BlockMorph) {
 | 
			
		||||
        proc.topBlock = action;
 | 
			
		||||
        rcvr = receiver || action.receiver();
 | 
			
		||||
        if (rcvr) {
 | 
			
		||||
            proc.homeContext = new Context();
 | 
			
		||||
            proc.homeContext.receiver = rcvr;
 | 
			
		||||
            if (rcvr.variables) {
 | 
			
		||||
                proc.homeContext.variables.parentFrame = rcvr.variables;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        proc.context = new Context(
 | 
			
		||||
            null,
 | 
			
		||||
            action.blockSequence(),
 | 
			
		||||
            proc.homeContext
 | 
			
		||||
        );
 | 
			
		||||
    } else {
 | 
			
		||||
        throw new Error('expecting a block or ring but getting ' + action);
 | 
			
		||||
    }
 | 
			
		||||
    if (block instanceof ReporterBlockMorph) {
 | 
			
		||||
        return proc.homeContext.inputs[0];
 | 
			
		||||
    if (suppressErrors) {
 | 
			
		||||
        proc.isCatchingErrors = false;
 | 
			
		||||
    }
 | 
			
		||||
    while (proc.isRunning()) {
 | 
			
		||||
        if (deadline && (Date.now() > deadline)) {
 | 
			
		||||
            throw (new Error(
 | 
			
		||||
                localize(
 | 
			
		||||
                    timeoutErrorMsg ||
 | 
			
		||||
                        "a synchronous Snap! script has timed out")
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        proc.runStep(deadline);
 | 
			
		||||
    }
 | 
			
		||||
    return proc.homeContext.inputs[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ThreadManager ///////////////////////////////////////////////////////
 | 
			
		||||
| 
						 | 
				
			
			@ -154,12 +197,14 @@ function ThreadManager() {
 | 
			
		|||
    this.processes = [];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ThreadManager.prototype.pauseCustomHatBlocks = false;
 | 
			
		||||
 | 
			
		||||
ThreadManager.prototype.toggleProcess = function (block) {
 | 
			
		||||
    var active = this.findProcess(block);
 | 
			
		||||
    if (active) {
 | 
			
		||||
        active.stop();
 | 
			
		||||
    } else {
 | 
			
		||||
        return this.startProcess(block);
 | 
			
		||||
        return this.startProcess(block, null, null, null, true);
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -167,7 +212,8 @@ ThreadManager.prototype.startProcess = function (
 | 
			
		|||
    block,
 | 
			
		||||
    isThreadSafe,
 | 
			
		||||
    exportResult,
 | 
			
		||||
    callback
 | 
			
		||||
    callback,
 | 
			
		||||
    isClicked
 | 
			
		||||
) {
 | 
			
		||||
    var active = this.findProcess(block),
 | 
			
		||||
        top = block.topBlock(),
 | 
			
		||||
| 
						 | 
				
			
			@ -181,6 +227,7 @@ ThreadManager.prototype.startProcess = function (
 | 
			
		|||
    }
 | 
			
		||||
    newProc = new Process(block.topBlock(), callback);
 | 
			
		||||
    newProc.exportResult = exportResult;
 | 
			
		||||
    newProc.isClicked = isClicked || false;
 | 
			
		||||
    if (!newProc.homeContext.receiver.isClone) {
 | 
			
		||||
        top.addHighlight();
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -267,7 +314,7 @@ ThreadManager.prototype.removeTerminatedProcesses = function () {
 | 
			
		|||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (proc.topBlock instanceof ReporterBlockMorph) {
 | 
			
		||||
            if (proc.topBlock instanceof ReporterBlockMorph || proc.isShowingResult) {
 | 
			
		||||
                if (proc.onComplete instanceof Function) {
 | 
			
		||||
                    proc.onComplete(proc.homeContext.inputs[0]);
 | 
			
		||||
                } else {
 | 
			
		||||
| 
						 | 
				
			
			@ -303,6 +350,38 @@ ThreadManager.prototype.findProcess = function (block) {
 | 
			
		|||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
ThreadManager.prototype.doWhen = function (block, stopIt) {
 | 
			
		||||
    if (this.pauseCustomHatBlocks) {return; }
 | 
			
		||||
    var pred = block.inputs()[0];
 | 
			
		||||
    if (block.removeHighlight()) {
 | 
			
		||||
        block.world().hand.destroyTemporaries();
 | 
			
		||||
    }
 | 
			
		||||
    if (stopIt) {return; }
 | 
			
		||||
    if ((!block) ||
 | 
			
		||||
        !(pred instanceof ReporterBlockMorph) ||
 | 
			
		||||
        this.findProcess(block)
 | 
			
		||||
    ) {return; }
 | 
			
		||||
    try {
 | 
			
		||||
        if (invoke(
 | 
			
		||||
            pred,
 | 
			
		||||
            null,
 | 
			
		||||
            null,
 | 
			
		||||
            20,
 | 
			
		||||
            'the predicate takes\ntoo long for a\ncustom hat block',
 | 
			
		||||
            true // suppress errors => handle them right here instead
 | 
			
		||||
        ) === true) {
 | 
			
		||||
            this.startProcess(block);
 | 
			
		||||
        }
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
        block.addErrorHighlight();
 | 
			
		||||
        block.showBubble(
 | 
			
		||||
            error.name
 | 
			
		||||
            + '\n'
 | 
			
		||||
            + error.message
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Process /////////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
| 
						 | 
				
			
			@ -349,6 +428,10 @@ ThreadManager.prototype.findProcess = function (block) {
 | 
			
		|||
    httpRequest         active instance of an HttpRequest or null
 | 
			
		||||
    pauseOffset         msecs between the start of an interpolated operation
 | 
			
		||||
                        and when the process was paused
 | 
			
		||||
    isClicked           boolean flag indicating whether the process was
 | 
			
		||||
                        initiated by a user-click on a block
 | 
			
		||||
    isShowingResult     boolean flag indicating whether a "report" command
 | 
			
		||||
                        has been executed in a user-clicked process
 | 
			
		||||
    exportResult        boolean flag indicating whether a picture of the top
 | 
			
		||||
                        block along with the result bubble shoud be exported
 | 
			
		||||
    onComplete          an optional callback function to be executed when
 | 
			
		||||
| 
						 | 
				
			
			@ -369,6 +452,8 @@ function Process(topBlock, onComplete) {
 | 
			
		|||
    this.readyToYield = false;
 | 
			
		||||
    this.readyToTerminate = false;
 | 
			
		||||
    this.isDead = false;
 | 
			
		||||
    this.isClicked = false;
 | 
			
		||||
    this.isShowingResult = false;
 | 
			
		||||
    this.errorFlag = false;
 | 
			
		||||
    this.context = null;
 | 
			
		||||
    this.homeContext = new Context();
 | 
			
		||||
| 
						 | 
				
			
			@ -404,7 +489,7 @@ Process.prototype.isRunning = function () {
 | 
			
		|||
 | 
			
		||||
// Process entry points
 | 
			
		||||
 | 
			
		||||
Process.prototype.runStep = function () {
 | 
			
		||||
Process.prototype.runStep = function (deadline) {
 | 
			
		||||
    // a step is an an uninterruptable 'atom', it can consist
 | 
			
		||||
    // of several contexts, even of several blocks
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -422,6 +507,14 @@ Process.prototype.runStep = function () {
 | 
			
		|||
        if (this.isPaused) {
 | 
			
		||||
            return this.pauseStep();
 | 
			
		||||
        }
 | 
			
		||||
        if (deadline && (Date.now() > deadline)) {
 | 
			
		||||
            if (this.isAtomic &&
 | 
			
		||||
                    this.homeContext.receiver &&
 | 
			
		||||
                    this.homeContext.receiver.endWarp) {
 | 
			
		||||
                this.homeContext.receiver.endWarp();
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.evaluateContext();
 | 
			
		||||
    }
 | 
			
		||||
    this.lastYield = Date.now();
 | 
			
		||||
| 
						 | 
				
			
			@ -505,9 +598,12 @@ Process.prototype.evaluateContext = function () {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.evaluateBlock = function (block, argCount) {
 | 
			
		||||
    var selector = block.selector;
 | 
			
		||||
    // check for special forms
 | 
			
		||||
    if (contains(['reportOr', 'reportAnd', 'doReport'], block.selector)) {
 | 
			
		||||
        return this[block.selector](block);
 | 
			
		||||
    if (selector === 'reportOr' ||
 | 
			
		||||
            selector ===  'reportAnd' ||
 | 
			
		||||
            selector === 'doReport') {
 | 
			
		||||
        return this[selector](block);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // first evaluate all inputs, then apply the primitive
 | 
			
		||||
| 
						 | 
				
			
			@ -517,13 +613,13 @@ Process.prototype.evaluateBlock = function (block, argCount) {
 | 
			
		|||
    if (argCount > inputs.length) {
 | 
			
		||||
        this.evaluateNextInput(block);
 | 
			
		||||
    } else {
 | 
			
		||||
        if (this[block.selector]) {
 | 
			
		||||
        if (this[selector]) {
 | 
			
		||||
            rcvr = this;
 | 
			
		||||
        }
 | 
			
		||||
        if (this.isCatchingErrors) {
 | 
			
		||||
            try {
 | 
			
		||||
                this.returnValueToParentContext(
 | 
			
		||||
                    rcvr[block.selector].apply(rcvr, inputs)
 | 
			
		||||
                    rcvr[selector].apply(rcvr, inputs)
 | 
			
		||||
                );
 | 
			
		||||
                this.popContext();
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
| 
						 | 
				
			
			@ -531,7 +627,7 @@ Process.prototype.evaluateBlock = function (block, argCount) {
 | 
			
		|||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            this.returnValueToParentContext(
 | 
			
		||||
                rcvr[block.selector].apply(rcvr, inputs)
 | 
			
		||||
                rcvr[selector].apply(rcvr, inputs)
 | 
			
		||||
            );
 | 
			
		||||
            this.popContext();
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -574,6 +670,9 @@ Process.prototype.reportAnd = function (block) {
 | 
			
		|||
 | 
			
		||||
Process.prototype.doReport = function (block) {
 | 
			
		||||
    var outer = this.context.outerContext;
 | 
			
		||||
    if (this.isClicked && (block.topBlock() === this.topBlock)) {
 | 
			
		||||
        this.isShowingResult = true;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.context.expression.partOfCustomCommand) {
 | 
			
		||||
        this.doStopCustomBlock();
 | 
			
		||||
        this.popContext();
 | 
			
		||||
| 
						 | 
				
			
			@ -655,10 +754,9 @@ Process.prototype.evaluateInput = function (input) {
 | 
			
		|||
    } else {
 | 
			
		||||
        ans = input.evaluate();
 | 
			
		||||
        if (ans) {
 | 
			
		||||
            if (contains(
 | 
			
		||||
                    [CommandSlotMorph, ReporterSlotMorph],
 | 
			
		||||
                    input.constructor
 | 
			
		||||
                ) || (input instanceof CSlotMorph &&
 | 
			
		||||
            if (input.constructor === CommandSlotMorph ||
 | 
			
		||||
                    input.constructor === ReporterSlotMorph ||
 | 
			
		||||
                    (input instanceof CSlotMorph &&
 | 
			
		||||
                        (!input.isStatic || input.isLambda))) {
 | 
			
		||||
                // I know, this still needs yet to be done right....
 | 
			
		||||
                ans = this.reify(ans, new List());
 | 
			
		||||
| 
						 | 
				
			
			@ -718,6 +816,7 @@ Process.prototype.evaluateNextInput = function (element) {
 | 
			
		|||
    var nxt = this.context.inputs.length,
 | 
			
		||||
        args = element.inputs(),
 | 
			
		||||
        exp = args[nxt],
 | 
			
		||||
        sel = this.context.expression.selector,
 | 
			
		||||
        outer = this.context.outerContext; // for tail call elimination
 | 
			
		||||
 | 
			
		||||
    if (exp.isUnevaluated) {
 | 
			
		||||
| 
						 | 
				
			
			@ -729,8 +828,7 @@ Process.prototype.evaluateNextInput = function (element) {
 | 
			
		|||
                THE SCRIPT), because those allow for additional
 | 
			
		||||
                explicit parameter bindings.
 | 
			
		||||
            */
 | 
			
		||||
            if (contains(['reify', 'reportScript'],
 | 
			
		||||
                    this.context.expression.selector)) {
 | 
			
		||||
            if (sel === 'reify' || sel === 'reportScript') {
 | 
			
		||||
                this.context.addInput(exp);
 | 
			
		||||
            } else {
 | 
			
		||||
                this.context.addInput(this.reify(exp, new List()));
 | 
			
		||||
| 
						 | 
				
			
			@ -944,6 +1042,15 @@ Process.prototype.evaluate = function (
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.fork = function (context, args) {
 | 
			
		||||
    var proc = new Process(),
 | 
			
		||||
        stage = this.homeContext.receiver.parentThatIsA(StageMorph);
 | 
			
		||||
    proc.initializeFor(context, args);
 | 
			
		||||
    proc.pushContext('doYield');
 | 
			
		||||
    stage.threads.processes.push(proc);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.initializeFor = function (context, args) {
 | 
			
		||||
    // used by Process.fork() and global invoke()
 | 
			
		||||
    if (context.isContinuation) {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
            'continuations cannot be forked'
 | 
			
		||||
| 
						 | 
				
			
			@ -961,8 +1068,7 @@ Process.prototype.fork = function (context, args) {
 | 
			
		|||
        parms = args.asArray(),
 | 
			
		||||
        i,
 | 
			
		||||
        value,
 | 
			
		||||
        stage = this.homeContext.receiver.parentThatIsA(StageMorph),
 | 
			
		||||
        proc = new Process();
 | 
			
		||||
        exit;
 | 
			
		||||
 | 
			
		||||
    // assign parameters if any were passed
 | 
			
		||||
    if (parms.length > 0) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1008,13 +1114,24 @@ Process.prototype.fork = function (context, args) {
 | 
			
		|||
 | 
			
		||||
    if (runnable.expression instanceof CommandBlockMorph) {
 | 
			
		||||
        runnable.expression = runnable.expression.blockSequence();
 | 
			
		||||
 | 
			
		||||
        // insert a tagged exit context
 | 
			
		||||
        // which "report" can catch later
 | 
			
		||||
        // needed for invoke() situations
 | 
			
		||||
        exit = new Context(
 | 
			
		||||
            runnable.parentContext,
 | 
			
		||||
            'expectReport',
 | 
			
		||||
            outer,
 | 
			
		||||
            outer.receiver
 | 
			
		||||
        );
 | 
			
		||||
        exit.tag = 'exit';
 | 
			
		||||
        runnable.parentContext = exit;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    proc.homeContext = context.outerContext;
 | 
			
		||||
    proc.topBlock = context.expression;
 | 
			
		||||
    proc.context = runnable;
 | 
			
		||||
    proc.pushContext('doYield');
 | 
			
		||||
    stage.threads.processes.push(proc);
 | 
			
		||||
    this.homeContext = new Context(); // context.outerContext;
 | 
			
		||||
    this.homeContext.receiver = context.outerContext.receiver;
 | 
			
		||||
    this.topBlock = context.expression;
 | 
			
		||||
    this.context = runnable;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// Process stopping blocks primitives
 | 
			
		||||
| 
						 | 
				
			
			@ -1091,8 +1208,21 @@ Process.prototype.evaluateCustomBlock = function () {
 | 
			
		|||
    this.procedureCount += 1;
 | 
			
		||||
    outer = new Context();
 | 
			
		||||
    outer.receiver = this.context.receiver;
 | 
			
		||||
    outer.variables.parentFrame = outer.receiver ?
 | 
			
		||||
            outer.receiver.variables : null;
 | 
			
		||||
 | 
			
		||||
    outer.variables.parentFrame = this.context.expression.variables;
 | 
			
		||||
 | 
			
		||||
    // block (instance) var support, experimental:
 | 
			
		||||
    // only splice in block vars if any are defined, because block vars
 | 
			
		||||
    // can cause race conditions in global block definitions that
 | 
			
		||||
    // access sprite-local variables at the same time.
 | 
			
		||||
    if (this.context.expression.definition.variableNames.length) {
 | 
			
		||||
        this.context.expression.variables.parentFrame = outer.receiver ?
 | 
			
		||||
                outer.receiver.variables : null;
 | 
			
		||||
    } else {
 | 
			
		||||
        // original code without block variables:
 | 
			
		||||
        outer.variables.parentFrame = outer.receiver ?
 | 
			
		||||
                outer.receiver.variables : null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    runnable = new Context(
 | 
			
		||||
        this.context.parentContext,
 | 
			
		||||
| 
						 | 
				
			
			@ -1420,7 +1550,10 @@ Process.prototype.reportListItem = function (index, list) {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.reportListLength = function (list) {
 | 
			
		||||
    return list.length();
 | 
			
		||||
    if (list instanceof List) {
 | 
			
		||||
        return list.length();
 | 
			
		||||
    }
 | 
			
		||||
    return list.length; // catch a common student error
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.reportListContainsItem = function (list, element) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2110,10 +2243,12 @@ Process.prototype.reportIsIdentical = function (a, b) {
 | 
			
		|||
 | 
			
		||||
Process.prototype.isImmutable = function (obj) {
 | 
			
		||||
    // private
 | 
			
		||||
    return contains(
 | 
			
		||||
        ['nothing', 'Boolean', 'text', 'number', 'undefined'],
 | 
			
		||||
        this.reportTypeOf(obj)
 | 
			
		||||
    );
 | 
			
		||||
    var type = this.reportTypeOf(obj);
 | 
			
		||||
    return type === 'nothing' ||
 | 
			
		||||
        type === 'Boolean' ||
 | 
			
		||||
        type === 'text' ||
 | 
			
		||||
        type === 'number' ||
 | 
			
		||||
        type === 'undefined';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.reportTrue = function () {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue