kopia lustrzana https://github.com/backface/turtlestitch
				
				
				
			first-class sprites
							rodzic
							
								
									94b63dde74
								
							
						
					
					
						commit
						ed3b56a610
					
				| 
						 | 
				
			
			@ -137,19 +137,19 @@
 | 
			
		|||
*/
 | 
			
		||||
 | 
			
		||||
/*global Array, BoxMorph,
 | 
			
		||||
Color, ColorPaletteMorph, CursorMorph, FrameMorph, Function, HandleMorph,
 | 
			
		||||
Math, MenuMorph, Morph, MorphicPreferences, Object, Point, ScrollFrameMorph,
 | 
			
		||||
ShadowMorph, String, StringMorph, TextMorph, WorldMorph, contains, degrees,
 | 
			
		||||
detect, document, getDocumentPositionOf, isNaN, isString, newCanvas, nop,
 | 
			
		||||
parseFloat, radians, useBlurredShadows, SpeechBubbleMorph, modules,
 | 
			
		||||
StageMorph, fontHeight, TableFrameMorph, SpriteMorph, Context,
 | 
			
		||||
ListWatcherMorph, CellMorph, DialogBoxMorph, BlockInputFragmentMorph,
 | 
			
		||||
PrototypeHatBlockMorph, Costume, IDE_Morph, BlockDialogMorph,
 | 
			
		||||
BlockEditorMorph, localize, isNil*/
 | 
			
		||||
Color, ColorPaletteMorph, FrameMorph, Function, HandleMorph, Math, MenuMorph,
 | 
			
		||||
Morph, MorphicPreferences, Object, Point, ScrollFrameMorph, ShadowMorph,
 | 
			
		||||
String, StringMorph, TextMorph, WorldMorph, contains, degrees, detect,
 | 
			
		||||
document, getDocumentPositionOf, isNaN, isString, newCanvas, nop, parseFloat,
 | 
			
		||||
radians, useBlurredShadows, SpeechBubbleMorph, modules, StageMorph,
 | 
			
		||||
fontHeight, TableFrameMorph, SpriteMorph, Context, ListWatcherMorph,
 | 
			
		||||
CellMorph, DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph,
 | 
			
		||||
Costume, IDE_Morph, BlockDialogMorph, BlockEditorMorph, localize, isNil,
 | 
			
		||||
isSnapObject*/
 | 
			
		||||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.blocks = '2016-March-16';
 | 
			
		||||
modules.blocks = '2016-May-02';
 | 
			
		||||
 | 
			
		||||
var SyntaxElementMorph;
 | 
			
		||||
var BlockMorph;
 | 
			
		||||
| 
						 | 
				
			
			@ -1031,6 +1031,15 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
 | 
			
		|||
                true
 | 
			
		||||
            );
 | 
			
		||||
            break;
 | 
			
		||||
        case '%get': // sprites, parts, speciment, clones
 | 
			
		||||
            part = new InputSlotMorph(
 | 
			
		||||
                null,
 | 
			
		||||
                false,
 | 
			
		||||
                'gettablesMenu',
 | 
			
		||||
                true
 | 
			
		||||
            );
 | 
			
		||||
            part.isStatic = true;
 | 
			
		||||
            break;
 | 
			
		||||
        case '%cst':
 | 
			
		||||
            part = new InputSlotMorph(
 | 
			
		||||
                null,
 | 
			
		||||
| 
						 | 
				
			
			@ -1049,7 +1058,7 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
 | 
			
		|||
                    comic: ['comic'],
 | 
			
		||||
                    duplicate: ['duplicate'],
 | 
			
		||||
                    confetti: ['confetti']
 | 
			
		||||
                    },
 | 
			
		||||
                },
 | 
			
		||||
                true
 | 
			
		||||
            );
 | 
			
		||||
            part.setContents(['ghost']);
 | 
			
		||||
| 
						 | 
				
			
			@ -1215,17 +1224,7 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
 | 
			
		|||
            part = new InputSlotMorph(
 | 
			
		||||
                null,
 | 
			
		||||
                false,
 | 
			
		||||
                {
 | 
			
		||||
                    number : ['number'],
 | 
			
		||||
                    text : ['text'],
 | 
			
		||||
                    Boolean : ['Boolean'],
 | 
			
		||||
                    list : ['list'],
 | 
			
		||||
                    command : ['command'],
 | 
			
		||||
                    reporter : ['reporter'],
 | 
			
		||||
                    predicate : ['predicate']
 | 
			
		||||
                    // ring : 'ring'
 | 
			
		||||
                    // object : 'object'
 | 
			
		||||
                },
 | 
			
		||||
                'typesMenu',
 | 
			
		||||
                true
 | 
			
		||||
            );
 | 
			
		||||
            part.setContents(['number']);
 | 
			
		||||
| 
						 | 
				
			
			@ -1789,11 +1788,28 @@ SyntaxElementMorph.prototype.showBubble = function (value, exportPic) {
 | 
			
		|||
        morphToShow.expand(this.parentThatIsA(ScrollFrameMorph).extent());
 | 
			
		||||
        isClickable = true;
 | 
			
		||||
    } else if (value instanceof Morph) {
 | 
			
		||||
        img = value.fullImage();
 | 
			
		||||
        morphToShow = new Morph();
 | 
			
		||||
        morphToShow.silentSetWidth(img.width);
 | 
			
		||||
        morphToShow.silentSetHeight(img.height);
 | 
			
		||||
        morphToShow.image = img;
 | 
			
		||||
        if (isSnapObject(value)) {
 | 
			
		||||
            img = value.thumbnail(new Point(40, 40));
 | 
			
		||||
            morphToShow = new Morph();
 | 
			
		||||
            morphToShow.silentSetWidth(img.width);
 | 
			
		||||
            morphToShow.silentSetHeight(img.height);
 | 
			
		||||
            morphToShow.image = img;
 | 
			
		||||
            morphToShow.version = value.version;
 | 
			
		||||
            morphToShow.step = function () {
 | 
			
		||||
                if (this.version !== value.version) {
 | 
			
		||||
                    img = value.thumbnail(new Point(40, 40));
 | 
			
		||||
                    this.image = img;
 | 
			
		||||
                    this.version = value.version;
 | 
			
		||||
                    this.changed();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            img = value.fullImage();
 | 
			
		||||
            morphToShow = new Morph();
 | 
			
		||||
            morphToShow.silentSetWidth(img.width);
 | 
			
		||||
            morphToShow.silentSetHeight(img.height);
 | 
			
		||||
            morphToShow.image = img;
 | 
			
		||||
        }
 | 
			
		||||
    } else if (value instanceof Costume) {
 | 
			
		||||
        img = value.thumbnail(new Point(40, 40));
 | 
			
		||||
        morphToShow = new Morph();
 | 
			
		||||
| 
						 | 
				
			
			@ -3245,13 +3261,14 @@ BlockMorph.prototype.fullCopy = function () {
 | 
			
		|||
    ans.allChildren().filter(function (block) {
 | 
			
		||||
        if (block instanceof SyntaxElementMorph) {
 | 
			
		||||
            block.cachedInputs = null;
 | 
			
		||||
            if (block instanceof InputSlotMorph) {
 | 
			
		||||
                block.contents().clearSelection();
 | 
			
		||||
            } else if (block.definition) {
 | 
			
		||||
//            if (block instanceof InputSlotMorph) {
 | 
			
		||||
//                block.contents().clearSelection();
 | 
			
		||||
//            } else
 | 
			
		||||
            if (block.definition) {
 | 
			
		||||
                block.initializeVariables();
 | 
			
		||||
            }
 | 
			
		||||
        } else if (block instanceof CursorMorph) {
 | 
			
		||||
            block.destroy();
 | 
			
		||||
//        } else if (block instanceof CursorMorph) {
 | 
			
		||||
//            block.destroy();
 | 
			
		||||
        }
 | 
			
		||||
        return !isNil(block.comment);
 | 
			
		||||
    }).forEach(function (block) {
 | 
			
		||||
| 
						 | 
				
			
			@ -7023,7 +7040,7 @@ InputSlotMorph.prototype.messagesMenu = function () {
 | 
			
		|||
        allNames = [];
 | 
			
		||||
 | 
			
		||||
    stage.children.concat(stage).forEach(function (morph) {
 | 
			
		||||
        if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
 | 
			
		||||
        if (isSnapObject(morph)) {
 | 
			
		||||
            allNames = allNames.concat(morph.allMessageNames());
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -7057,7 +7074,7 @@ InputSlotMorph.prototype.messagesReceivedMenu = function () {
 | 
			
		|||
        allNames = [];
 | 
			
		||||
 | 
			
		||||
    stage.children.concat(stage).forEach(function (morph) {
 | 
			
		||||
        if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
 | 
			
		||||
        if (isSnapObject(morph)) {
 | 
			
		||||
            allNames = allNames.concat(morph.allMessageNames());
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -7175,6 +7192,48 @@ InputSlotMorph.prototype.objectsMenu = function () {
 | 
			
		|||
    return dict;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
InputSlotMorph.prototype.typesMenu = function () {
 | 
			
		||||
    var dict = {
 | 
			
		||||
        number : ['number'],
 | 
			
		||||
        text : ['text'],
 | 
			
		||||
        Boolean : ['Boolean'],
 | 
			
		||||
        list : ['list']
 | 
			
		||||
    };
 | 
			
		||||
    if (SpriteMorph.prototype.enableFirstClass) {
 | 
			
		||||
        dict.sprite = ['sprite'];
 | 
			
		||||
    }
 | 
			
		||||
    dict.command = ['command'];
 | 
			
		||||
    dict.reporter = ['reporter'];
 | 
			
		||||
    dict.predicate = ['predicate'];
 | 
			
		||||
    return dict;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
InputSlotMorph.prototype.gettablesMenu = function () {
 | 
			
		||||
    var dict = {
 | 
			
		||||
        neighbors : ['neighbors'],
 | 
			
		||||
        self : ['self'],
 | 
			
		||||
        'other sprites' : ['other sprites'],
 | 
			
		||||
        clones : ['clones'],
 | 
			
		||||
        'other clones' : ['other clones']
 | 
			
		||||
    };
 | 
			
		||||
    if (SpriteMorph.prototype.enableNesting) {
 | 
			
		||||
        dict.parts = ['parts'];
 | 
			
		||||
        dict.anchor = ['anchor'];
 | 
			
		||||
    }
 | 
			
		||||
    dict.stage = ['stage'];
 | 
			
		||||
    if (StageMorph.prototype.enableInheritance) {
 | 
			
		||||
        dict.children = ['children'];
 | 
			
		||||
        dict.parent = ['parent'];
 | 
			
		||||
    }
 | 
			
		||||
    dict.name = ['name'];
 | 
			
		||||
    dict['dangling?'] = ['dangling?'];
 | 
			
		||||
    dict['rotation x'] = ['rotation x'];
 | 
			
		||||
    dict['rotation y'] = ['rotation y'];
 | 
			
		||||
    dict['center x'] = ['center x'];
 | 
			
		||||
    dict['center y'] = ['center y'];
 | 
			
		||||
    return dict;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
InputSlotMorph.prototype.attributesMenu = function () {
 | 
			
		||||
    var block = this.parentThatIsA(BlockMorph),
 | 
			
		||||
        objName = block.inputs()[1].evaluate(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -65,11 +65,12 @@ BlockExportDialogMorph, BlockImportDialogMorph, SnapTranslator, localize,
 | 
			
		|||
List, InputSlotMorph, SnapCloud, Uint8Array, HandleMorph, SVG_Costume,
 | 
			
		||||
fontHeight, hex_sha512, sb, CommentMorph, CommandBlockMorph,
 | 
			
		||||
BlockLabelPlaceHolderMorph, Audio, SpeechBubbleMorph, ScriptFocusMorph,
 | 
			
		||||
XML_Element, WatcherMorph, BlockRemovalDialogMorph, saveAs, TableMorph*/
 | 
			
		||||
XML_Element, WatcherMorph, BlockRemovalDialogMorph, saveAs, TableMorph,
 | 
			
		||||
isSnapObject*/
 | 
			
		||||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.gui = '2016-March-16';
 | 
			
		||||
modules.gui = '2016-May-02';
 | 
			
		||||
 | 
			
		||||
// Declarations
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1399,8 +1400,10 @@ IDE_Morph.prototype.createCorral = function () {
 | 
			
		|||
    frame.alpha = 0;
 | 
			
		||||
 | 
			
		||||
    this.sprites.asArray().forEach(function (morph) {
 | 
			
		||||
        template = new SpriteIconMorph(morph, template);
 | 
			
		||||
        frame.contents.add(template);
 | 
			
		||||
        if (!morph.isClone) {
 | 
			
		||||
            template = new SpriteIconMorph(morph, template);
 | 
			
		||||
            frame.contents.add(template);
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.corral.frame = frame;
 | 
			
		||||
| 
						 | 
				
			
			@ -1988,6 +1991,7 @@ IDE_Morph.prototype.removeSprite = function (sprite) {
 | 
			
		|||
    sprite.parts.forEach(function (part) {myself.removeSprite(part); });
 | 
			
		||||
    idx = this.sprites.asArray().indexOf(sprite) + 1;
 | 
			
		||||
    this.stage.threads.stopAllForReceiver(sprite);
 | 
			
		||||
    sprite.corpsify();
 | 
			
		||||
    sprite.destroy();
 | 
			
		||||
    this.stage.watchers().forEach(function (watcher) {
 | 
			
		||||
        if (watcher.object() === sprite) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2421,6 +2425,20 @@ IDE_Morph.prototype.settingsMenu = function () {
 | 
			
		|||
        'check to enable\nsprite composition',
 | 
			
		||||
        true
 | 
			
		||||
    );
 | 
			
		||||
    addPreference(
 | 
			
		||||
        'First-Class Sprites',
 | 
			
		||||
        function () {
 | 
			
		||||
            SpriteMorph.prototype.enableFirstClass =
 | 
			
		||||
                !SpriteMorph.prototype.enableFirstClass;
 | 
			
		||||
            myself.currentSprite.blocksCache.sensing = null;
 | 
			
		||||
            myself.currentSprite.paletteCache.sensing = null;
 | 
			
		||||
            myself.refreshPalette();
 | 
			
		||||
        },
 | 
			
		||||
        SpriteMorph.prototype.enableFirstClass,
 | 
			
		||||
        'uncheck to disable support\nfor first-class sprites',
 | 
			
		||||
        'check to enable support\n for first-class sprite',
 | 
			
		||||
        true
 | 
			
		||||
    );
 | 
			
		||||
    addPreference(
 | 
			
		||||
        'Keyboard Editing',
 | 
			
		||||
        function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -2471,6 +2489,17 @@ IDE_Morph.prototype.settingsMenu = function () {
 | 
			
		|||
            false
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    addPreference(
 | 
			
		||||
        'Live coding support',
 | 
			
		||||
        function () {
 | 
			
		||||
            Process.prototype.enableLiveCoding =
 | 
			
		||||
                !Process.prototype.enableLiveCoding;
 | 
			
		||||
        },
 | 
			
		||||
        Process.prototype.enableLiveCoding,
 | 
			
		||||
        'EXPERIMENTAL! uncheck to disable live\ncustom control structures',
 | 
			
		||||
        'EXPERIMENTAL! check to enable\n live custom control structures',
 | 
			
		||||
        true
 | 
			
		||||
    );
 | 
			
		||||
    menu.addLine(); // everything below this line is stored in the project
 | 
			
		||||
    addPreference(
 | 
			
		||||
        'Thread safe scripts',
 | 
			
		||||
| 
						 | 
				
			
			@ -2799,7 +2828,6 @@ IDE_Morph.prototype.parseResourceFile = function (text) {
 | 
			
		|||
    return items;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// IDE_Morph menu actions
 | 
			
		||||
 | 
			
		||||
IDE_Morph.prototype.aboutSnap = function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -2807,7 +2835,7 @@ IDE_Morph.prototype.aboutSnap = function () {
 | 
			
		|||
        module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn,
 | 
			
		||||
        world = this.world();
 | 
			
		||||
 | 
			
		||||
    aboutTxt = 'Snap! 4.0.6\nBuild Your Own Blocks\n\n'
 | 
			
		||||
    aboutTxt = 'Snap! 4.0.7\nBuild Your Own Blocks\n\n'
 | 
			
		||||
        + 'Copyright \u24B8 2016 Jens M\u00F6nig and '
 | 
			
		||||
        + 'Brian Harvey\n'
 | 
			
		||||
        + 'jens@moenig.org, bh@cs.berkeley.edu\n\n'
 | 
			
		||||
| 
						 | 
				
			
			@ -3027,6 +3055,7 @@ IDE_Morph.prototype.newProject = function () {
 | 
			
		|||
    StageMorph.prototype.enableInheritance = false;
 | 
			
		||||
    StageMorph.prototype.enableSublistIDs = false;
 | 
			
		||||
    SpriteMorph.prototype.useFlatLineEnds = false;
 | 
			
		||||
    Process.prototype.enableLiveCoding = false;
 | 
			
		||||
    this.setProjectName('');
 | 
			
		||||
    this.projectNotes = '';
 | 
			
		||||
    this.createStage();
 | 
			
		||||
| 
						 | 
				
			
			@ -3534,6 +3563,7 @@ IDE_Morph.prototype.rawOpenProjectString = function (str) {
 | 
			
		|||
    StageMorph.prototype.enableCodeMapping = false;
 | 
			
		||||
    StageMorph.prototype.enableInheritance = false;
 | 
			
		||||
    StageMorph.prototype.enableSublistIDs = false;
 | 
			
		||||
    Process.prototype.enableLiveCoding = false;
 | 
			
		||||
    if (Process.prototype.isCatchingErrors) {
 | 
			
		||||
        try {
 | 
			
		||||
            this.serializer.openProject(
 | 
			
		||||
| 
						 | 
				
			
			@ -3578,6 +3608,7 @@ IDE_Morph.prototype.rawOpenCloudDataString = function (str) {
 | 
			
		|||
    StageMorph.prototype.enableCodeMapping = false;
 | 
			
		||||
    StageMorph.prototype.enableInheritance = false;
 | 
			
		||||
    StageMorph.prototype.enableSublistIDs = false;
 | 
			
		||||
    Process.prototype.enableLiveCoding = false;
 | 
			
		||||
    if (Process.prototype.isCatchingErrors) {
 | 
			
		||||
        try {
 | 
			
		||||
            model = this.serializer.parse(str);
 | 
			
		||||
| 
						 | 
				
			
			@ -3929,7 +3960,7 @@ IDE_Morph.prototype.toggleZebraColoring = function () {
 | 
			
		|||
 | 
			
		||||
    // select all scripts:
 | 
			
		||||
    this.stage.children.concat(this.stage).forEach(function (morph) {
 | 
			
		||||
        if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
 | 
			
		||||
        if (isSnapObject(morph)) {
 | 
			
		||||
            scripts = scripts.concat(
 | 
			
		||||
                morph.scripts.children.filter(function (morph) {
 | 
			
		||||
                    return morph instanceof BlockMorph;
 | 
			
		||||
| 
						 | 
				
			
			@ -6298,7 +6329,8 @@ CostumeIconMorph.prototype.editCostume = function () {
 | 
			
		|||
    } else {
 | 
			
		||||
        this.object.edit(
 | 
			
		||||
            this.world(),
 | 
			
		||||
            this.parentThatIsA(IDE_Morph)
 | 
			
		||||
            this.parentThatIsA(IDE_Morph),
 | 
			
		||||
            false // not a new costume, retain existing rotation center
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										33
									
								
								history.txt
								
								
								
								
							
							
						
						
									
										33
									
								
								history.txt
								
								
								
								
							| 
						 | 
				
			
			@ -2882,3 +2882,36 @@ end - bulk of 151215
 | 
			
		|||
* added web api / https reporter library
 | 
			
		||||
* Blocks, Store: New “transient variable” feature
 | 
			
		||||
* German translation update
 | 
			
		||||
 | 
			
		||||
== v4.0.6 ==== - saving linked lists
 | 
			
		||||
 | 
			
		||||
160502
 | 
			
		||||
------
 | 
			
		||||
* first class sprites, new MY reporter block and extended functionality of TOUCHING
 | 
			
		||||
* fixed switching from list watcher to table view inside sprite speech bubbles
 | 
			
		||||
* fixed paint editor automatic rotation center issue
 | 
			
		||||
* enhanced functionality to SET a sprite’s attributes
 | 
			
		||||
* execute clone initialization scripts’ first step in the same frame as the clone command
 | 
			
		||||
* auto-repair (sorta) certain broken project files
 | 
			
		||||
* Threads: More aggressive emergency yielding
 | 
			
		||||
* “corpsify” deleted sprites, which might still be referred to by variables and lists
 | 
			
		||||
* Blocks: simplify block copying
 | 
			
		||||
* experimental hidden “live-coding support” preference
 | 
			
		||||
* updated penTrails library to shrinkWrap generated costumes
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
demos from the documentation:
 | 
			
		||||
http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=population
 | 
			
		||||
http://snap.berkeley.edu/run#present:Username=jens&ProjectName=Woodworm
 | 
			
		||||
http://snap.berkeley.edu/run#present:Username=jens&ProjectName=Ferris%20Wheel%202016
 | 
			
		||||
http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=PathFollower
 | 
			
		||||
http://snap.berkeley.edu/run#present:Username=jens&ProjectName=cartwheel
 | 
			
		||||
http://snap.berkeley.edu/run#cloud:Username=jens&ProjectName=rotation
 | 
			
		||||
 | 
			
		||||
* Translation updates: Slovenian, Portuguese, Chinese
 | 
			
		||||
* minor bug fixes
 | 
			
		||||
 | 
			
		||||
== v4.0.7 ==== - first class sprites
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -185,7 +185,7 @@ SnapTranslator.dict.de = {
 | 
			
		|||
    'translator_e-mail':
 | 
			
		||||
        'jens@moenig.org', // optional
 | 
			
		||||
    'last_changed':
 | 
			
		||||
        '2016-03-16', // this, too, will appear in the Translators tab
 | 
			
		||||
        '2016-05-02', // this, too, will appear in the Translators tab
 | 
			
		||||
 | 
			
		||||
    // GUI
 | 
			
		||||
    // control bar:
 | 
			
		||||
| 
						 | 
				
			
			@ -527,6 +527,8 @@ SnapTranslator.dict.de = {
 | 
			
		|||
        'Stoppuhr',
 | 
			
		||||
    '%att of %spr':
 | 
			
		||||
        '%att von %spr',
 | 
			
		||||
    'my %get':
 | 
			
		||||
        'Attribut %get',
 | 
			
		||||
    'http:// %s':
 | 
			
		||||
        'http:// %s',
 | 
			
		||||
    'turbo mode?':
 | 
			
		||||
| 
						 | 
				
			
			@ -1352,10 +1354,46 @@ SnapTranslator.dict.de = {
 | 
			
		|||
        'Funktionsblock',
 | 
			
		||||
    'predicate':
 | 
			
		||||
        'Pr\u00e4dikat',
 | 
			
		||||
    'sprite':
 | 
			
		||||
        'Objekt',
 | 
			
		||||
 | 
			
		||||
    // list indices
 | 
			
		||||
    'last':
 | 
			
		||||
        'letztes',
 | 
			
		||||
    'any':
 | 
			
		||||
        'beliebig'
 | 
			
		||||
        'beliebig',
 | 
			
		||||
 | 
			
		||||
    // attributes
 | 
			
		||||
    'neighbors':
 | 
			
		||||
        'Nachbarn',
 | 
			
		||||
    'self':
 | 
			
		||||
        'selbst',
 | 
			
		||||
    'other sprites':
 | 
			
		||||
        'andere Objekte',
 | 
			
		||||
    'parts':
 | 
			
		||||
        'Teile',
 | 
			
		||||
    'anchor':
 | 
			
		||||
        'Verankerung',
 | 
			
		||||
    'parent':
 | 
			
		||||
        'Vorfahr',
 | 
			
		||||
    'children':
 | 
			
		||||
        'Abkömmlinge',
 | 
			
		||||
    'clones':
 | 
			
		||||
        'Klone',
 | 
			
		||||
    'other clones':
 | 
			
		||||
        'andere Klone',
 | 
			
		||||
    'dangling?':
 | 
			
		||||
        'Baumeln?',
 | 
			
		||||
    'rotation x':
 | 
			
		||||
        'Drehpunkt x',
 | 
			
		||||
    'rotation y':
 | 
			
		||||
        'Drehpunkt y',
 | 
			
		||||
    'center x':
 | 
			
		||||
        'Mittelpunkt x',
 | 
			
		||||
    'center y':
 | 
			
		||||
        'Mittelpunkt y',
 | 
			
		||||
    'name':
 | 
			
		||||
        'Name',
 | 
			
		||||
    'stage':
 | 
			
		||||
        'B\u00fchne',
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
<blocks app="Snap! 4.0, http://snap.berkeley.edu" version="1"><block-definition s="pen trails" type="reporter" category="pen"><header></header><code></code><inputs></inputs><script><block s="doReport"><block s="evaluate"><block s="reportJSFunction"><list></list><l>return new Costume(
  this.parentThatIsA(StageMorph).trailsCanvas
);</l></block><list></list></block></block></script></block-definition><block-definition s="set pen trails to: %'costume'" type="command" category="pen"><header></header><code></code><inputs><input type="%s" readonly="true"></input></inputs><script><block s="doRun"><block s="reportJSFunction"><list><l>cst</l></list><l>var stage = this.parentThatIsA(StageMorph);
stage.trailsCanvas = cst.contents;
stage.changed();</l></block><list><block var="costume"/></list></block></script></block-definition></blocks>
 | 
			
		||||
<blocks app="Snap! 4.0, http://snap.berkeley.edu" version="1"><block-definition s="pen trails" type="reporter" category="pen"><header></header><code></code><inputs></inputs><script><block s="doReport"><block s="evaluate"><block s="reportJSFunction"><list></list><l>var cst = new Costume(
  this.parentThatIsA(StageMorph).trailsCanvas
);
cst.shrinkWrap();
return cst;</l></block><list></list></block></block></script></block-definition><block-definition s="set pen trails to: %'costume'" type="command" category="pen"><header></header><code></code><inputs><input type="%s" readonly="true"></input></inputs><script><block s="doRun"><block s="reportJSFunction"><list><l>cst</l></list><l>var stage = this.parentThatIsA(StageMorph);
stage.trailsCanvas = cst.contents;
stage.changed();</l></block><list><block var="costume"/></list></block></script></block-definition></blocks>
 | 
			
		||||
| 
						 | 
				
			
			@ -60,9 +60,9 @@
 | 
			
		|||
Color, Point, WatcherMorph, StringMorph, SpriteMorph, ScrollFrameMorph,
 | 
			
		||||
CellMorph, ArrowMorph, MenuMorph, snapEquals, Morph, isNil, localize,
 | 
			
		||||
MorphicPreferences, TableDialogMorph, SpriteBubbleMorph, SpeechBubbleMorph,
 | 
			
		||||
TableFrameMorph, TableMorph, Variable*/
 | 
			
		||||
TableFrameMorph, TableMorph, Variable, isSnapObject*/
 | 
			
		||||
 | 
			
		||||
modules.lists = '2016-February-24';
 | 
			
		||||
modules.lists = '2016-May-02';
 | 
			
		||||
 | 
			
		||||
var List;
 | 
			
		||||
var ListWatcherMorph;
 | 
			
		||||
| 
						 | 
				
			
			@ -322,6 +322,27 @@ List.prototype.asArray = function () {
 | 
			
		|||
    return this.contents;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
List.prototype.itemsArray = function () {
 | 
			
		||||
    // answer an array containing my elements
 | 
			
		||||
    // don't convert linked lists to arrays
 | 
			
		||||
    if (this.isLinked) {
 | 
			
		||||
        var next = this,
 | 
			
		||||
            result = [],
 | 
			
		||||
            i;
 | 
			
		||||
        while (next && next.isLinked) {
 | 
			
		||||
            result.push(next.first);
 | 
			
		||||
            next = next.rest;
 | 
			
		||||
        }
 | 
			
		||||
        if (next) {
 | 
			
		||||
            for (i = 1; i <= next.contents.length; i += 1) {
 | 
			
		||||
                result.push(next.at(i));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return result;
 | 
			
		||||
    }
 | 
			
		||||
    return this.contents;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
List.prototype.asText = function () {
 | 
			
		||||
    var result = '',
 | 
			
		||||
        length,
 | 
			
		||||
| 
						 | 
				
			
			@ -353,17 +374,7 @@ List.prototype.asText = function () {
 | 
			
		|||
 | 
			
		||||
List.prototype.becomeArray = function () {
 | 
			
		||||
    if (this.isLinked) {
 | 
			
		||||
        var next = this, i;
 | 
			
		||||
        this.contents = [];
 | 
			
		||||
        while (next && next.isLinked) {
 | 
			
		||||
            this.contents.push(next.first);
 | 
			
		||||
            next = next.rest;
 | 
			
		||||
        }
 | 
			
		||||
        if (next) {
 | 
			
		||||
            for (i = 1; i <= next.contents.length; i += 1) {
 | 
			
		||||
                this.contents.push(next.at(i));
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        this.contents = this.itemsArray();
 | 
			
		||||
        this.isLinked = false;
 | 
			
		||||
        this.first = null;
 | 
			
		||||
        this.rest = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -543,10 +554,12 @@ ListWatcherMorph.prototype.update = function (anyway) {
 | 
			
		|||
        starttime, maxtime = 1000;
 | 
			
		||||
 | 
			
		||||
    this.frame.contents.children.forEach(function (m) {
 | 
			
		||||
 | 
			
		||||
        if (m instanceof CellMorph
 | 
			
		||||
                && m.contentsMorph instanceof ListWatcherMorph) {
 | 
			
		||||
            m.contentsMorph.update();
 | 
			
		||||
        if (m instanceof CellMorph) {
 | 
			
		||||
            if (m.contentsMorph instanceof ListWatcherMorph) {
 | 
			
		||||
                m.contentsMorph.update();
 | 
			
		||||
            } else if (isSnapObject(m.contents)) {
 | 
			
		||||
                m.update();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -802,7 +815,7 @@ ListWatcherMorph.prototype.showTableView = function () {
 | 
			
		|||
    if (!view) {return; }
 | 
			
		||||
    if (view instanceof SpriteBubbleMorph) {
 | 
			
		||||
        view.changed();
 | 
			
		||||
        view.drawNew();
 | 
			
		||||
        view.drawNew(true);
 | 
			
		||||
    } else if (view instanceof SpeechBubbleMorph) {
 | 
			
		||||
        view.contents = new TableFrameMorph(new TableMorph(this.list, 10));
 | 
			
		||||
        view.contents.expand(this.extent());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -42,7 +42,7 @@
 | 
			
		|||
 | 
			
		||||
/*global modules, contains*/
 | 
			
		||||
 | 
			
		||||
modules.locale = '2016-March-16';
 | 
			
		||||
modules.locale = '2016-May-02';
 | 
			
		||||
 | 
			
		||||
// Global stuff
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -161,7 +161,7 @@ SnapTranslator.dict.de = {
 | 
			
		|||
    'translator_e-mail':
 | 
			
		||||
        'jens@moenig.org',
 | 
			
		||||
    'last_changed':
 | 
			
		||||
        '2016-03-16'
 | 
			
		||||
        '2016-05-02'
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SnapTranslator.dict.it = {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -82,7 +82,7 @@ SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph,
 | 
			
		|||
BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize,
 | 
			
		||||
TableMorph, TableFrameMorph*/
 | 
			
		||||
 | 
			
		||||
modules.objects = '2016-March-16';
 | 
			
		||||
modules.objects = '2016-May-02';
 | 
			
		||||
 | 
			
		||||
var SpriteMorph;
 | 
			
		||||
var StageMorph;
 | 
			
		||||
| 
						 | 
				
			
			@ -98,6 +98,10 @@ var StagePrompterMorph;
 | 
			
		|||
var Note;
 | 
			
		||||
var SpriteHighlightMorph;
 | 
			
		||||
 | 
			
		||||
function isSnapObject(thing) {
 | 
			
		||||
    return thing instanceof SpriteMorph || (thing instanceof StageMorph);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SpriteMorph /////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
// I am a scriptable object
 | 
			
		||||
| 
						 | 
				
			
			@ -144,6 +148,7 @@ SpriteMorph.prototype.sliderColor
 | 
			
		|||
SpriteMorph.prototype.isCachingPrimitives = true;
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.enableNesting = true;
 | 
			
		||||
SpriteMorph.prototype.enableFirstClass = true;
 | 
			
		||||
SpriteMorph.prototype.useFlatLineEnds = false;
 | 
			
		||||
SpriteMorph.prototype.highlightColor = new Color(250, 200, 130);
 | 
			
		||||
SpriteMorph.prototype.highlightBorder = 8;
 | 
			
		||||
| 
						 | 
				
			
			@ -863,6 +868,12 @@ SpriteMorph.prototype.initBlocks = function () {
 | 
			
		|||
            category: 'sensing',
 | 
			
		||||
            spec: 'current %dates'
 | 
			
		||||
        },
 | 
			
		||||
        reportGet:{
 | 
			
		||||
            type: 'reporter',
 | 
			
		||||
            category: 'sensing',
 | 
			
		||||
            spec: 'my %get',
 | 
			
		||||
            defaults: [['neighbors']]
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        // Operators
 | 
			
		||||
        reifyScript: {
 | 
			
		||||
| 
						 | 
				
			
			@ -1310,6 +1321,7 @@ SpriteMorph.prototype.init = function (globals) {
 | 
			
		|||
    this.rotationStyle = 1; // 1 = full, 2 = left/right, 0 = off
 | 
			
		||||
    this.version = Date.now(); // for observer optimization
 | 
			
		||||
    this.isClone = false; // indicate a "temporary" Scratch-style clone
 | 
			
		||||
    this.isCorpse = false; // indicate whether a sprite/clone has been deleted
 | 
			
		||||
    this.cloneOriginName = '';
 | 
			
		||||
 | 
			
		||||
    // sprite nesting properties
 | 
			
		||||
| 
						 | 
				
			
			@ -1356,7 +1368,6 @@ SpriteMorph.prototype.fullCopy = function () {
 | 
			
		|||
    var c = SpriteMorph.uber.fullCopy.call(this),
 | 
			
		||||
        myself = this,
 | 
			
		||||
        arr = [],
 | 
			
		||||
        dp,
 | 
			
		||||
        cb;
 | 
			
		||||
 | 
			
		||||
    c.stopTalking();
 | 
			
		||||
| 
						 | 
				
			
			@ -1393,7 +1404,7 @@ SpriteMorph.prototype.fullCopy = function () {
 | 
			
		|||
    c.anchor = null;
 | 
			
		||||
    c.parts = [];
 | 
			
		||||
    this.parts.forEach(function (part) {
 | 
			
		||||
        dp = part.fullCopy();
 | 
			
		||||
        var dp = part.fullCopy();
 | 
			
		||||
        dp.nestingScale = part.nestingScale;
 | 
			
		||||
        dp.rotatesWithAnchor = part.rotatesWithAnchor;
 | 
			
		||||
        c.attachPart(dp);
 | 
			
		||||
| 
						 | 
				
			
			@ -1404,10 +1415,12 @@ SpriteMorph.prototype.fullCopy = function () {
 | 
			
		|||
 | 
			
		||||
SpriteMorph.prototype.appearIn = function (ide) {
 | 
			
		||||
    // private - used in IDE_Morph.duplicateSprite()
 | 
			
		||||
    this.name = ide.newSpriteName(this.name);
 | 
			
		||||
    if (!this.isClone) {
 | 
			
		||||
        this.name = ide.newSpriteName(this.name);
 | 
			
		||||
        ide.corral.addSprite(this);
 | 
			
		||||
        ide.sprites.add(this);
 | 
			
		||||
    }
 | 
			
		||||
    ide.stage.add(this);
 | 
			
		||||
    ide.sprites.add(this);
 | 
			
		||||
    ide.corral.addSprite(this);
 | 
			
		||||
    this.parts.forEach(function (part) {
 | 
			
		||||
        part.appearIn(ide);
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -1531,7 +1544,7 @@ SpriteMorph.prototype.drawNew = function () {
 | 
			
		|||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    this.version = Date.now();
 | 
			
		||||
    this.version = Date.now(); // for observer optimization
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.endWarp = function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -1921,7 +1934,12 @@ SpriteMorph.prototype.blockTemplates = function (category) {
 | 
			
		|||
        blocks.push(block('getTimer'));
 | 
			
		||||
        blocks.push('-');
 | 
			
		||||
        blocks.push(block('reportAttributeOf'));
 | 
			
		||||
 | 
			
		||||
        if (SpriteMorph.prototype.enableFirstClass) {
 | 
			
		||||
            blocks.push(block('reportGet'));
 | 
			
		||||
        }
 | 
			
		||||
        blocks.push('-');
 | 
			
		||||
 | 
			
		||||
        blocks.push(block('reportURL'));
 | 
			
		||||
        blocks.push('-');
 | 
			
		||||
        blocks.push(block('reportIsFastTracking'));
 | 
			
		||||
| 
						 | 
				
			
			@ -2756,7 +2774,9 @@ SpriteMorph.prototype.userMenu = function () {
 | 
			
		|||
        // menu.addItem('help', 'nop');
 | 
			
		||||
        return menu;
 | 
			
		||||
    }
 | 
			
		||||
    menu.addItem("duplicate", 'duplicate');
 | 
			
		||||
    if (!this.isClone) {
 | 
			
		||||
        menu.addItem("duplicate", 'duplicate');
 | 
			
		||||
    }
 | 
			
		||||
    menu.addItem("delete", 'remove');
 | 
			
		||||
    menu.addItem("move", 'moveCenter');
 | 
			
		||||
    if (!this.isClone) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2836,19 +2856,35 @@ SpriteMorph.prototype.clonify = function (stage) {
 | 
			
		|||
    stage.add(this);
 | 
			
		||||
    hats = this.allHatBlocksFor('__clone__init__');
 | 
			
		||||
    hats.forEach(function (block) {
 | 
			
		||||
        stage.threads.startProcess(block, stage.isThreadSafe);
 | 
			
		||||
        stage.threads.startProcess(
 | 
			
		||||
            block,
 | 
			
		||||
            stage.isThreadSafe,
 | 
			
		||||
            null, // export result
 | 
			
		||||
            null, // callback
 | 
			
		||||
            null, // is clicked
 | 
			
		||||
            true // right away
 | 
			
		||||
            );
 | 
			
		||||
    });
 | 
			
		||||
    this.endWarp();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.removeClone = function () {
 | 
			
		||||
    if (this.isClone) {
 | 
			
		||||
        // this.stopTalking();
 | 
			
		||||
        this.parent.threads.stopAllForReceiver(this);
 | 
			
		||||
        this.corpsify();
 | 
			
		||||
        this.destroy();
 | 
			
		||||
        this.parent.cloneCount -= 1;
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// SpriteMorph deleting
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.corpsify = function () {
 | 
			
		||||
    this.isCorpse = true;
 | 
			
		||||
    this.version = Date.now();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// SpriteMorph primitives
 | 
			
		||||
 | 
			
		||||
// SpriteMorph hiding and showing:
 | 
			
		||||
| 
						 | 
				
			
			@ -3666,6 +3702,53 @@ SpriteMorph.prototype.bounceOffEdge = function () {
 | 
			
		|||
    this.positionTalkBubble();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// SpriteMorph rotation center / fixation point manipulation
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.setRotationX = function (absoluteX) {
 | 
			
		||||
  this.setRotationCenter(new Point(absoluteX, this.yPosition()));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.setRotationY = function (absoluteY) {
 | 
			
		||||
  this.setRotationCenter(new Point(this.xPosition(), absoluteY));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.setRotationCenter = function (absoluteCoordinate) {
 | 
			
		||||
    var delta, normal;
 | 
			
		||||
    if (!this.costume) {
 | 
			
		||||
        throw new Error('setting the rotation center requires a costume');
 | 
			
		||||
    }
 | 
			
		||||
    delta = absoluteCoordinate.subtract(
 | 
			
		||||
        new Point(this.xPosition(), this.yPosition())
 | 
			
		||||
    ).divideBy(this.scale).rotateBy(radians(90 - this.heading));
 | 
			
		||||
    normal = this.costume.rotationCenter.add(new Point(delta.x, -delta.y));
 | 
			
		||||
    this.costume.rotationCenter = normal;
 | 
			
		||||
    this.drawNew();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.xCenter = function () {
 | 
			
		||||
    var stage = this.parentThatIsA(StageMorph);
 | 
			
		||||
 | 
			
		||||
    if (!stage && this.parent.grabOrigin) { // I'm currently being dragged
 | 
			
		||||
        stage = this.parent.grabOrigin.origin;
 | 
			
		||||
    }
 | 
			
		||||
    if (stage) {
 | 
			
		||||
        return (this.center().x - stage.center().x) / stage.scale;
 | 
			
		||||
    }
 | 
			
		||||
    return this.center().x;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.yCenter = function () {
 | 
			
		||||
    var stage = this.parentThatIsA(StageMorph);
 | 
			
		||||
 | 
			
		||||
    if (!stage && this.parent.grabOrigin) { // I'm currently being dragged
 | 
			
		||||
        stage = this.parent.grabOrigin.origin;
 | 
			
		||||
    }
 | 
			
		||||
    if (stage) {
 | 
			
		||||
        return (stage.center().y - this.center().y) / stage.scale;
 | 
			
		||||
    }
 | 
			
		||||
    return this.center().y;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// SpriteMorph message broadcasting
 | 
			
		||||
 | 
			
		||||
SpriteMorph.prototype.allMessageNames = function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -4363,7 +4446,23 @@ SpriteMorph.prototype.thumbnail = function (extentPoint) {
 | 
			
		|||
        trg = newCanvas(extentPoint),
 | 
			
		||||
        ctx = trg.getContext('2d');
 | 
			
		||||
 | 
			
		||||
    function xOut(style, alpha, width) {
 | 
			
		||||
        var inset = Math.min(extentPoint.x, extentPoint.y) / 10;
 | 
			
		||||
        ctx.strokeStyle = style;
 | 
			
		||||
        ctx.globalAlpha = alpha;
 | 
			
		||||
        ctx.compositeOperation = 'lighter';
 | 
			
		||||
        ctx.lineWidth = width || 1;
 | 
			
		||||
        ctx.moveTo(inset, inset);
 | 
			
		||||
        ctx.lineTo(trg.width - inset, trg.height - inset);
 | 
			
		||||
        ctx.moveTo(inset, trg.height - inset);
 | 
			
		||||
        ctx.lineTo(trg.width - inset, inset);
 | 
			
		||||
        ctx.stroke();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ctx.save();
 | 
			
		||||
    if (this.isCorpse) {
 | 
			
		||||
        ctx.globalAlpha = 0.3;
 | 
			
		||||
    }
 | 
			
		||||
    if (src.width && src.height) {
 | 
			
		||||
        ctx.scale(scale, scale);
 | 
			
		||||
        ctx.drawImage(
 | 
			
		||||
| 
						 | 
				
			
			@ -4372,6 +4471,11 @@ SpriteMorph.prototype.thumbnail = function (extentPoint) {
 | 
			
		|||
            Math.floor(yOffset / scale)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
    if (this.isCorpse) {
 | 
			
		||||
        ctx.restore();
 | 
			
		||||
        xOut('white', 0.8, 6);
 | 
			
		||||
        xOut('black', 0.8, 1);
 | 
			
		||||
    }
 | 
			
		||||
    return trg;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -4709,7 +4813,6 @@ StageMorph.uber = FrameMorph.prototype;
 | 
			
		|||
// StageMorph preferences settings
 | 
			
		||||
 | 
			
		||||
StageMorph.prototype.dimensions = new Point(480, 360); // unscaled extent
 | 
			
		||||
 | 
			
		||||
StageMorph.prototype.frameRate = 0; // unscheduled per default
 | 
			
		||||
 | 
			
		||||
StageMorph.prototype.isCachingPrimitives
 | 
			
		||||
| 
						 | 
				
			
			@ -4845,6 +4948,7 @@ StageMorph.prototype.drawNew = function () {
 | 
			
		|||
        );
 | 
			
		||||
        this.image = this.applyGraphicsEffects(this.image);
 | 
			
		||||
    }
 | 
			
		||||
    this.version = Date.now(); // for observer optimization
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
StageMorph.prototype.drawOn = function (aCanvas, aRect) {
 | 
			
		||||
| 
						 | 
				
			
			@ -5256,7 +5360,7 @@ StageMorph.prototype.fireKeyEvent = function (key) {
 | 
			
		|||
        return this.fireStopAllEvent();
 | 
			
		||||
    }
 | 
			
		||||
    this.children.concat(this).forEach(function (morph) {
 | 
			
		||||
        if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
 | 
			
		||||
        if (isSnapObject(morph)) {
 | 
			
		||||
            hats = hats.concat(morph.allHatBlocksForKey(evt));
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -5284,7 +5388,7 @@ StageMorph.prototype.fireGreenFlagEvent = function () {
 | 
			
		|||
        myself = this;
 | 
			
		||||
 | 
			
		||||
    this.children.concat(this).forEach(function (morph) {
 | 
			
		||||
        if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
 | 
			
		||||
        if (isSnapObject(morph)) {
 | 
			
		||||
            hats = hats.concat(morph.allHatBlocksFor('__shout__go__'));
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
| 
						 | 
				
			
			@ -5327,6 +5431,8 @@ StageMorph.prototype.removeAllClones = function () {
 | 
			
		|||
        );
 | 
			
		||||
    clones.forEach(function (clone) {
 | 
			
		||||
        myself.threads.stopAllForReceiver(clone);
 | 
			
		||||
        clone.detachFromAnchor();
 | 
			
		||||
        clone.corpsify();
 | 
			
		||||
        clone.destroy();
 | 
			
		||||
    });
 | 
			
		||||
    this.cloneCount = 0;
 | 
			
		||||
| 
						 | 
				
			
			@ -5575,7 +5681,12 @@ StageMorph.prototype.blockTemplates = function (category) {
 | 
			
		|||
        blocks.push(block('getTimer'));
 | 
			
		||||
        blocks.push('-');
 | 
			
		||||
        blocks.push(block('reportAttributeOf'));
 | 
			
		||||
 | 
			
		||||
        if (SpriteMorph.prototype.enableFirstClass) {
 | 
			
		||||
            blocks.push(block('reportGet'));
 | 
			
		||||
        }
 | 
			
		||||
        blocks.push('-');
 | 
			
		||||
 | 
			
		||||
        blocks.push(block('reportURL'));
 | 
			
		||||
        blocks.push('-');
 | 
			
		||||
        blocks.push(block('reportIsFastTracking'));
 | 
			
		||||
| 
						 | 
				
			
			@ -5848,9 +5959,16 @@ StageMorph.prototype.userMenu = function () {
 | 
			
		|||
StageMorph.prototype.showAll = function () {
 | 
			
		||||
    var myself = this;
 | 
			
		||||
    this.children.forEach(function (m) {
 | 
			
		||||
        m.show();
 | 
			
		||||
        m.keepWithin(myself);
 | 
			
		||||
        if (m.fixLayout) {m.fixLayout(); }
 | 
			
		||||
        if (m instanceof SpriteMorph) {
 | 
			
		||||
            if (!m.anchor) {
 | 
			
		||||
                m.show();
 | 
			
		||||
                m.keepWithin(myself);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            m.show();
 | 
			
		||||
            m.keepWithin(myself);
 | 
			
		||||
            if (m.fixLayout) {m.fixLayout(); }
 | 
			
		||||
        }
 | 
			
		||||
    });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -6173,14 +6291,31 @@ SpriteBubbleMorph.prototype.init = function (
 | 
			
		|||
 | 
			
		||||
SpriteBubbleMorph.prototype.dataAsMorph = function (data, toggle) {
 | 
			
		||||
    var contents,
 | 
			
		||||
        isTable,
 | 
			
		||||
        sprite = SpriteMorph.prototype,
 | 
			
		||||
        isText,
 | 
			
		||||
        img,
 | 
			
		||||
        scaledImg,
 | 
			
		||||
        width;
 | 
			
		||||
 | 
			
		||||
    if (data instanceof Morph) {
 | 
			
		||||
        contents = data;
 | 
			
		||||
        if (isSnapObject(data)) {
 | 
			
		||||
            img = data.thumbnail(new Point(40, 40));
 | 
			
		||||
            contents = new Morph();
 | 
			
		||||
            contents.silentSetWidth(img.width);
 | 
			
		||||
            contents.silentSetHeight(img.height);
 | 
			
		||||
            contents.image = img;
 | 
			
		||||
            contents.version = data.version;
 | 
			
		||||
            contents.step = function () {
 | 
			
		||||
                if (this.version !== data.version) {
 | 
			
		||||
                    img = data.thumbnail(new Point(40, 40));
 | 
			
		||||
                    this.image = img;
 | 
			
		||||
                    this.version = data.version;
 | 
			
		||||
                    this.changed();
 | 
			
		||||
                }
 | 
			
		||||
            };
 | 
			
		||||
        } else {
 | 
			
		||||
            contents = data;
 | 
			
		||||
        }
 | 
			
		||||
    } else if (isString(data)) {
 | 
			
		||||
        isText = true;
 | 
			
		||||
        contents = new TextMorph(
 | 
			
		||||
| 
						 | 
				
			
			@ -6209,7 +6344,13 @@ SpriteBubbleMorph.prototype.dataAsMorph = function (data, toggle) {
 | 
			
		|||
        contents.silentSetHeight(data.height);
 | 
			
		||||
        contents.image = data;
 | 
			
		||||
    } else if (data instanceof List) {
 | 
			
		||||
        if (!toggle && data.isTable()) {
 | 
			
		||||
        if (toggle && this.contentsMorph) {
 | 
			
		||||
            isTable = (this.contentsMorph instanceof ListWatcherMorph);
 | 
			
		||||
        } else {
 | 
			
		||||
            isTable = data.isTable();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (isTable) { // (!toggle && data.isTable()) {
 | 
			
		||||
            contents = new TableFrameMorph(new TableMorph(data, 10));
 | 
			
		||||
            if (this.stage) {
 | 
			
		||||
                contents.expand(this.stage.extent().translateBy(
 | 
			
		||||
| 
						 | 
				
			
			@ -6540,7 +6681,7 @@ Costume.prototype.edit = function (aWorld, anIDE, isnew, oncancel, onsubmit) {
 | 
			
		|||
                newCanvas(StageMorph.prototype.dimensions) :
 | 
			
		||||
                this.contents,
 | 
			
		||||
        isnew ?
 | 
			
		||||
                new Point(240, 180) :
 | 
			
		||||
                null :
 | 
			
		||||
                this.rotationCenter,
 | 
			
		||||
        function (img, rc) {
 | 
			
		||||
            myself.contents = img;
 | 
			
		||||
| 
						 | 
				
			
			@ -6963,6 +7104,7 @@ CellMorph.prototype.init = function (contents, color, idx, parentCell) {
 | 
			
		|||
    );
 | 
			
		||||
    this.color = color || new Color(255, 140, 0);
 | 
			
		||||
    this.isBig = false;
 | 
			
		||||
    this.version = null; // only for observing sprites
 | 
			
		||||
    this.drawNew();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -7012,6 +7154,16 @@ CellMorph.prototype.fixLayout = function () {
 | 
			
		|||
 | 
			
		||||
// CellMorph drawing:
 | 
			
		||||
 | 
			
		||||
CellMorph.prototype.update = function () {
 | 
			
		||||
    // special case for observing sprites
 | 
			
		||||
    if (!isSnapObject(this.contents)) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    if (this.version !== this.contents.version) {
 | 
			
		||||
        this.drawNew();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
CellMorph.prototype.drawNew = function (toggle, type) {
 | 
			
		||||
    var context,
 | 
			
		||||
        txt,
 | 
			
		||||
| 
						 | 
				
			
			@ -7029,11 +7181,21 @@ CellMorph.prototype.drawNew = function (toggle, type) {
 | 
			
		|||
    // re-build my contents
 | 
			
		||||
    if (toggle || (this.contentsMorph && !isSameList && !isSameTable)) {
 | 
			
		||||
        this.contentsMorph.destroy();
 | 
			
		||||
        this.version = null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (toggle || (!isSameList && !isSameTable)) {
 | 
			
		||||
        if (this.contents instanceof Morph) {
 | 
			
		||||
            this.contentsMorph = this.contents;
 | 
			
		||||
            if (isSnapObject(this.contents)) {
 | 
			
		||||
                img = this.contents.thumbnail(new Point(40, 40));
 | 
			
		||||
                this.contentsMorph = new Morph();
 | 
			
		||||
                this.contentsMorph.silentSetWidth(img.width);
 | 
			
		||||
                this.contentsMorph.silentSetHeight(img.height);
 | 
			
		||||
                this.contentsMorph.image = img;
 | 
			
		||||
                this.version = this.contents.version;
 | 
			
		||||
            } else {
 | 
			
		||||
                this.contentsMorph = this.contents;
 | 
			
		||||
            }
 | 
			
		||||
        } else if (isString(this.contents)) {
 | 
			
		||||
            txt  = this.contents.length > 500 ?
 | 
			
		||||
                    this.contents.slice(0, 500) + '...' : this.contents;
 | 
			
		||||
| 
						 | 
				
			
			@ -7444,6 +7606,8 @@ WatcherMorph.prototype.update = function () {
 | 
			
		|||
    }
 | 
			
		||||
    if (this.cellMorph.contentsMorph instanceof ListWatcherMorph) {
 | 
			
		||||
        this.cellMorph.contentsMorph.update();
 | 
			
		||||
    } else if (isSnapObject(this.cellMorph.contents)) {
 | 
			
		||||
        this.cellMorph.update();
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,19 +61,18 @@
 | 
			
		|||
    Oct 02 - revert disable smoothing (Jens)
 | 
			
		||||
    Dec 15 - center rotation point on costume creating (Craxic)
 | 
			
		||||
    Jan 18 - avoid pixel collision detection in PaintCanvas (Jens)
 | 
			
		||||
 */
 | 
			
		||||
    Mar 22 - fixed automatic rotation center point mechanism (Jens)
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
/*global Point, Rectangle, DialogBoxMorph, fontHeight, AlignmentMorph,
 | 
			
		||||
 FrameMorph, PushButtonMorph, Color, SymbolMorph, newCanvas, Morph, TextMorph,
 | 
			
		||||
 CostumeIconMorph, IDE_Morph, Costume, SpriteMorph, nop, Image, WardrobeMorph,
 | 
			
		||||
 TurtleIconMorph, localize, MenuMorph, InputFieldMorph, SliderMorph,
 | 
			
		||||
 ToggleMorph, ToggleButtonMorph, BoxMorph, modules, radians,
 | 
			
		||||
 MorphicPreferences, getDocumentPositionOf, StageMorph
 | 
			
		||||
 */
 | 
			
		||||
/*global Point, Rectangle, DialogBoxMorph, AlignmentMorph, PushButtonMorph,
 | 
			
		||||
Color, SymbolMorph, newCanvas, Morph, TextMorph, Costume, SpriteMorph, nop,
 | 
			
		||||
localize, InputFieldMorph, SliderMorph, ToggleMorph, ToggleButtonMorph,
 | 
			
		||||
BoxMorph, modules, radians, MorphicPreferences, getDocumentPositionOf,
 | 
			
		||||
StageMorph, isNil*/
 | 
			
		||||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.paint = '2016-January-18';
 | 
			
		||||
modules.paint = '2016-May-02';
 | 
			
		||||
 | 
			
		||||
// Declarations
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -253,7 +252,6 @@ PaintEditorMorph.prototype.buildScaleBox = function () {
 | 
			
		|||
PaintEditorMorph.prototype.openIn = function (world, oldim, oldrc, callback) {
 | 
			
		||||
    // Open the editor in a world with an optional image to edit
 | 
			
		||||
    this.oldim = oldim;
 | 
			
		||||
    this.oldrc = oldrc.copy();
 | 
			
		||||
    this.callback = callback || nop;
 | 
			
		||||
 | 
			
		||||
    this.processKeyUp = function () {
 | 
			
		||||
| 
						 | 
				
			
			@ -268,9 +266,10 @@ PaintEditorMorph.prototype.openIn = function (world, oldim, oldrc, callback) {
 | 
			
		|||
 | 
			
		||||
    //merge oldim:
 | 
			
		||||
    if (this.oldim) {
 | 
			
		||||
        this.paper.automaticCrosshairs = isNil(oldrc);
 | 
			
		||||
        this.paper.centermerge(this.oldim, this.paper.paper);
 | 
			
		||||
        this.paper.rotationCenter =
 | 
			
		||||
            this.oldrc.add(
 | 
			
		||||
            (oldrc || new Point(0, 0)).add(
 | 
			
		||||
                new Point(
 | 
			
		||||
                    (this.paper.paper.width - this.oldim.width) / 2,
 | 
			
		||||
                    (this.paper.paper.height - this.oldim.height) / 2
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,11 +56,11 @@ Color, List, newCanvas, Costume, Sound, Audio, IDE_Morph, ScriptsMorph,
 | 
			
		|||
BlockMorph, ArgMorph, InputSlotMorph, TemplateSlotMorph, CommandSlotMorph,
 | 
			
		||||
FunctionSlotMorph, MultiArgMorph, ColorSlotMorph, nop, CommentMorph, isNil,
 | 
			
		||||
localize, sizeOf, ArgLabelMorph, SVG_Costume, MorphicPreferences,
 | 
			
		||||
SyntaxElementMorph, Variable*/
 | 
			
		||||
SyntaxElementMorph, Variable, isSnapObject, console*/
 | 
			
		||||
 | 
			
		||||
// Global stuff ////////////////////////////////////////////////////////
 | 
			
		||||
 | 
			
		||||
modules.store = '2016-March-16';
 | 
			
		||||
modules.store = '2016-May-02';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
// XML_Serializer ///////////////////////////////////////////////////////
 | 
			
		||||
| 
						 | 
				
			
			@ -974,7 +974,16 @@ SnapSerializer.prototype.loadScript = function (model) {
 | 
			
		|||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (block) {
 | 
			
		||||
            block.nextBlock(nextBlock);
 | 
			
		||||
            if (block.nextBlock && (nextBlock instanceof CommandBlockMorph)) {
 | 
			
		||||
                block.nextBlock(nextBlock);
 | 
			
		||||
            } else { // +++
 | 
			
		||||
                console.log(
 | 
			
		||||
                    'SNAP: expecting a command but getting a reporter:\n' +
 | 
			
		||||
                        '  ' + block.blockSpec + '\n' +
 | 
			
		||||
                        '  ' + nextBlock.blockSpec
 | 
			
		||||
                );
 | 
			
		||||
                return topBlock;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            topBlock = nextBlock;
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			@ -1605,10 +1614,14 @@ VariableFrame.prototype.toXML = function (serializer) {
 | 
			
		|||
            dta = serializer.format(
 | 
			
		||||
                '<variable name="@">%</variable>',
 | 
			
		||||
                v,
 | 
			
		||||
                typeof val === 'object' ? serializer.store(val)
 | 
			
		||||
                        : typeof val === 'boolean' ?
 | 
			
		||||
                                serializer.format('<bool>$</bool>', val)
 | 
			
		||||
                                : serializer.format('<l>$</l>', val)
 | 
			
		||||
                typeof val === 'object' ?
 | 
			
		||||
                        (isSnapObject(val) ? ''
 | 
			
		||||
                                : serializer.store(val))
 | 
			
		||||
                                : typeof val === 'boolean' ?
 | 
			
		||||
                                        serializer.format(
 | 
			
		||||
                                            '<bool>$</bool>', val
 | 
			
		||||
                                        )
 | 
			
		||||
                                        : serializer.format('<l>$</l>', val)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
        return vars + dta;
 | 
			
		||||
| 
						 | 
				
			
			@ -1897,7 +1910,8 @@ List.prototype.toXML = function (serializer, mediaContext) {
 | 
			
		|||
                xml += serializer.format(
 | 
			
		||||
                    '<item>%</item>',
 | 
			
		||||
                    typeof value === 'object' ?
 | 
			
		||||
                            serializer.store(value, mediaContext)
 | 
			
		||||
                            (isSnapObject(value) ? ''
 | 
			
		||||
                                    : serializer.store(value, mediaContext))
 | 
			
		||||
                            : typeof value === 'boolean' ?
 | 
			
		||||
                                    serializer.format('<bool>$</bool>', value)
 | 
			
		||||
                                    : serializer.format('<l>$</l>', value)
 | 
			
		||||
| 
						 | 
				
			
			@ -1916,7 +1930,8 @@ List.prototype.toXML = function (serializer, mediaContext) {
 | 
			
		|||
                xml += serializer.format(
 | 
			
		||||
                    '<item>%</item>',
 | 
			
		||||
                    typeof value === 'object' ?
 | 
			
		||||
                            serializer.store(value, mediaContext)
 | 
			
		||||
                            (isSnapObject(value) ? ''
 | 
			
		||||
                                    : serializer.store(value, mediaContext))
 | 
			
		||||
                            : typeof value === 'boolean' ?
 | 
			
		||||
                                    serializer.format('<bool>$</bool>', value)
 | 
			
		||||
                                    : serializer.format('<l>$</l>', value)
 | 
			
		||||
| 
						 | 
				
			
			@ -1933,7 +1948,8 @@ List.prototype.toXML = function (serializer, mediaContext) {
 | 
			
		|||
            return xml + serializer.format(
 | 
			
		||||
                '<item>%</item>',
 | 
			
		||||
                typeof item === 'object' ?
 | 
			
		||||
                        serializer.store(item, mediaContext)
 | 
			
		||||
                        (isSnapObject(item) ? ''
 | 
			
		||||
                                : serializer.store(item, mediaContext))
 | 
			
		||||
                        : typeof item === 'boolean' ?
 | 
			
		||||
                                serializer.format('<bool>$</bool>', item)
 | 
			
		||||
                                : serializer.format('<l>$</l>', item)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -68,9 +68,9 @@
 | 
			
		|||
MorphicPreferences, FrameMorph, HandleMorph, DialogBoxMorph, isString,
 | 
			
		||||
SpriteMorph, Context, Costume, ArgMorph, BlockEditorMorph,
 | 
			
		||||
SyntaxElementMorph, MenuMorph, SpriteBubbleMorph, SpeechBubbleMorph,
 | 
			
		||||
CellMorph, ListWatcherMorph, isNil, BoxMorph, Variable*/
 | 
			
		||||
CellMorph, ListWatcherMorph, isNil, BoxMorph, Variable, isSnapObject*/
 | 
			
		||||
 | 
			
		||||
modules.tables = '2016-February-24';
 | 
			
		||||
modules.tables = '2016-May-02';
 | 
			
		||||
 | 
			
		||||
var Table;
 | 
			
		||||
var TableCellMorph;
 | 
			
		||||
| 
						 | 
				
			
			@ -307,6 +307,10 @@ TableCellMorph.prototype.setData = function (data, extent) {
 | 
			
		|||
    // note: don't call changed(), let the TableMorph handle it instead
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TableCellMorph.prototype.getData = function () {
 | 
			
		||||
    return this.data instanceof Array ? this.data[0] : this.data;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
TableCellMorph.prototype.drawNew = function () {
 | 
			
		||||
    this.image = newCanvas(this.extent());
 | 
			
		||||
    this.drawData();
 | 
			
		||||
| 
						 | 
				
			
			@ -373,7 +377,11 @@ TableCellMorph.prototype.drawData = function (lbl, bg) {
 | 
			
		|||
 | 
			
		||||
TableCellMorph.prototype.dataRepresentation = function (dta) {
 | 
			
		||||
    if (dta instanceof Morph) {
 | 
			
		||||
        return dta.fullImageClassic();
 | 
			
		||||
        if (isSnapObject(dta)) {
 | 
			
		||||
            return dta.thumbnail(new Point(40, 40));
 | 
			
		||||
        } else {
 | 
			
		||||
            return dta.fullImageClassic();
 | 
			
		||||
        }
 | 
			
		||||
    } else if (isString(dta)) {
 | 
			
		||||
        return dta.length > 100 ? dta.slice(0, 100) + '...' : dta;
 | 
			
		||||
    } else if (typeof dta === 'number') {
 | 
			
		||||
| 
						 | 
				
			
			@ -544,6 +552,9 @@ TableMorph.prototype.init = function (
 | 
			
		|||
    this.resizeCol = null;
 | 
			
		||||
    this.resizeRow = null;
 | 
			
		||||
 | 
			
		||||
    // cached property for updating (don not persist):
 | 
			
		||||
    this.wantsUpdate = false;
 | 
			
		||||
 | 
			
		||||
    // initialize inherited properties:
 | 
			
		||||
    // make sure not to draw anything just yet
 | 
			
		||||
    // therefore omit FrameMorph's properties (not needed here)
 | 
			
		||||
| 
						 | 
				
			
			@ -697,6 +708,9 @@ TableMorph.prototype.buildCells = function () {
 | 
			
		|||
                ).add(pos)
 | 
			
		||||
            );
 | 
			
		||||
            this.add(cell);
 | 
			
		||||
            if (isSnapObject(cell.getData())) {
 | 
			
		||||
                this.wantsUpdate = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    this.add(this.hBar);
 | 
			
		||||
| 
						 | 
				
			
			@ -718,6 +732,9 @@ TableMorph.prototype.drawData = function (noScrollUpdate) {
 | 
			
		|||
                    !r ? r : r + this.startRow - 1
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
            if (isSnapObject(cell.getData())) {
 | 
			
		||||
                this.wantsUpdate = true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    if (!noScrollUpdate) {this.updateScrollBars(); }
 | 
			
		||||
| 
						 | 
				
			
			@ -777,7 +794,10 @@ TableMorph.prototype.update = function () {
 | 
			
		|||
        version = this.table instanceof List ?
 | 
			
		||||
            this.table.version(this.startRow, this.rows)
 | 
			
		||||
                    : this.table.lastChanged;
 | 
			
		||||
    if (this.tableVersion === version) { return; }
 | 
			
		||||
    if (this.tableVersion === version && !this.wantsUpdate) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    this.wantsUpdate = false;
 | 
			
		||||
    if (this.table instanceof List) {
 | 
			
		||||
        oldCols = this.columns.length;
 | 
			
		||||
        oldRows = this.rows;
 | 
			
		||||
| 
						 | 
				
			
			@ -1048,6 +1068,7 @@ TableMorph.prototype.showListView = function () {
 | 
			
		|||
        view.drawNew(true);
 | 
			
		||||
    } else if (view instanceof SpeechBubbleMorph) {
 | 
			
		||||
        view.contents = new ListWatcherMorph(this.table);
 | 
			
		||||
        view.contents.step = view.contents.update;
 | 
			
		||||
        view.contents.expand(this.extent());
 | 
			
		||||
        view.drawNew(true);
 | 
			
		||||
    } else { // watcher cell
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -59,9 +59,9 @@ degrees, detect, nop, radians, ReporterSlotMorph, CSlotMorph, RingMorph,
 | 
			
		|||
IDE_Morph, ArgLabelMorph, localize, XML_Element, hex_sha512, TableDialogMorph,
 | 
			
		||||
StageMorph, SpriteMorph, StagePrompterMorph, Note, modules, isString, copy,
 | 
			
		||||
isNil, WatcherMorph, List, ListWatcherMorph, alert, console, TableMorph,
 | 
			
		||||
TableFrameMorph*/
 | 
			
		||||
TableFrameMorph, isSnapObject*/
 | 
			
		||||
 | 
			
		||||
modules.threads = '2016-March-16';
 | 
			
		||||
modules.threads = '2016-May-02';
 | 
			
		||||
 | 
			
		||||
var ThreadManager;
 | 
			
		||||
var Process;
 | 
			
		||||
| 
						 | 
				
			
			@ -191,7 +191,8 @@ ThreadManager.prototype.startProcess = function (
 | 
			
		|||
    isThreadSafe,
 | 
			
		||||
    exportResult,
 | 
			
		||||
    callback,
 | 
			
		||||
    isClicked
 | 
			
		||||
    isClicked,
 | 
			
		||||
    rightAway
 | 
			
		||||
) {
 | 
			
		||||
    var active = this.findProcess(block),
 | 
			
		||||
        top = block.topBlock(),
 | 
			
		||||
| 
						 | 
				
			
			@ -203,13 +204,16 @@ ThreadManager.prototype.startProcess = function (
 | 
			
		|||
        active.stop();
 | 
			
		||||
        this.removeTerminatedProcesses();
 | 
			
		||||
    }
 | 
			
		||||
    newProc = new Process(block.topBlock(), callback);
 | 
			
		||||
    newProc = new Process(block.topBlock(), callback, rightAway);
 | 
			
		||||
    newProc.exportResult = exportResult;
 | 
			
		||||
    newProc.isClicked = isClicked || false;
 | 
			
		||||
    if (!newProc.homeContext.receiver.isClone) {
 | 
			
		||||
        top.addHighlight();
 | 
			
		||||
    }
 | 
			
		||||
    this.processes.push(newProc);
 | 
			
		||||
    if (rightAway) {
 | 
			
		||||
        newProc.runStep();
 | 
			
		||||
    }
 | 
			
		||||
    return newProc;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -351,7 +355,7 @@ ThreadManager.prototype.doWhen = function (block, stopIt) {
 | 
			
		|||
            pred,
 | 
			
		||||
            null,
 | 
			
		||||
            null,
 | 
			
		||||
            20,
 | 
			
		||||
            50,
 | 
			
		||||
            'the predicate takes\ntoo long for a\ncustom hat block',
 | 
			
		||||
            true // suppress errors => handle them right here instead
 | 
			
		||||
        ) === true) {
 | 
			
		||||
| 
						 | 
				
			
			@ -430,8 +434,9 @@ Process.prototype = {};
 | 
			
		|||
Process.prototype.constructor = Process;
 | 
			
		||||
Process.prototype.timeout = 500; // msecs after which to force yield
 | 
			
		||||
Process.prototype.isCatchingErrors = true;
 | 
			
		||||
Process.prototype.enableLiveCoding = false; // experimental
 | 
			
		||||
 | 
			
		||||
function Process(topBlock, onComplete) {
 | 
			
		||||
function Process(topBlock, onComplete, rightAway) {
 | 
			
		||||
    this.topBlock = topBlock || null;
 | 
			
		||||
 | 
			
		||||
    this.readyToYield = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -462,7 +467,9 @@ function Process(topBlock, onComplete) {
 | 
			
		|||
            topBlock.blockSequence(),
 | 
			
		||||
            this.homeContext
 | 
			
		||||
        );
 | 
			
		||||
        this.pushContext('doYield'); // highlight top block
 | 
			
		||||
        if (!rightAway) {
 | 
			
		||||
            this.pushContext('doYield'); // highlight top block
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -485,8 +492,9 @@ Process.prototype.runStep = function (deadline) {
 | 
			
		|||
    this.readyToYield = false;
 | 
			
		||||
    while (!this.readyToYield
 | 
			
		||||
            && this.context
 | 
			
		||||
            && (this.isAtomic ?
 | 
			
		||||
                    (Date.now() - this.lastYield < this.timeout) : true)
 | 
			
		||||
            && // (this.isAtomic ?
 | 
			
		||||
                    (Date.now() - this.lastYield < this.timeout)
 | 
			
		||||
               //             : true)
 | 
			
		||||
                ) {
 | 
			
		||||
        // also allow pausing inside atomic steps - for PAUSE block primitive:
 | 
			
		||||
        if (this.isPaused) {
 | 
			
		||||
| 
						 | 
				
			
			@ -870,7 +878,8 @@ Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) {
 | 
			
		|||
        i = 0;
 | 
			
		||||
 | 
			
		||||
    if (topBlock) {
 | 
			
		||||
        context.expression = topBlock.fullCopy();
 | 
			
		||||
        context.expression = this.enableLiveCoding ?
 | 
			
		||||
                topBlock : topBlock.fullCopy();
 | 
			
		||||
        context.expression.show(); // be sure to make visible if in app mode
 | 
			
		||||
 | 
			
		||||
        if (!isCustomBlock) {
 | 
			
		||||
| 
						 | 
				
			
			@ -888,7 +897,8 @@ Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) {
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
    } else {
 | 
			
		||||
        context.expression = [this.context.expression.fullCopy()];
 | 
			
		||||
        context.expression = this.enableLiveCoding ? [this.context.expression]
 | 
			
		||||
                : [this.context.expression.fullCopy()];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    context.inputs = parameterNames.asArray();
 | 
			
		||||
| 
						 | 
				
			
			@ -1294,16 +1304,20 @@ Process.prototype.doDeclareVariables = function (varNames) {
 | 
			
		|||
 | 
			
		||||
Process.prototype.doSetVar = function (varName, value) {
 | 
			
		||||
    var varFrame = this.context.variables,
 | 
			
		||||
        name = varName;
 | 
			
		||||
        name = varName,
 | 
			
		||||
        rcvr;
 | 
			
		||||
    if (name instanceof Context) {
 | 
			
		||||
        rcvr = this.blockReceiver();
 | 
			
		||||
        if (name.expression.selector === 'reportGetVar') {
 | 
			
		||||
            name.variables.setVar(
 | 
			
		||||
                name.expression.blockSpec,
 | 
			
		||||
                value,
 | 
			
		||||
                this.blockReceiver()
 | 
			
		||||
                rcvr
 | 
			
		||||
            );
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        this.doSet(name, value);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    varFrame.setVar(name, value, this.blockReceiver());
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1959,7 +1973,7 @@ Process.prototype.blockReceiver = function () {
 | 
			
		|||
// Process sound primitives (interpolated)
 | 
			
		||||
 | 
			
		||||
Process.prototype.doPlaySoundUntilDone = function (name) {
 | 
			
		||||
    var sprite = this.homeContext.receiver;
 | 
			
		||||
    var sprite = this.blockReceiver();
 | 
			
		||||
    if (this.context.activeAudio === null) {
 | 
			
		||||
        this.context.activeAudio = sprite.playSound(name);
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -2060,7 +2074,7 @@ Process.prototype.doBroadcast = function (message) {
 | 
			
		|||
    if (message !== '') {
 | 
			
		||||
        stage.lastMessage = message;
 | 
			
		||||
        stage.children.concat(stage).forEach(function (morph) {
 | 
			
		||||
            if (morph instanceof SpriteMorph || morph instanceof StageMorph) {
 | 
			
		||||
            if (isSnapObject(morph)) {
 | 
			
		||||
                hats = hats.concat(morph.allHatBlocksFor(message));
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
| 
						 | 
				
			
			@ -2116,6 +2130,12 @@ Process.prototype.assertType = function (thing, typeString) {
 | 
			
		|||
    throw new Error('expecting ' + typeString + ' but getting ' + thingType);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.assertAlive = function (thing) {
 | 
			
		||||
    if (thing && thing.isCorpse) {
 | 
			
		||||
        throw new Error('cannot operate on a deleted sprite');
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.reportTypeOf = function (thing) {
 | 
			
		||||
    // answer a string denoting the argument's type
 | 
			
		||||
    var exp;
 | 
			
		||||
| 
						 | 
				
			
			@ -2134,6 +2154,12 @@ Process.prototype.reportTypeOf = function (thing) {
 | 
			
		|||
    if (thing instanceof List) {
 | 
			
		||||
        return 'list';
 | 
			
		||||
    }
 | 
			
		||||
    if (thing instanceof SpriteMorph) {
 | 
			
		||||
        return 'sprite';
 | 
			
		||||
    }
 | 
			
		||||
    if (thing instanceof StageMorph) {
 | 
			
		||||
        return 'stage';
 | 
			
		||||
    }
 | 
			
		||||
    if (thing instanceof Context) {
 | 
			
		||||
        if (thing.expression instanceof RingMorph) {
 | 
			
		||||
            return thing.expression.dataType();
 | 
			
		||||
| 
						 | 
				
			
			@ -2473,6 +2499,11 @@ Process.prototype.getOtherObject = function (name, thisObj, stageObj) {
 | 
			
		|||
    // private, find the sprite indicated by the given name
 | 
			
		||||
    // either onstage or in the World's hand
 | 
			
		||||
 | 
			
		||||
    // experimental: deal with first-class sprites
 | 
			
		||||
    if (isSnapObject(name)) {
 | 
			
		||||
        return name;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var stage = isNil(stageObj) ?
 | 
			
		||||
                thisObj.parentThatIsA(StageMorph) : stageObj,
 | 
			
		||||
        thatObj = null;
 | 
			
		||||
| 
						 | 
				
			
			@ -2564,7 +2595,7 @@ Process.prototype.doGotoObject = function (name) {
 | 
			
		|||
// Process temporary cloning (Scratch-style)
 | 
			
		||||
 | 
			
		||||
Process.prototype.createClone = function (name) {
 | 
			
		||||
    var thisObj = this.homeContext.receiver,
 | 
			
		||||
    var thisObj = this.blockReceiver(),
 | 
			
		||||
        thatObj;
 | 
			
		||||
 | 
			
		||||
    if (!name) {return; }
 | 
			
		||||
| 
						 | 
				
			
			@ -2623,7 +2654,14 @@ Process.prototype.objectTouchingObject = function (thisObj, name) {
 | 
			
		|||
                    thisObj.isTouching(stage.penTrailsMorph())) {
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            those = this.getObjectsNamed(name, thisObj, stage); // clones
 | 
			
		||||
            if (isSnapObject(name)) {
 | 
			
		||||
                return thisObj.isTouching(name);
 | 
			
		||||
            }
 | 
			
		||||
            if (name instanceof List) { // assume all elements to be sprites
 | 
			
		||||
                those = name.itemsArray();
 | 
			
		||||
            } else {
 | 
			
		||||
                those = this.getObjectsNamed(name, thisObj, stage); // clones
 | 
			
		||||
            }
 | 
			
		||||
            if (those.some(function (any) {
 | 
			
		||||
                    return thisObj.isTouching(any);
 | 
			
		||||
                })) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2640,7 +2678,7 @@ Process.prototype.objectTouchingObject = function (thisObj, name) {
 | 
			
		|||
 | 
			
		||||
Process.prototype.reportTouchingColor = function (aColor) {
 | 
			
		||||
    // also check for any parts (subsprites)
 | 
			
		||||
    var thisObj = this.homeContext.receiver,
 | 
			
		||||
    var thisObj = this.blockReceiver(),
 | 
			
		||||
        stage;
 | 
			
		||||
 | 
			
		||||
    if (thisObj) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2661,7 +2699,7 @@ Process.prototype.reportTouchingColor = function (aColor) {
 | 
			
		|||
 | 
			
		||||
Process.prototype.reportColorIsTouchingColor = function (color1, color2) {
 | 
			
		||||
    // also check for any parts (subsprites)
 | 
			
		||||
    var thisObj = this.homeContext.receiver,
 | 
			
		||||
    var thisObj = this.blockReceiver(),
 | 
			
		||||
        stage;
 | 
			
		||||
 | 
			
		||||
    if (thisObj) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2713,6 +2751,7 @@ Process.prototype.reportAttributeOf = function (attribute, name) {
 | 
			
		|||
        stage;
 | 
			
		||||
 | 
			
		||||
    if (thisObj) {
 | 
			
		||||
        this.assertAlive(thisObj);
 | 
			
		||||
        stage = thisObj.parentThatIsA(StageMorph);
 | 
			
		||||
        if (stage.name === name) {
 | 
			
		||||
            thatObj = stage;
 | 
			
		||||
| 
						 | 
				
			
			@ -2720,6 +2759,7 @@ Process.prototype.reportAttributeOf = function (attribute, name) {
 | 
			
		|||
            thatObj = this.getOtherObject(name, thisObj, stage);
 | 
			
		||||
        }
 | 
			
		||||
        if (thatObj) {
 | 
			
		||||
            this.assertAlive(thatObj);
 | 
			
		||||
            if (attribute instanceof Context) {
 | 
			
		||||
                return this.reportContextFor(attribute, thatObj);
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -2747,6 +2787,139 @@ Process.prototype.reportAttributeOf = function (attribute, name) {
 | 
			
		|||
    return '';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.reportGet = function (query) {
 | 
			
		||||
    // experimental, answer a reference to a first-class member
 | 
			
		||||
    // or a list of first-class members
 | 
			
		||||
    var thisObj = this.blockReceiver(),
 | 
			
		||||
        neighborhood,
 | 
			
		||||
        stage,
 | 
			
		||||
        objName;
 | 
			
		||||
 | 
			
		||||
    if (thisObj) {
 | 
			
		||||
        switch (this.inputOption(query)) {
 | 
			
		||||
        case 'self' :
 | 
			
		||||
            return thisObj;
 | 
			
		||||
        case 'other sprites':
 | 
			
		||||
            stage = thisObj.parentThatIsA(StageMorph);
 | 
			
		||||
            return new List(
 | 
			
		||||
                stage.children.filter(function (each) {
 | 
			
		||||
                    return each instanceof SpriteMorph &&
 | 
			
		||||
                        each !== thisObj;
 | 
			
		||||
                })
 | 
			
		||||
            );
 | 
			
		||||
        case 'parts':
 | 
			
		||||
            return new List(thisObj.parts || []);
 | 
			
		||||
        case 'anchor':
 | 
			
		||||
            return thisObj.anchor || '';
 | 
			
		||||
        case 'parent':
 | 
			
		||||
            return thisObj.exemplar || '';
 | 
			
		||||
        case 'children':
 | 
			
		||||
            return new List(thisObj.specimens ? thisObj.specimens() : []);
 | 
			
		||||
        case 'clones':
 | 
			
		||||
            stage = thisObj.parentThatIsA(StageMorph);
 | 
			
		||||
            objName = thisObj.name || thisObj.cloneOriginName;
 | 
			
		||||
            return new List(
 | 
			
		||||
                stage.children.filter(function (each) {
 | 
			
		||||
                    return each.isClone &&
 | 
			
		||||
                        (each !== thisObj) &&
 | 
			
		||||
                        (each.cloneOriginName === objName);
 | 
			
		||||
                })
 | 
			
		||||
            );
 | 
			
		||||
        case 'other clones':
 | 
			
		||||
            return thisObj.isClone ? this.reportGet(['clones']) : new List();
 | 
			
		||||
        case 'neighbors':
 | 
			
		||||
            stage = thisObj.parentThatIsA(StageMorph);
 | 
			
		||||
            neighborhood = thisObj.bounds.expandBy(new Point(
 | 
			
		||||
                thisObj.width(),
 | 
			
		||||
                thisObj.height()
 | 
			
		||||
            ));
 | 
			
		||||
            return new List(
 | 
			
		||||
                stage.children.filter(function (each) {
 | 
			
		||||
                    return each instanceof SpriteMorph &&
 | 
			
		||||
                        (each !== thisObj) &&
 | 
			
		||||
                        each.bounds.intersects(neighborhood);
 | 
			
		||||
                })
 | 
			
		||||
            );
 | 
			
		||||
        case 'dangling?':
 | 
			
		||||
            return !thisObj.rotatesWithAnchor;
 | 
			
		||||
        case 'rotation x':
 | 
			
		||||
            return thisObj.xPosition();
 | 
			
		||||
        case 'rotation y':
 | 
			
		||||
            return thisObj.yPosition();
 | 
			
		||||
        case 'center x':
 | 
			
		||||
            return thisObj.xCenter();
 | 
			
		||||
        case 'center y':
 | 
			
		||||
            return thisObj.yCenter();
 | 
			
		||||
        case 'name':
 | 
			
		||||
            return thisObj.name;
 | 
			
		||||
        case 'stage':
 | 
			
		||||
            return thisObj.parentThatIsA(StageMorph);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    return '';
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.doSet = function (attribute, value) {
 | 
			
		||||
    // experimental, manipulate sprites' attributes
 | 
			
		||||
    var name, rcvr;
 | 
			
		||||
    if (!(attribute instanceof Context)) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    rcvr = this.blockReceiver();
 | 
			
		||||
    this.assertAlive(rcvr);
 | 
			
		||||
    if (!(attribute instanceof Context) ||
 | 
			
		||||
            attribute.expression.selector !== 'reportGet') {
 | 
			
		||||
        throw new Error(localize('unsupported attribute'));
 | 
			
		||||
    }
 | 
			
		||||
    name = attribute.expression.inputs()[0].evaluate();
 | 
			
		||||
    if (name instanceof Array) {
 | 
			
		||||
        name = name[0];
 | 
			
		||||
    }
 | 
			
		||||
    switch (name) {
 | 
			
		||||
    case 'anchor':
 | 
			
		||||
        this.assertType(rcvr, 'sprite');
 | 
			
		||||
        if (value instanceof SpriteMorph) {
 | 
			
		||||
            // avoid circularity here, because the GUI already checks for
 | 
			
		||||
            // conflicts while the user drags parts over prospective targets
 | 
			
		||||
            if (!rcvr.enableNesting || contains(rcvr.allParts(), value)) {
 | 
			
		||||
                throw new Error(
 | 
			
		||||
                    localize('unable to nest\n(disabled or circular?)')
 | 
			
		||||
                );
 | 
			
		||||
            }
 | 
			
		||||
            value.attachPart(rcvr);
 | 
			
		||||
        } else {
 | 
			
		||||
            rcvr.detachFromAnchor();
 | 
			
		||||
        }
 | 
			
		||||
        break;
 | 
			
		||||
    case 'parent':
 | 
			
		||||
        this.assertType(rcvr, 'sprite');
 | 
			
		||||
        value = value instanceof SpriteMorph ? value : null;
 | 
			
		||||
        // needed: circularity avoidance
 | 
			
		||||
        rcvr.setExemplar(value);
 | 
			
		||||
        break;
 | 
			
		||||
    case 'dangling?':
 | 
			
		||||
        this.assertType(rcvr, 'sprite');
 | 
			
		||||
        this.assertType(value, 'Boolean');
 | 
			
		||||
        rcvr.rotatesWithAnchor = !value;
 | 
			
		||||
        rcvr.version = Date.now();
 | 
			
		||||
        break;
 | 
			
		||||
    case 'rotation x':
 | 
			
		||||
        this.assertType(rcvr, 'sprite');
 | 
			
		||||
        this.assertType(value, 'number');
 | 
			
		||||
        rcvr.setRotationX(value);
 | 
			
		||||
        break;
 | 
			
		||||
    case 'rotation y':
 | 
			
		||||
        this.assertType(rcvr, 'sprite');
 | 
			
		||||
        this.assertType(value, 'number');
 | 
			
		||||
        rcvr.setRotationY(value);
 | 
			
		||||
        break;
 | 
			
		||||
    default:
 | 
			
		||||
        throw new Error(
 | 
			
		||||
            '"' + localize(name) + '" ' + localize('is read-only')
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Process.prototype.reportContextFor = function (context, otherObj) {
 | 
			
		||||
    // Private - return a copy of the context
 | 
			
		||||
    // and bind it to another receiver
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Ładowanie…
	
		Reference in New Issue