diff --git a/.gitignore b/.gitignore index f31b3e29..cfe1a910 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store *.swp +.tern-port diff --git a/Backgrounds/BACKGROUNDS b/Backgrounds/BACKGROUNDS new file mode 100644 index 00000000..9492cffc --- /dev/null +++ b/Backgrounds/BACKGROUNDS @@ -0,0 +1,12 @@ +atom-playground.jpg Atom Playground +bedroom1.gif Bedroom 1 +bedroom2.gif Bedroom 2 +berkeley-mural.jpg Berkeley Mural +brick-wall-and-stairs.jpg Brick Wall and Stairs +brick-wall1.jpg Brick Wall 1 +brick-wall2.jpg Brick Wall 2 +desert.gif Desert +night-city-with-street.gif Night City with Street +party-room.jpg Party Room +pathway.jpg Pathway +xy-grid.gif XY Grid diff --git a/Costumes/COSTUMES b/Costumes/COSTUMES new file mode 100644 index 00000000..1c303d77 --- /dev/null +++ b/Costumes/COSTUMES @@ -0,0 +1,32 @@ +alonzo.png Alonzo +bat1-a.png Bat 1a +bat1-b.png Bat 1b +bat2-a.png Bat 2a +bat2-b.png Bat 2b +boy1-standing.gif Boy 1 Standing +boy1-walking.gif Boy 1 Walking +boy2.gif Boy 2 +boy3.gif Boy 3 +cat2.gif Cat 2 +cat3.png Cat 3 +cat4.png Cat 4 +cat5.gif Cat 5 +dog1-a.png Dog 1a +dog1-b.png Dog 1b +dog2-a.png Dog 2a +dog2-b.png Dog 2b +dog2-c.png Dog 2c +dragon1-a.png Dragon 1a +dragon1-b.png Dragon 1b +dragon2.gif Dragon 2 +girl1-standing.gif Girl 1 Standing +girl1-walking.gif Girl 1 Walking +girl2-shouting.gif Girl 2 Shouting +girl2-standing.gif Girl 2 Standing +girl3-basketball.gif Girl 3 Basketball +girl3-running.gif Girl 3 Running +girl3-standing.gif Girl 3 Standing +marissa-crouching.gif Marissa Crouching +marissa-sitting.gif Marissa Sitting +marissa.gif Marissa +unicorn1.png Unicorn diff --git a/Examples/EXAMPLES b/Examples/EXAMPLES new file mode 100644 index 00000000..70b1ec53 --- /dev/null +++ b/Examples/EXAMPLES @@ -0,0 +1,10 @@ +animal-game.xml Animal Game +Codification.xml Codification +copter.xml Copter +count-change.xml Count Change +icecream-visual.xml Icecream Visual +JSfunctions.xml JSfunctions +live-tree.xml Live Tree +swimmer.xml Swimmer +tree.xml Tree +vee.xml Vee diff --git a/Examples/animal-game.xml b/Examples/animal-game.xml index f27d81cd..6a712450 100644 --- a/Examples/animal-game.xml +++ b/Examples/animal-game.xml @@ -1 +1 @@ -Be sure to save the projet after playing so that it will remember the animals you teach it!data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAACT0lEQVR4nO3bQYrqQBRA0d5WT3vYS+hF9zZchn/QICGmKlEjd/DPgSAYBAeXZyVWPq4Q+qi/AP83AZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAU78fH9dr7+ff6+8hQAHfr6/bsf19/Pv4HQCHFhOPZPwfQR4wHIKivBcAtyxnH4m4fkEOLEOziQ8nwAnblNv9Z4IzyPAHbMAtyK8necQAe7ZCG0WoXXiYwS4YyuodYBb60QBHiPAHVvTbivAUZDMCXBg85+QBw4BHiPAgWfDE+FjBDjxaoQC3CfACQG+nwB3iPC9BLjnxXWgm9JzApw440LEFJwT4MRZAZqCYwIcODU+AQ4JcGC9D/AuyOt1eKP6crncnfMzvE2AA5sbDnYi2zp3uVxEOCHALVsXEYPIloHNPmOb1jYBruxNPlfE5xLgwrvisxYcE+DCXShPRibA4wS4sH4I/ewdMeK7J8CVvWdAHglPcPsEeMBoV/T0fQ4R4BGT9ZxnQF4jwAPuIlutE93je54AD1j/rA4fWOdhAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFL/ANRdqvbhGkj8AAAAAElFTkSuQmCCdata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAFoCAYAAACPNyggAAACtUlEQVR4nO3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+Bo3+AAF/RMkcAAAAAElFTkSuQmCC1N1 leafrabbit \ No newline at end of file +Be sure to save the project after playing so that it will remember the animals you teach it!data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAAB4CAYAAAB1ovlvAAACT0lEQVR4nO3bQYrqQBRA0d5WT3vYS+hF9zZchn/QICGmKlEjd/DPgSAYBAeXZyVWPq4Q+qi/AP83AZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAZISICkBkhIgKQGSEiApAU78fH9dr7+ff6+8hQAHfr6/bsf19/Pv4HQCHFhOPZPwfQR4wHIKivBcAtyxnH4m4fkEOLEOziQ8nwAnblNv9Z4IzyPAHbMAtyK8necQAe7ZCG0WoXXiYwS4YyuodYBb60QBHiPAHVvTbivAUZDMCXBg85+QBw4BHiPAgWfDE+FjBDjxaoQC3CfACQG+nwB3iPC9BLjnxXWgm9JzApw440LEFJwT4MRZAZqCYwIcODU+AQ4JcGC9D/AuyOt1eKP6crncnfMzvE2AA5sbDnYi2zp3uVxEOCHALVsXEYPIloHNPmOb1jYBruxNPlfE5xLgwrvisxYcE+DCXShPRibA4wS4sH4I/ewdMeK7J8CVvWdAHglPcPsEeMBoV/T0fQ4R4BGT9ZxnQF4jwAPuIlutE93je54AD1j/rA4fWOdhAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFICJCVAUgIkJUBSAiQlQFL/ANRdqvbhGkj8AAAAAElFTkSuQmCCdata:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAeAAAAFoCAYAAACPNyggAAACtUlEQVR4nO3BMQEAAADCoPVPbQwfoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA+Bo3+AAF/RMkcAAAAAElFTkSuQmCC1N1 leafrabbit diff --git a/FileSaver.min.js b/FileSaver.min.js new file mode 100644 index 00000000..8bbbf769 --- /dev/null +++ b/FileSaver.min.js @@ -0,0 +1,2 @@ +/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */ +var saveAs=saveAs||function(e){"use strict";if(typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),i="download"in r,o=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),f=e.webkitRequestFileSystem,u=e.requestFileSystem||f||e.mozRequestFileSystem,s=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},c="application/octet-stream",d=0,l=500,w=function(t){var r=function(){if(typeof t==="string"){n().revokeObjectURL(t)}else{t.remove()}};if(e.chrome){r()}else{setTimeout(r,l)}},p=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var i=e["on"+t[r]];if(typeof i==="function"){try{i.call(e,n||e)}catch(o){s(o)}}}},v=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob(["\ufeff",e],{type:e.type})}return e},y=function(t,s,l){if(!l){t=v(t)}var y=this,m=t.type,S=false,h,R,O=function(){p(y,"writestart progress write writeend".split(" "))},g=function(){if(R&&a&&typeof FileReader!=="undefined"){var r=new FileReader;r.onloadend=function(){var e=r.result;R.location.href="data:attachment/file"+e.slice(e.search(/[,;]/));y.readyState=y.DONE;O()};r.readAsDataURL(t);y.readyState=y.INIT;return}if(S||!h){h=n().createObjectURL(t)}if(R){R.location.href=h}else{var i=e.open(h,"_blank");if(i==undefined&&a){e.location.href=h}}y.readyState=y.DONE;O();w(h)},b=function(e){return function(){if(y.readyState!==y.DONE){return e.apply(this,arguments)}}},E={create:true,exclusive:false},N;y.readyState=y.INIT;if(!s){s="download"}if(i){h=n().createObjectURL(t);r.href=h;r.download=s;setTimeout(function(){o(r);O();w(h);y.readyState=y.DONE});return}if(e.chrome&&m&&m!==c){N=t.slice||t.webkitSlice;t=N.call(t,0,t.size,c);S=true}if(f&&s!=="download"){s+=".download"}if(m===c||f){R=e}if(!u){g();return}d+=t.size;u(e.TEMPORARY,d,b(function(e){e.root.getDirectory("saved",E,b(function(e){var n=function(){e.getFile(s,E,b(function(e){e.createWriter(b(function(n){n.onwriteend=function(t){R.location.href=e.toURL();y.readyState=y.DONE;p(y,"writeend",t);w(e)};n.onerror=function(){var e=n.error;if(e.code!==e.ABORT_ERR){g()}};"writestart progress write abort".split(" ").forEach(function(e){n["on"+e]=y["on"+e]});n.write(t);y.abort=function(){n.abort();y.readyState=y.DONE};y.readyState=y.WRITING}),g)}),g)};e.getFile(s,{create:false},b(function(e){e.remove();n()}),b(function(e){if(e.code===e.NOT_FOUND_ERR){n()}else{g()}}))}),g)}),g)},m=y.prototype,S=function(e,t,n){return new y(e,t,n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){if(!n){e=v(e)}return navigator.msSaveOrOpenBlob(e,t||"download")}}m.abort=function(){var e=this;e.readyState=e.DONE;p(e,"abort")};m.readyState=m.INIT=0;m.WRITING=1;m.DONE=2;m.error=m.onwritestart=m.onprogress=m.onwrite=m.onabort=m.onerror=m.onwriteend=null;return S}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!=null){define([],function(){return saveAs})} \ No newline at end of file diff --git a/README.md b/README.md index a48f0dd9..0fbc6d0f 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ visual, blocks based programming language inspired by Scratch written by Jens Mönig and Brian Harvey -Copyright (C) 2015 by Jens Mönig and Brian Harvey +Copyright (C) 2016 by Jens Mönig and Brian Harvey This is free software: you can redistribute it and/or modify @@ -30,3 +30,6 @@ GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . + +Want to use Snap! but scared by the open-source license? Get in touch with us, +we'll make it work. diff --git a/Sounds/SOUNDS b/Sounds/SOUNDS new file mode 100644 index 00000000..e5f538f5 --- /dev/null +++ b/Sounds/SOUNDS @@ -0,0 +1,12 @@ +Cat.mp3 Cat +Chord.wav Chord +Dog1.wav Dog 1 +Dog2.wav Dog 2 +FingerSnap.wav Finger Snap +Kitten.wav Kitten +Laugh-female.wav Laugh Female +Laugh-male1.wav Laugh Male 1 +Laugh-male2.wav Laugh Male 2 +Laugh-male3.mp3 Laugh Male 3 +Meow.wav Meow +Pop.wav Pop diff --git a/blocks.js b/blocks.js index 8e16529c..53b8eec2 100644 --- a/blocks.js +++ b/blocks.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -136,27 +136,20 @@ constructor for further details. */ -/*global Array, BlinkerMorph, BouncerMorph, BoxMorph, CircleBoxMorph, -Color, ColorPaletteMorph, ColorPickerMorph, CursorMorph, Date, -FrameMorph, Function, GrayPaletteMorph, HandMorph, HandleMorph, -InspectorMorph, ListMorph, Math, MenuItemMorph, MenuMorph, Morph, -MorphicPreferences, MouseSensorMorph, Node, Object, PenMorph, Point, -Rectangle, ScrollFrameMorph, ShadowMorph, SliderButtonMorph, -SliderMorph, String, StringFieldMorph, StringMorph, TextMorph, -TriggerMorph, WorldMorph, clone, contains, copy, degrees, detect, -document, getDocumentPositionOf, isNaN, isObject, isString, newCanvas, -nop, parseFloat, radians, standardSettings, touchScreenSettings, -useBlurredShadows, version, window, SpeechBubbleMorph, modules, StageMorph, -fontHeight*/ - -/*global SpriteMorph, Context, ListWatcherMorph, CellMorph, -DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/ - -/*global IDE_Morph, BlockDialogMorph, BlockEditorMorph, localize, isNil*/ +/*global Array, BoxMorph, +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, copy, PushButtonMorph, SpriteIconMorph, Process*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2015-August-14'; +modules.blocks = '2016-October-31'; var SyntaxElementMorph; var BlockMorph; @@ -193,7 +186,7 @@ WorldMorph.prototype.customMorphs = function () { /* return [ new SymbolMorph( - 'pipette', + 'stepForward', 50, new Color(250, 250, 250), new Point(-1, -1), @@ -349,22 +342,19 @@ function SyntaxElementMorph() { this.init(); } -SyntaxElementMorph.prototype.init = function () { +SyntaxElementMorph.prototype.init = function (silently) { this.cachedClr = null; this.cachedClrBright = null; this.cachedClrDark = null; + this.cachedNormalColor = null; // for single-stepping this.isStatic = false; // if true, I cannot be exchanged - SyntaxElementMorph.uber.init.call(this); + SyntaxElementMorph.uber.init.call(this, silently); this.defaults = []; this.cachedInputs = null; }; -// SyntaxElementMorph stepping: - -SyntaxElementMorph.prototype.step = null; - // SyntaxElementMorph accessing: SyntaxElementMorph.prototype.parts = function () { @@ -472,8 +462,7 @@ SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) { var scripts = this.parentThatIsA(ScriptsMorph), replacement = newArg, idx = this.children.indexOf(oldArg), - i = 0, - nb; + i = 0; // try to find the ArgLabel embedding the newArg, // used for the undrop() feature @@ -490,6 +479,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); @@ -511,13 +504,6 @@ SyntaxElementMorph.prototype.replaceInput = function (oldArg, newArg) { oldArg.moveBy(replacement.extent()); oldArg.fixBlockColor(); } - } else if (oldArg instanceof CommandSlotMorph) { - nb = oldArg.nestedBlock(); - if (nb) { - scripts.add(nb); - nb.moveBy(replacement.extent()); - nb.fixBlockColor(); - } } if (replacement instanceof MultiArgMorph || replacement instanceof ArgLabelMorph @@ -544,6 +530,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); } @@ -640,6 +629,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() @@ -703,14 +696,16 @@ SyntaxElementMorph.prototype.dark = function () { // SyntaxElementMorph color changing: -SyntaxElementMorph.prototype.setColor = function (aColor) { +SyntaxElementMorph.prototype.setColor = function (aColor, silently) { if (aColor) { if (!this.color.eq(aColor)) { this.color = aColor; - this.drawNew(); + if (!silently) {this.drawNew(); } this.children.forEach(function (child) { - child.drawNew(); - child.changed(); + if (!silently || child instanceof TemplateSlotMorph) { + child.drawNew(); + child.changed(); + } }); this.changed(); } @@ -737,6 +732,20 @@ SyntaxElementMorph.prototype.setLabelColor = function ( }); }; +SyntaxElementMorph.prototype.flash = function () { + if (!this.cachedNormalColor) { + this.cachedNormalColor = this.color; + this.setColor(this.activeHighlight); + } +}; + +SyntaxElementMorph.prototype.unflash = function () { + if (this.cachedNormalColor) { + var clr = this.cachedNormalColor; + this.cachedNormalColor = null; + this.setColor(clr); + } +}; // SyntaxElementMorph zebra coloring @@ -790,6 +799,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; @@ -859,7 +872,8 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { part.isUnevaluated = true; break; case '%txt': - part = new InputSlotMorph(); + part = new InputSlotMorph(); // supports whitespace dots + // part = new TextSlotMorph(); // multi-line, no whitespace dots part.minWidth = part.height() * 1.7; // "landscape" part.fixLayout(); break; @@ -942,6 +956,7 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { }, true // read-only ); + part.isStatic = true; break; case '%dates': part = new InputSlotMorph( @@ -1032,6 +1047,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, @@ -1044,13 +1068,19 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { part = new InputSlotMorph( null, false, - { brightness : ['brightness'], - ghost : ['ghost'], + { color: ['color'], + fisheye: ['fisheye'], + whirl: ['whirl'], + pixelate: ['pixelate'], + mosaic: ['mosaic'], + duplicate: ['duplicate'], negative : ['negative'], comic: ['comic'], - duplicate: ['duplicate'], - confetti: ['confetti'] - }, + confetti: ['confetti'], + saturation: ['saturation'], + brightness : ['brightness'], + ghost: ['ghost'] + }, true ); part.setContents(['ghost']); @@ -1068,6 +1098,7 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { null, false, { + 'any key' : ['any key'], 'up arrow': ['up arrow'], 'down arrow': ['down arrow'], 'right arrow': ['right arrow'], @@ -1149,6 +1180,7 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { false, { abs : ['abs'], + ceiling : ['ceiling'], floor : ['floor'], sqrt : ['sqrt'], sin : ['sin'], @@ -1214,17 +1246,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']); @@ -1275,8 +1297,15 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { part = new ArgMorph('list'); break; case '%b': + part = new BooleanSlotMorph(); + break; case '%boolUE': - part = new BooleanSlotMorph(null, true); + part = new BooleanSlotMorph(); + part.isUnevaluated = true; + break; + case '%bool': + part = new BooleanSlotMorph(true); + part.isStatic = true; break; case '%cmd': part = new CommandSlotMorph(); @@ -1300,6 +1329,11 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { case '%cs': part = new CSlotMorph(); // non-static break; + case '%cl': + part = new CSlotMorph(); + part.isStatic = true; // rejects reporter drops + part.isLambda = true; // auto-reifies nested script + break; case '%clr': part = new ColorSlotMorph(); part.isStatic = true; @@ -1499,7 +1533,7 @@ SyntaxElementMorph.prototype.isObjInputFragment = function () { // SyntaxElementMorph layout: -SyntaxElementMorph.prototype.fixLayout = function () { +SyntaxElementMorph.prototype.fixLayout = function (silently) { var nb, parts = this.parts(), myself = this, @@ -1680,8 +1714,8 @@ SyntaxElementMorph.prototype.fixLayout = function () { } } - // set my extent: - this.setExtent(new Point(blockWidth, blockHeight)); + // set my extent (silently, because we'll redraw later anyway): + this.silentSetExtent(new Point(blockWidth, blockHeight)); // adjust CSlots parts.forEach(function (part) { @@ -1695,7 +1729,7 @@ SyntaxElementMorph.prototype.fixLayout = function () { }); // redraw in order to erase CSlot backgrounds - this.drawNew(); + if (!silently) {this.drawNew(); } // position next block: if (nb) { @@ -1764,6 +1798,10 @@ SyntaxElementMorph.prototype.showBubble = function (value, exportPic) { img, morphToShow, isClickable = false, + ide = this.parentThatIsA(IDE_Morph), + rcvr = this.receiver(), + anchor = this, + pos = this.rightCenter().add(new Point(2, 0)), sf = this.parentThatIsA(ScrollFrameMorph), wrrld = this.world(); @@ -1775,13 +1813,36 @@ SyntaxElementMorph.prototype.showBubble = function (value, exportPic) { morphToShow.update(true); morphToShow.step = value.update; morphToShow.isDraggable = false; + morphToShow.expand(this.parentThatIsA(ScrollFrameMorph).extent()); + isClickable = true; + } else if (value instanceof TableFrameMorph) { + morphToShow = value; + morphToShow.isDraggable = false; + 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(); @@ -1821,6 +1882,17 @@ SyntaxElementMorph.prototype.showBubble = function (value, exportPic) { this.fontSize ); } + if (ide && (ide.currentSprite !== rcvr)) { + if (rcvr instanceof StageMorph) { + anchor = ide.corral.stageIcon; + } else { + anchor = detect( + ide.corral.frame.contents.children, + function (icon) {return icon.object === rcvr; } + ); + } + pos = anchor.center(); + } bubble = new SpeechBubbleMorph( morphToShow, null, @@ -1829,19 +1901,22 @@ SyntaxElementMorph.prototype.showBubble = function (value, exportPic) { ); bubble.popUp( wrrld, - this.rightCenter().add(new Point(2, 0)), + pos, isClickable ); if (exportPic) { this.exportPictureWithResult(bubble); } - if (sf) { + if (anchor instanceof SpriteIconMorph) { + bubble.keepWithin(ide.corral); + } else if (sf) { bubble.keepWithin(sf); } }; SyntaxElementMorph.prototype.exportPictureWithResult = function (aBubble) { - var scr = this.fullImage(), + var ide = this.parentThatIsA(IDE_Morph), + scr = this.fullImage(), bub = aBubble.fullImageClassic(), taller = Math.max(0, bub.height - scr.height), pic = newCanvas(new Point( @@ -1851,7 +1926,12 @@ SyntaxElementMorph.prototype.exportPictureWithResult = function (aBubble) { ctx = pic.getContext('2d'); ctx.drawImage(scr, 0, pic.height - scr.height); ctx.drawImage(bub, scr.width + 2, 0); - window.open(pic.toDataURL()); + // request to open pic in new window. + ide.saveCanvasAs( + pic, + ide.projetName || localize('Untitled') + ' ' + localize('script pic'), + true + ); }; // SyntaxElementMorph code mapping @@ -1925,14 +2005,14 @@ SyntaxElementMorph.prototype.endLayout = function () { arity: single - %br - user-forced line break - %s - white rectangular type-in slot ("string-type") + %br - user-forced line break + %s - white rectangular type-in slot ("string-type") %txt - white rectangular type-in slot ("text-type") %mlt - white rectangular type-in slot ("multi-line-text-type") - %code - white rectangular type-in slot, monospaced font - %n - white roundish type-in slot ("numerical") + %code - white rectangular type-in slot, monospaced font + %n - white roundish type-in slot ("numerical") %dir - white roundish type-in slot with drop-down for directions - %inst - white roundish type-in slot with drop-down for instruments + %inst - white roundish type-in slot with drop-down for instruments %ida - white roundish type-in slot with drop-down for list indices %idx - white roundish type-in slot for indices incl. "any" %obj - specially drawn slot for object reporters @@ -1950,16 +2030,19 @@ SyntaxElementMorph.prototype.endLayout = function () { %var - chameleon colored rectangular drop-down for variable names %shd - Chameleon colored rectuangular drop-down for shadowed var names %lst - chameleon colored rectangular drop-down for list names - %b - chameleon colored hexagonal slot (for predicates) - %l - list icon - %c - C-shaped command slot + %b - chameleon colored hexagonal slot (for predicates) + %bool - chameleon colored hexagonal slot (for predicates), static + %l - list icon + %c - C-shaped command slot, special form for primitives + %cs - C-shaped, auto-reifying, accepts reporter drops + %cl - C-shaped, auto-reifying, rejects reporters %clr - interactive color slot - %t - inline variable reporter template - %anyUE - white rectangular type-in slot, unevaluated if replaced - %boolUE - chameleon colored hexagonal slot, unevaluated if replaced - %f - round function slot, unevaluated if replaced, - %r - round reporter slot - %p - hexagonal predicate slot + %t - inline variable reporter template + %anyUE - white rectangular type-in slot, unevaluated if replaced + %boolUE - chameleon colored hexagonal slot, unevaluated if replaced + %f - round function slot, unevaluated if replaced, + %r - round reporter slot + %p - hexagonal predicate slot rings: @@ -2025,7 +2108,7 @@ function BlockMorph() { this.init(); } -BlockMorph.prototype.init = function () { +BlockMorph.prototype.init = function (silently) { this.selector = null; // name of method to be triggered this.blockSpec = ''; // formal description of label and arguments this.comment = null; // optional "sticky" comment morph @@ -2034,7 +2117,7 @@ BlockMorph.prototype.init = function () { this.instantiationSpec = null; // spec to set upon fullCopy() of template this.category = null; // for zebra coloring (non persistent) - BlockMorph.uber.init.call(this); + BlockMorph.uber.init.call(this, silently); this.color = new Color(0, 17, 173); this.cashedInputs = null; }; @@ -2099,7 +2182,7 @@ BlockMorph.prototype.parseSpec = function (spec) { return result; }; -BlockMorph.prototype.setSpec = function (spec) { +BlockMorph.prototype.setSpec = function (spec, silently) { var myself = this, part, inputIdx = -1; @@ -2124,11 +2207,9 @@ BlockMorph.prototype.setSpec = function (spec) { 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) { @@ -2142,10 +2223,17 @@ BlockMorph.prototype.setSpec = function (spec) { } }); this.blockSpec = spec; - this.fixLayout(); + this.fixLayout(silently); this.cachedInputs = null; }; +BlockMorph.prototype.userSetSpec = function (spec) { + var tb = this.topBlock(); + tb.fullChanged(); + this.setSpec(spec); + tb.fullChanged(); +}; + BlockMorph.prototype.buildSpec = function () { // create my blockSpec from my parts - for demo purposes only var myself = this; @@ -2186,11 +2274,22 @@ BlockMorph.prototype.userMenu = function () { myself = this, shiftClicked = world.currentKey === 16, proc = this.activeProcess(), - vNames = proc ? proc.context.outerContext.variables.names() : [], + vNames = proc && proc.context && proc.context.outerContext ? + proc.context.outerContext.variables.names() : [], alternatives, top, blck; + function addOption(label, toggle, test, onHint, offHint) { + var on = '\u2611 ', + off = '\u2610 '; + menu.addItem( + (test ? on : off) + localize(label), + toggle, + test ? onHint : offHint + ); + } + menu.addItem( "help...", 'showHelp' @@ -2211,7 +2310,15 @@ BlockMorph.prototype.userMenu = function () { } if (this.isTemplate) { if (!(this.parent instanceof SyntaxElementMorph)) { - if (this.selector !== 'evaluateCustomBlock') { + if (this.selector === 'reportGetVar') { + addOption( + 'transient', + 'toggleTransientVariable', + myself.isTransientVariable(), + 'uncheck to save contents\nin the project', + 'check to prevent contents\nfrom being saved' + ); + } else if (this.selector !== 'evaluateCustomBlock') { menu.addItem( "hide", 'hidePrimitive' @@ -2240,7 +2347,7 @@ BlockMorph.prototype.userMenu = function () { function () { new DialogBoxMorph( myself, - myself.setSpec, + myself.userSetSpec, myself ).prompt( "Variable name", @@ -2311,7 +2418,16 @@ BlockMorph.prototype.userMenu = function () { menu.addItem( "script pic...", function () { - window.open(myself.topBlock().fullImage().toDataURL()); + var ide = myself.parentThatIsA(IDE_Morph) || + myself.parentThatIsA(BlockEditorMorph).target.parentThatIsA( + IDE_Morph + ); + ide.saveCanvasAs( + myself.topBlock().scriptPic(), + ide.projetName || localize('Untitled') + ' ' + + localize('script pic'), + true // request new window + ); }, 'open a new window\nwith a picture of this script' ); @@ -2329,7 +2445,7 @@ BlockMorph.prototype.userMenu = function () { } return menu; } - if (this.parentThatIsA(RingMorph)) { + if (this.parent.parentThatIsA(RingMorph)) { menu.addLine(); menu.addItem("unringify", 'unringify'); menu.addItem("ringify", 'ringify'); @@ -2366,7 +2482,7 @@ BlockMorph.prototype.developersMenu = function () { new DialogBoxMorph( this, - this.setSpec, + this.userSetSpec, this ).prompt( menu.title + '\nspec', @@ -2396,6 +2512,20 @@ BlockMorph.prototype.hidePrimitive = function () { ide.refreshPalette(); }; +BlockMorph.prototype.isTransientVariable = function () { + // private - only for variable getter template inside the palette + var varFrame = this.receiver().variables.silentFind(this.blockSpec); + return varFrame ? varFrame.vars[this.blockSpec].isTransient : false; +}; + +BlockMorph.prototype.toggleTransientVariable = function () { + // private - only for variable getter template inside the palette + var varFrame = this.receiver().variables.silentFind(this.blockSpec); + if (!varFrame) {return; } + varFrame.vars[this.blockSpec].isTransient = + !(varFrame.vars[this.blockSpec].isTransient); +}; + BlockMorph.prototype.deleteBlock = function () { // delete just this one block, keep inputs and next block around var scripts = this.parentThatIsA(ScriptsMorph), @@ -2412,10 +2542,10 @@ BlockMorph.prototype.deleteBlock = function () { } }); } - if (this instanceof ReporterBlockMorph) { - if (this.parent instanceof BlockMorph) { - this.parent.revertToDefaultInput(this); - } + if ((this.parent instanceof BlockMorph) + || (this.parent instanceof MultiArgMorph) + || (this.parent instanceof ReporterSlotMorph)) { + this.parent.revertToDefaultInput(this); } else { // CommandBlockMorph if (this.parent) { if (this.parent.fixLayout) { @@ -2446,6 +2576,7 @@ BlockMorph.prototype.ringify = function () { center = top.fullBounds().center(); if (this.parent === null) {return null; } + top.fullChanged(); if (this.parent instanceof SyntaxElementMorph) { if (this instanceof ReporterBlockMorph) { this.parent.silentReplaceInput(this, ring); @@ -2461,11 +2592,14 @@ BlockMorph.prototype.ringify = function () { ring.setCenter(center); } this.fixBlockColor(null, true); + top.fullChanged(); + }; BlockMorph.prototype.unringify = function () { // remove a Ring around me, if any - var ring = this.parentThatIsA(RingMorph), + var ring = this.parent.parentThatIsA(RingMorph), + top = this.topBlock(), scripts = this.parentThatIsA(ScriptsMorph), block, center; @@ -2474,6 +2608,7 @@ BlockMorph.prototype.unringify = function () { block = ring.contents(); center = ring.center(); + top.fullChanged(); if (ring.parent instanceof SyntaxElementMorph) { if (block instanceof ReporterBlockMorph) { ring.parent.silentReplaceInput(ring, block); @@ -2489,6 +2624,7 @@ BlockMorph.prototype.unringify = function () { ring.destroy(); } this.fixBlockColor(null, true); + top.fullChanged(); }; BlockMorph.prototype.relabel = function (alternativeSelectors) { @@ -2557,6 +2693,8 @@ BlockMorph.prototype.restoreInputs = function (oldInputs) { BlockMorph.prototype.showHelp = function () { var myself = this, + ide = this.parentThatIsA(IDE_Morph), + blockEditor, pic = new Image(), help, comment, @@ -2566,8 +2704,15 @@ BlockMorph.prototype.showHelp = function () { this.definition.helpSpec() : this.selector, ctx; + if (!ide) { + blockEditor = this.parentThatIsA(BlockEditorMorph); + if (blockEditor) { + ide = blockEditor.target.parentThatIsA(IDE_Morph); + } + } + pic.onload = function () { - help = newCanvas(new Point(pic.width, pic.height)); + help = newCanvas(new Point(pic.width, pic.height), true); // nonRetina ctx = help.getContext('2d'); ctx.drawImage(pic, 0, 0); new DialogBoxMorph().inform( @@ -2594,7 +2739,7 @@ BlockMorph.prototype.showHelp = function () { block.fullImage() ); } else { - pic.src = 'help/' + spec + '.png'; + pic.src = ide.resourceURL('help', spec + '.png'); } }; @@ -2846,7 +2991,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, @@ -2885,12 +3030,13 @@ BlockMorph.prototype.eraseHoles = function (context) { var w = hole.width(), h = Math.floor(hole.height()) - 2; // Opera needs this context.clearRect( - Math.floor(hole.bounds.origin.x - myself.bounds.origin.x) + 1, - Math.floor(hole.bounds.origin.y - myself.bounds.origin.y) + 1, - isReporter ? w - 1 : w + 1, + hole.bounds.origin.x - myself.bounds.origin.x + 1, + hole.bounds.origin.y - myself.bounds.origin.y + 1, + isRing ? w - 2 : w + 1, h ); }); + }; // BlockMorph highlighting @@ -3079,7 +3225,7 @@ BlockMorph.prototype.fixBlockColor = function (nearestBlock, isForced) { BlockMorph.prototype.forceNormalColoring = function () { var clr = SpriteMorph.prototype.blockColor[this.category]; - this.setColor(clr); + this.setColor(clr, true); // silently this.setLabelColor( new Color(255, 255, 255), clr.darker(this.labelContrast), @@ -3094,10 +3240,11 @@ BlockMorph.prototype.alternateBlockColor = function () { if (this.color.eq(clr)) { this.setColor( this.zebraContrast < 0 ? clr.darker(Math.abs(this.zebraContrast)) - : clr.lighter(this.zebraContrast) + : clr.lighter(this.zebraContrast), + this.hasLabels() // silently ); } else { - this.setColor(clr); + this.setColor(clr, this.hasLabels()); // silently } this.fixLabelColor(); this.fixChildrensBlockColor(true); // has issues if not forced @@ -3136,6 +3283,9 @@ BlockMorph.prototype.fixChildrensBlockColor = function (isForced) { morph.fixBlockColor(null, isForced); } else if (morph instanceof SyntaxElementMorph) { morph.fixBlockColor(myself, isForced); + if (morph instanceof BooleanSlotMorph) { + morph.drawNew(); + } } }); }; @@ -3147,9 +3297,22 @@ BlockMorph.prototype.setCategory = function (aString) { this.endLayout(); }; +BlockMorph.prototype.hasLabels = function () { + return this.children.some(function (any) { + return any instanceof StringMorph; + }); +}; + // BlockMorph copying -BlockMorph.prototype.fullCopy = function () { +BlockMorph.prototype.fullCopy = function (forClone) { + if (forClone) { + if (this.hasBlockVars()) { + forClone = false; + } else { + return copy(this); + } + } var ans = BlockMorph.uber.fullCopy.call(this); ans.removeHighlight(); ans.isDraggable = true; @@ -3159,19 +3322,15 @@ BlockMorph.prototype.fullCopy = function () { ans.allChildren().filter(function (block) { if (block instanceof SyntaxElementMorph) { block.cachedInputs = null; - if (block instanceof InputSlotMorph) { - block.contents().clearSelection(); + if (block.definition) { + block.initializeVariables(); } - } else if (block instanceof CursorMorph) { - block.destroy(); } return !isNil(block.comment); }).forEach(function (block) { var cmnt = block.comment.fullCopy(); block.comment = cmnt; cmnt.block = block; - //block.comment = null; - }); ans.cachedInputs = null; return ans; @@ -3181,6 +3340,12 @@ BlockMorph.prototype.reactToTemplateCopy = function () { this.forceNormalColoring(); }; +BlockMorph.prototype.hasBlockVars = function () { + return this.anyChild(function (any) { + return any.definition && any.definition.variableNames.length; + }); +}; + // BlockMorph events BlockMorph.prototype.mouseClickLeft = function () { @@ -3233,7 +3398,7 @@ BlockMorph.prototype.activeProcess = function () { return null; }; -// BlockMorph thumbnail +// BlockMorph thumbnail and script pic BlockMorph.prototype.thumbnail = function (scale, clipWidth) { var nb = this.nextBlock(), @@ -3275,6 +3440,23 @@ BlockMorph.prototype.thumbnail = function (scale, clipWidth) { return trgt; }; +BlockMorph.prototype.scriptPic = function () { + // answer a canvas image that also includes comments + var scr = this.fullImage(), + fb = this.stackFullBounds(), + pic = newCanvas(fb.extent()), + ctx = pic.getContext('2d'); + this.allComments().forEach(function (comment) { + ctx.drawImage( + comment.fullImageClassic(), + comment.fullBounds().left() - fb.left(), + comment.top() - fb.top() + ); + }); + ctx.drawImage(scr, 0, 0); + return pic; +}; + // BlockMorph dragging and dropping BlockMorph.prototype.rootForGrab = function () { @@ -3329,6 +3511,7 @@ BlockMorph.prototype.prepareToBeGrabbed = function (hand) { }; BlockMorph.prototype.justDropped = function () { + this.alpha = 1; this.allComments().forEach(function (comment) { comment.stopFollowing(); }); @@ -3346,6 +3529,12 @@ BlockMorph.prototype.destroy = function () { this.allComments().forEach(function (comment) { comment.destroy(); }); + + if (!this.parent || !this.parent.topBlock + && this.activeProcess()) { + this.activeProcess().stop(); + } + BlockMorph.uber.destroy.call(this); }; @@ -3357,8 +3546,27 @@ BlockMorph.prototype.stackHeight = function () { return Math.max(fb.bottom(), commentsBottom) - fb.top(); }; +BlockMorph.prototype.stackFullBounds = function () { + var fb = this.fullBounds(); + this.allComments().forEach(function (comment) { + fb.mergeWith(comment.bounds); + }); + return fb; +}; + +BlockMorph.prototype.stackWidth = function () { + var fb = this.fullBounds(), + commentsRight = Math.max(this.allComments().map( + function (comment) {return comment.right(); } + )) || this.right(); + return Math.max(fb.right(), commentsRight) - fb.left(); +}; + BlockMorph.prototype.snap = function () { - var top = this.topBlock(); + var top = this.topBlock(), + receiver, + stage, + ide; top.allComments().forEach(function (comment) { comment.align(top); }); @@ -3369,6 +3577,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 /////////////////////////////////////////////////// @@ -3401,11 +3624,12 @@ function CommandBlockMorph() { this.init(); } -CommandBlockMorph.prototype.init = function () { - CommandBlockMorph.uber.init.call(this); - this.setExtent(new Point(200, 100)); +CommandBlockMorph.prototype.init = function (silently) { + CommandBlockMorph.uber.init.call(this, silently); + this.setExtent(new Point(200, 100), silently); this.partOfCustomCommand = false; this.exitTag = null; + // this.cachedNextBlock = null; // don't serialize }; // CommandBlockMorph enumerating: @@ -3433,14 +3657,32 @@ 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); } - this.fixLayout(); + block.setPosition( + new Point( + this.left(), + this.bottom() - (this.corner) + ) + ); if (affected) { 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) { @@ -3508,6 +3750,9 @@ CommandBlockMorph.prototype.allAttachTargets = function (newParent) { answer = [], topBlocks; + if (this instanceof HatBlockMorph && newParent.rejectsHats) { + return answer; + } topBlocks = target.children.filter(function (child) { return (child !== myself) && child instanceof SyntaxElementMorph && @@ -3545,7 +3790,7 @@ CommandBlockMorph.prototype.closestAttachTarget = function (newParent) { } ); } - if (!this.isStop()) { + if (!bottomBlock.isStop()) { ref.push( { point: bottomBlock.bottomAttachPoint(), @@ -3659,6 +3904,7 @@ CommandBlockMorph.prototype.userDestroyJustThis = function () { above, cslot = this.parentThatIsA(CSlotMorph); + this.topBlock().fullChanged(); if (this.parent) { pb = this.parent.parentThatIsA(CommandBlockMorph); } @@ -4082,7 +4328,7 @@ function HatBlockMorph() { } HatBlockMorph.prototype.init = function () { - HatBlockMorph.uber.init.call(this); + HatBlockMorph.uber.init.call(this, true); // silently this.setExtent(new Point(300, 150)); }; @@ -4259,10 +4505,11 @@ function ReporterBlockMorph(isPredicate) { this.init(isPredicate); } -ReporterBlockMorph.prototype.init = function (isPredicate) { - ReporterBlockMorph.uber.init.call(this); +ReporterBlockMorph.prototype.init = function (isPredicate, silently) { + ReporterBlockMorph.uber.init.call(this, silently); this.isPredicate = isPredicate || false; - this.setExtent(new Point(200, 80)); + this.setExtent(new Point(200, 80), silently); + this.cachedSlotSpec = null; // don't serialize }; // ReporterBlockMorph drag & drop: @@ -4270,9 +4517,11 @@ ReporterBlockMorph.prototype.init = function (isPredicate) { ReporterBlockMorph.prototype.snap = function (hand) { // passing the hand is optional (for when blocks are dragged & dropped) var scripts = this.parent, + nb, target; - if (!scripts instanceof ScriptsMorph) { + this.cachedSlotSpec = null; + if (!(scripts instanceof ScriptsMorph)) { return null; } @@ -4286,6 +4535,16 @@ ReporterBlockMorph.prototype.snap = function (hand) { if (target instanceof MultiArgMorph) { scripts.lastPreservedBlocks = target.inputs(); scripts.lastReplacedInput = target.fullCopy(); + } else if (target instanceof CommandSlotMorph) { + scripts.lastReplacedInput = target; + nb = target.nestedBlock(); + if (nb) { + nb = nb.fullCopy(); + scripts.add(nb); + nb.moveBy(nb.extent()); + nb.fixBlockColor(); + scripts.lastPreservedBlocks = [nb]; + } } target.parent.replaceInput(target, this); if (this.snapSound) { @@ -4309,6 +4568,8 @@ ReporterBlockMorph.prototype.prepareToBeGrabbed = function (handMorph) { this.setPosition(oldPos); } ReporterBlockMorph.uber.prepareToBeGrabbed.call(this, handMorph); + this.alpha = 0.85; + this.cachedSlotSpec = null; }; // ReporterBlockMorph enumerating @@ -4321,11 +4582,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 () { @@ -4335,6 +4597,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( @@ -4361,19 +4645,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.setSpec, + this.userSetSpec, this ).prompt( - isRing ? "Input name" : "Script variable name", + label, this.blockSpec, this.world() ); @@ -4403,6 +4693,7 @@ ReporterBlockMorph.prototype.ExportResultPic = function () { ReporterBlockMorph.prototype.userDestroy = function () { // make sure to restore default slot of parent block + this.topBlock().fullChanged(); this.prepareToBeGrabbed(this.world().hand); this.destroy(); }; @@ -4830,7 +5121,13 @@ RingMorph.prototype.embed = function (aBlock, inputNames) { this.selector = 'reifyPredicate'; slot = this.parts()[0]; slot.silentReplaceInput(slot.contents(), aBlock); - } else { // reporter + } else if (aBlock instanceof BooleanSlotMorph) { + this.isStatic = false; + this.setSpec('%rp %ringparms'); + this.selector = 'reifyPredicate'; + slot = this.parts()[0]; + slot.silentReplaceInput(slot.contents(), aBlock); + } else { // reporter or input slot) this.isStatic = false; this.setSpec('%rr %ringparms'); this.selector = 'reifyReporter'; @@ -4891,9 +5188,7 @@ RingMorph.prototype.dataType = function () { RingMorph.prototype.fixBlockColor = function (nearest, isForced) { var slot = this.parts()[0]; RingMorph.uber.fixBlockColor.call(this, nearest, isForced); - if (slot instanceof RingCommandSlotMorph) { - slot.fixLayout(); - } + slot.fixLayout(); }; // ScriptsMorph //////////////////////////////////////////////////////// @@ -4931,6 +5226,7 @@ ScriptsMorph.prototype.init = function (owner) { this.owner = owner || null; this.feedbackColor = SyntaxElementMorph.prototype.feedbackColor; this.feedbackMorph = new BoxMorph(); + this.rejectsHats = false; // "undrop" attributes: this.lastDroppedBlock = null; @@ -4944,11 +5240,12 @@ ScriptsMorph.prototype.init = function (owner) { ScriptsMorph.uber.init.call(this); this.setColor(new Color(70, 70, 70)); + this.noticesTransparentClick = true; }; // ScriptsMorph deep copying: -ScriptsMorph.prototype.fullCopy = function () { +ScriptsMorph.prototype.fullCopy = function (forClone) { var cpy = new ScriptsMorph(), pos = this.position(), child; @@ -4957,17 +5254,21 @@ ScriptsMorph.prototype.fullCopy = function () { } this.children.forEach(function (morph) { if (!morph.block) { // omit anchored comments - child = morph.fullCopy(); - child.setPosition(morph.position().subtract(pos)); + child = morph.fullCopy(forClone); cpy.add(child); - if (child instanceof BlockMorph) { - child.allComments().forEach(function (comment) { - comment.align(child); - }); + if (!forClone) { + child.setPosition(morph.position().subtract(pos)); + if (child instanceof BlockMorph) { + child.allComments().forEach(function (comment) { + comment.align(child); + }); + } } } }); - cpy.adjustBounds(); + if (!forClone) { + cpy.adjustBounds(); + } return cpy; }; @@ -5137,14 +5438,15 @@ ScriptsMorph.prototype.closestInput = function (reporter, hand) { all, function (input) { return (input instanceof InputSlotMorph - || input instanceof ArgMorph + || (input instanceof ArgMorph + && !(input instanceof CommandSlotMorph) + && !(input instanceof MultiArgMorph)) || (input instanceof RingMorph && !input.contents()) || input.isEmptySlot()) && !input.isLocked() && input.bounds.containsPoint(handPos) - && !contains(blackList, input) - && touchingVariadicArrowsIfAny(input, handPos); + && !contains(blackList, input); } ); if (target) { @@ -5331,9 +5633,15 @@ ScriptsMorph.prototype.cleanUp = function () { }; ScriptsMorph.prototype.exportScriptsPicture = function () { - var pic = this.scriptsPicture(); + var pic = this.scriptsPicture(), + ide = this.world().children[0]; if (pic) { - window.open(pic.toDataURL()); + ide.saveCanvasAs( + pic, + ide.projetName || localize('Untitled') + ' ' + + localize('script pic'), + true // request new window + ); } }; @@ -5350,7 +5658,7 @@ ScriptsMorph.prototype.scriptsPicture = function () { pic = newCanvas(boundingBox.extent()); ctx = pic.getContext('2d'); this.children.forEach(function (child) { - var pos = child.position(); + var pos = child.fullBounds().origin; if (child.isVisible) { ctx.drawImage( child.fullImageClassic(), @@ -5418,6 +5726,20 @@ ScriptsMorph.prototype.clearDropHistory = function () { this.lastNextBlock = null; }; +// ScriptsMorph sorting blocks and comments + +ScriptsMorph.prototype.sortedElements = function () { + // return all scripts and unattached comments + var scripts = this.children.filter(function (each) { + return each instanceof CommentMorph ? !each.block : true; + }); + scripts.sort(function (a, b) { + // make sure the prototype hat block always stays on top + return a instanceof PrototypeHatBlockMorph ? 0 : a.top() - b.top(); + }); + return scripts; +}; + // ScriptsMorph blocks layout fix ScriptsMorph.prototype.fixMultiArgs = function () { @@ -5436,6 +5758,9 @@ ScriptsMorph.prototype.fixMultiArgs = function () { ScriptsMorph.prototype.wantsDropOf = function (aMorph) { // override the inherited method + if (aMorph instanceof HatBlockMorph) { + return !this.rejectsHats; + } return aMorph instanceof SyntaxElementMorph || aMorph instanceof CommentMorph; }; @@ -5493,14 +5818,49 @@ function ArgMorph(type) { this.init(type); } -ArgMorph.prototype.init = function (type) { +ArgMorph.prototype.init = function (type, silently) { this.type = type || null; this.isHole = false; - ArgMorph.uber.init.call(this); + ArgMorph.uber.init.call(this, silently); this.color = new Color(0, 17, 173); - this.setExtent(new Point(50, 50)); + this.setExtent(new Point(50, 50), silently); }; +// ArgMorph preferences settings: + +ArgMorph.prototype.executeOnSliderEdit = false; + +// ArgMorph events: + +ArgMorph.prototype.reactToSliderEdit = function () { +/* + directly execute the stack of blocks I'm part of if my + "executeOnSliderEdit" setting is turned on, obeying the stage's + thread safety setting. This feature allows for "Bret Victor" style + interactive coding. +*/ + var block, top, receiver, stage; + if (!this.executeOnSliderEdit) {return; } + block = this.parentThatIsA(BlockMorph); + if (block) { + top = block.topBlock(); + receiver = top.receiver(); + if (top instanceof PrototypeHatBlockMorph) { + return; + } + if (receiver) { + stage = receiver.parentThatIsA(StageMorph); + if (stage && (stage.isThreadSafe || + Process.prototype.enableSingleStepping)) { + stage.threads.startProcess(top, stage.isThreadSafe); + } else { + top.mouseClickLeft(); + } + } + } +}; + + // ArgMorph drag & drop: for demo puposes only ArgMorph.prototype.justDropped = function () { @@ -5606,10 +5966,13 @@ function CommandSlotMorph() { this.init(); } -CommandSlotMorph.prototype.init = function () { - CommandSlotMorph.uber.init.call(this); +CommandSlotMorph.prototype.init = function (silently) { + CommandSlotMorph.uber.init.call(this, null, true); // silently this.color = new Color(0, 17, 173); - this.setExtent(new Point(230, this.corner * 4 + this.cSlotPadding)); + this.setExtent( + new Point(230, this.corner * 4 + this.cSlotPadding), + silently + ); }; CommandSlotMorph.prototype.getSpec = function () { @@ -6061,8 +6424,8 @@ function RingCommandSlotMorph() { this.init(); } -RingCommandSlotMorph.prototype.init = function () { - RingCommandSlotMorph.uber.init.call(this); +RingCommandSlotMorph.prototype.init = function (silently) { + RingCommandSlotMorph.uber.init.call(this, silently); this.isHole = true; this.noticesTransparentClick = true; this.color = new Color(0, 17, 173); @@ -6212,11 +6575,15 @@ function CSlotMorph() { this.init(); } -CSlotMorph.prototype.init = function () { - CommandSlotMorph.uber.init.call(this); +CSlotMorph.prototype.init = function (silently) { + CommandSlotMorph.uber.init.call(this, null, true); // silently this.isHole = true; + this.isLambda = false; // see Process.prototype.evaluateInput this.color = new Color(0, 17, 173); - this.setExtent(new Point(230, this.corner * 4 + this.cSlotPadding)); + this.setExtent( + new Point(230, this.corner * 4 + this.cSlotPadding), + silently + ); }; CSlotMorph.prototype.getSpec = function () { @@ -6629,10 +6996,6 @@ InputSlotMorph.prototype = new ArgMorph(); InputSlotMorph.prototype.constructor = InputSlotMorph; InputSlotMorph.uber = ArgMorph.prototype; -// InputSlotMorph preferences settings: - -InputSlotMorph.prototype.executeOnSliderEdit = false; - // InputSlotMorph instance creation: function InputSlotMorph(text, isNumeric, choiceDict, isReadOnly) { @@ -6664,7 +7027,7 @@ InputSlotMorph.prototype.init = function ( this.minWidth = 0; // can be chaged for text-type inputs ("landscape") this.constant = null; - InputSlotMorph.uber.init.call(this); + InputSlotMorph.uber.init.call(this, null, true); this.color = new Color(255, 255, 255); this.add(contents); this.add(arrow); @@ -6783,7 +7146,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()); } }); @@ -6817,7 +7180,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()); } }); @@ -6935,6 +7298,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(), @@ -7095,7 +7500,8 @@ InputSlotMorph.prototype.fixLayout = function () { + this.typeInPadding * 2, contents.rawHeight ? // single vs. multi-line contents contents.rawHeight() + arrowWidth - : contents.height() / 1.2 + arrowWidth, + : fontHeight(contents.fontSize) / 1.3 + + arrowWidth, this.minWidth // for text-type slots ); } @@ -7139,7 +7545,6 @@ InputSlotMorph.prototype.mouseDownLeft = function (pos) { this.escalateEvent('mouseDownLeft', pos); } else { this.contents().edit(); - this.contents().selectAll(); } }; @@ -7150,7 +7555,6 @@ InputSlotMorph.prototype.mouseClickLeft = function (pos) { this.dropDownMenu(); } else { this.contents().edit(); - this.contents().selectAll(); } }; @@ -7168,31 +7572,10 @@ InputSlotMorph.prototype.reactToEdit = function () { this.contents().clearSelection(); }; -InputSlotMorph.prototype.reactToSliderEdit = function () { -/* - directly execute the stack of blocks I'm part of if my - "executeOnSliderEdit" setting is turned on, obeying the stage's - thread safety setting. This feature allows for "Bret Victor" style - interactive coding. -*/ - var block, top, receiver, stage; - if (!this.executeOnSliderEdit) {return; } - block = this.parentThatIsA(BlockMorph); - if (block) { - top = block.topBlock(); - receiver = top.receiver(); - if (top instanceof PrototypeHatBlockMorph) { - return; - } - if (receiver) { - stage = receiver.parentThatIsA(StageMorph); - if (stage && stage.isThreadSafe) { - stage.threads.startProcess(top, stage.isThreadSafe); - } else { - top.mouseClickLeft(); - } - } - } +InputSlotMorph.prototype.freshTextEdit = function (aStringOrTextMorph) { + this.onNextStep = function () { + aStringOrTextMorph.selectAll(); + }; }; // InputSlotMorph menu: @@ -7276,6 +7659,29 @@ InputSlotMorph.prototype.isEmptySlot = function () { return this.contents().text === ''; }; +// InputSlotMorph single-stepping: + +InputSlotMorph.prototype.flash = function () { + // don't redraw the label b/c zebra coloring + if (!this.cachedNormalColor) { + this.cachedNormalColor = this.color; + this.color = this.activeHighlight; + this.drawNew(); + this.changed(); + } +}; + +InputSlotMorph.prototype.unflash = function () { + // don't redraw the label b/c zebra coloring + if (this.cachedNormalColor) { + var clr = this.cachedNormalColor; + this.cachedNormalColor = null; + this.color = clr; + this.drawNew(); + this.changed(); + } +}; + // InputSlotMorph drawing: InputSlotMorph.prototype.drawNew = function () { @@ -7284,13 +7690,15 @@ InputSlotMorph.prototype.drawNew = function () { // initialize my surface property this.image = newCanvas(this.extent()); context = this.image.getContext('2d'); - if (this.parent) { + if (this.cachedNormalColor) { // if flashing + borderColor = this.color; + } else if (this.parent) { borderColor = this.parent.color; } else { borderColor = new Color(120, 120, 120); } context.fillStyle = this.color.toString(); - if (this.isReadOnly) { + if (this.isReadOnly && !this.cachedNormalColor) { // unless flashing context.fillStyle = borderColor.darker().toString(); } @@ -7631,6 +8039,16 @@ TemplateSlotMorph.prototype.drawNew = function () { TemplateSlotMorph.prototype.drawRounded = ReporterBlockMorph .prototype.drawRounded; +// TemplateSlotMorph single-stepping + +TemplateSlotMorph.prototype.flash = function () { + this.template().flash(); +}; + +TemplateSlotMorph.prototype.unflash = function () { + this.template().unflash(); +}; + // BooleanSlotMorph //////////////////////////////////////////////////// /* @@ -7640,7 +8058,16 @@ TemplateSlotMorph.prototype.drawRounded = ReporterBlockMorph %b - Boolean %boolUE - Boolean unevaluated - evaluate returns null + I can be directly edited. When the user clicks on me I toggle + between , and values. + + evaluate returns my value. + + my most important public attributes and accessors are: + + value - user editable contents (Boolean or null) + setContents(Boolean/null) - display the argument (Boolean or null) + */ // BooleanSlotMorph inherits from ArgMorph: @@ -7651,57 +8078,196 @@ BooleanSlotMorph.uber = ArgMorph.prototype; // BooleanSlotMorph instance creation: -function BooleanSlotMorph() { - this.init(); +function BooleanSlotMorph(initialValue) { + this.init(initialValue); } -BooleanSlotMorph.prototype.init = function () { +BooleanSlotMorph.prototype.init = function (initialValue) { + this.value = (typeof initialValue === 'boolean') ? initialValue : null; + this.isUnevaluated = false; BooleanSlotMorph.uber.init.call(this); }; BooleanSlotMorph.prototype.getSpec = function () { - return '%b'; + return this.isUnevaluated ? '%boolUE' : '%b'; +}; + +// BooleanSlotMorph accessing: + +BooleanSlotMorph.prototype.evaluate = function () { + return this.value; +}; + +BooleanSlotMorph.prototype.isEmptySlot = function () { + return this.value === null; +}; + +BooleanSlotMorph.prototype.setContents = function (boolOrNull, silently) { + this.value = (typeof boolOrNull === 'boolean') ? boolOrNull : null; + if (silently) {return; } + this.drawNew(); + this.changed(); +}; + +BooleanSlotMorph.prototype.toggleValue = function () { + var ide = this.parentThatIsA(IDE_Morph); + if (this.isStatic) { + this.setContents(!this.value, true); + } else { + switch (this.value) { + case true: + this.value = false; + break; + case false: + this.value = null; + break; + default: + this.value = true; + } + } + if (ide && !ide.isAnimating) { + this.drawNew(); + this.changed(); + return; + } + this.drawNew(3); + this.changed(); + this.nextSteps ([ + function () { + this.drawNew(2); + this.changed(); + }, + function () { + this.drawNew(1); + this.changed(); + }, + function () { + this.drawNew(); + this.changed(); + }, + ]); +}; + +// BooleanSlotMorph events: + +BooleanSlotMorph.prototype.mouseClickLeft = function () { + this.toggleValue(); + if (isNil(this.value)) {return; } + this.reactToSliderEdit(); +}; + +BooleanSlotMorph.prototype.mouseEnter = function () { + if (this.isStatic) {return; } + if (this.value === false) { + var oldValue = this.value; + this.value = null; + this.drawNew(3); + this.changed(); + this.value = oldValue; + return; + } + this.drawNew(1); + this.changed(); +}; + +BooleanSlotMorph.prototype.mouseLeave = function () { + if (this.isStatic) {return; } + this.drawNew(); + this.changed(); }; // BooleanSlotMorph drawing: -BooleanSlotMorph.prototype.drawNew = function () { - var context; - this.silentSetExtent(new Point( - (this.fontSize + this.edge * 2) * 2, - this.fontSize + this.edge * 2 - )); - if (this.parent) { - this.color = this.parent.color; +BooleanSlotMorph.prototype.drawNew = function (progress) { + // "progress" is an optional number sliding the knob + // on a range between 0 and 4 + var context, + textLabel = this.isStatic ? this.textLabel() : null, + h; + + if (textLabel) { + h = textLabel.height + (this.edge * 3); + this.silentSetExtent(new Point( + textLabel.width + (h * 1.5) + (this.edge * 2), + h + )); + } else { + this.silentSetExtent(new Point( + (this.fontSize + this.edge * 2) * 2, + this.fontSize + this.edge * 2 + )); + } + if (!(this.cachedNormalColor)) { // unless flashing + this.color = this.parent ? + this.parent.color : new Color(200, 200, 200); } this.cachedClr = this.color.toString(); this.cachedClrBright = this.bright(); this.cachedClrDark = this.dark(); this.image = newCanvas(this.extent()); context = this.image.getContext('2d'); - this.drawDiamond(context, true); + this.drawDiamond(context, progress); + this.drawLabel(context, textLabel); + this.drawKnob(context, progress); }; -BooleanSlotMorph.prototype.drawDiamond = function (context) { +BooleanSlotMorph.prototype.drawDiamond = function (context, progress) { var w = this.width(), h = this.height(), r = h / 2, + w2 = w / 2, shift = this.edge / 2, gradient; // draw the 'flat' shape: - context.fillStyle = this.color.darker(25).toString(); - context.beginPath(); + if (this.cachedNormalColor) { // if flashing + context.fillStyle = this.color.toString(); + } else { + switch (this.value) { + case true: + context.fillStyle = 'rgb(0, 200, 0)'; + break; + case false: + context.fillStyle = 'rgb(200, 0, 0)'; + break; + default: + context.fillStyle = this.color.darker(25).toString(); + } + } - context.moveTo(0, r); - context.lineTo(r, 0); - context.lineTo(w - r, 0); - context.lineTo(w, r); - context.lineTo(w - r, h); - context.lineTo(r, h); + if (progress && !this.isEmptySlot()) { + // left half: + context.fillStyle = 'rgb(0, 200, 0)'; + context.beginPath(); + context.moveTo(0, r); + context.lineTo(r, 0); + context.lineTo(w2, 0); + context.lineTo(w2, h); + context.lineTo(r, h); + context.closePath(); + context.fill(); - context.closePath(); - context.fill(); + // right half: + context.fillStyle = 'rgb(200, 0, 0)'; + context.beginPath(); + context.moveTo(w2, 0); + context.lineTo(w - r, 0); + context.lineTo(w, r); + context.lineTo(w - r, h); + context.lineTo(w2, h); + context.closePath(); + context.fill(); + } else { + context.beginPath(); + context.moveTo(0, r); + context.lineTo(r, 0); + context.lineTo(w - r, 0); + context.lineTo(w, r); + context.lineTo(w - r, h); + context.lineTo(r, h); + context.closePath(); + context.fill(); + } if (MorphicPreferences.isFlat) {return; } @@ -7786,10 +8352,222 @@ BooleanSlotMorph.prototype.drawDiamond = function (context) { context.stroke(); }; -// BooleanSlotMorph implicit formal parameters: +BooleanSlotMorph.prototype.drawLabel = function (context, textLabel) { + var w = this.width(), + r = this.height() / 2 - this.edge, + r2 = r / 2, + shift = this.edge / 2, + x, + y = this.height() / 2; -BooleanSlotMorph.prototype.isEmptySlot = function () { - return true; + if (this.isEmptySlot()) { + return; + } + if (textLabel) { + y = (this.height() - textLabel.height) / 2; + if (this.value) { + x = this.height() / 2; + } else { + x = this.width() - (this.height() / 2) - textLabel.width; + } + if (!MorphicPreferences.isFlat) { + context.shadowOffsetX = -shift; + context.shadowOffsetY = -shift; + context.shadowBlur = shift; + context.shadowColor = this.value ? 'rgb(0, 100, 0)' : 'rgb(100, 0, 0)'; + } + context.drawImage(textLabel, x, y); + return; + } + // "tick:" + x = r + (this.edge * 2) + shift; + if (!MorphicPreferences.isFlat) { + context.shadowOffsetX = -shift; + context.shadowOffsetY = -shift; + context.shadowBlur = shift; + context.shadowColor = 'rgb(0, 100, 0)'; + } + context.strokeStyle = 'white'; + context.lineWidth = this.edge + shift; + context.lineCap = 'round'; + context.lineJoin = 'miter'; + context.beginPath(); + context.moveTo(x - r2, y); + context.lineTo(x, y + r2); + context.lineTo(x + r2, r2 + this.edge); + context.stroke(); + + // "cross:" + x = w - y - (this.edge * 2); + if (!MorphicPreferences.isFlat) { + context.shadowOffsetX = -shift; + context.shadowOffsetY = -shift; + context.shadowBlur = shift; + context.shadowColor = 'rgb(100, 0, 0)'; + } + context.strokeStyle = 'white'; + context.lineWidth = this.edge; + context.lineCap = 'butt'; + context.beginPath(); + context.moveTo(x - r2, y - r2); + context.lineTo(x + r2, y + r2); + context.moveTo(x - r2, y + r2); + context.lineTo(x + r2, y - r2); + context.stroke(); +}; + +BooleanSlotMorph.prototype.drawKnob = function (context, progress) { + var w = this.width(), + r = this.height() / 2, + shift = this.edge / 2, + slideStep = (this.width() - this.height()) / 4 * (progress || 0), + gradient, + x, + y = r, + outline = PushButtonMorph.prototype.outline / 2, + outlineColor = PushButtonMorph.prototype.outlineColor, + color = PushButtonMorph.prototype.color, + contrast = PushButtonMorph.prototype.contrast, + topColor = color.lighter(contrast), + bottomColor = color.darker(contrast); + + // draw the 'flat' shape: + switch (this.value) { + case false: + x = r + slideStep; + if (!MorphicPreferences.isFlat) { + context.shadowOffsetX = shift; + context.shadowOffsetY = 0; + context.shadowBlur = shift; + context.shadowColor = 'black'; + } + break; + case true: + x = w - r - slideStep; + if (!MorphicPreferences.isFlat) { + context.shadowOffsetX = 0; + context.shadowOffsetY = 0; + context.shadowBlur = 0; + } + break; + default: + if (!progress) {return; } + x = r; + if (!MorphicPreferences.isFlat) { + context.shadowOffsetX = shift; + context.shadowOffsetY = 0; + context.shadowBlur = shift; + context.shadowColor = 'black'; + } + context.globalAlpha = 0.2 * ((progress || 0) + 1); + } + + context.fillStyle = color.toString(); + context.beginPath(); + context.arc(x, y, r, radians(0), radians(360)); + context.closePath(); + context.fill(); + + if (MorphicPreferences.isFlat) {return; } + + // add 3D-Effect + // outline: + context.shadowOffsetX = 0; + context.shadowBlur = 0; + context.shadowColor = 'black'; + context.lineWidth = outline; + context.strokeStyle = outlineColor.toString(); + context.beginPath(); + context.arc(x, y, r - (outline / 2), radians(0), radians(360)); + context.stroke(); + + // top-left: + gradient = context.createRadialGradient( + x, + y, + r - outline - this.edge, + x, + y, + r - outline + ); + gradient.addColorStop(1, topColor.toString()); + gradient.addColorStop(0, color.toString()); + + context.strokeStyle = gradient; + context.lineCap = 'round'; + context.lineWidth = this.edge; + context.beginPath(); + context.arc( + x, + y, + r - outline - this.edge / 2, + radians(180), + radians(270), + false + ); + context.stroke(); + + // bottom-right: + gradient = context.createRadialGradient( + x, + y, + r - outline - this.edge, + x, + y, + r - outline + ); + gradient.addColorStop(1, bottomColor.toString()); + gradient.addColorStop(0, color.toString()); + + context.strokeStyle = gradient; + context.lineCap = 'round'; + context.lineWidth = this.edge; + context.beginPath(); + context.arc( + x, + y, + r - outline - this.edge / 2, + radians(0), + radians(90), + false + ); + context.stroke(); +}; + +BooleanSlotMorph.prototype.textLabel = function () { + if (this.isEmptySlot()) {return null; } + var t, f, img, lbl, x, y; + t = new StringMorph( + localize('true'), + this.fontSize, + null, + true, // bold + null, + null, + null, + null, + new Color(255, 255, 255) + ).image; + f = new StringMorph( + localize('false'), + this.fontSize, + null, + true, // bold + null, + null, + null, + null, + new Color(255, 255, 255) + ).image; + img = newCanvas(new Point( + Math.max(t.width, f.width), + Math.max(t.height, f.height) + )); + lbl = this.value ? t : f; + x = (img.width - lbl.width) / 2; + y = (img.height - lbl.height) / 2; + img.getContext('2d').drawImage(lbl, x, y); + return img; }; // ArrowMorph ////////////////////////////////////////////////////////// @@ -7817,7 +8595,7 @@ ArrowMorph.prototype.init = function (direction, size, padding, color) { this.size = size || ((size === 0) ? 0 : 50); this.padding = padding || 0; - ArrowMorph.uber.init.call(this); + ArrowMorph.uber.init.call(this, true); // silently this.color = color || new Color(0, 0, 0); this.setExtent(new Point(this.size, this.size)); }; @@ -7906,7 +8684,7 @@ TextSlotMorph.prototype.init = function ( this.minWidth = 0; // can be chaged for text-type inputs ("landscape") this.constant = null; - InputSlotMorph.uber.init.call(this); + InputSlotMorph.uber.init.call(this, null, null, null, null, true); // sil. this.color = new Color(255, 255, 255); this.add(contents); this.add(arrow); @@ -7964,6 +8742,7 @@ SymbolMorph.uber = Morph.prototype; SymbolMorph.prototype.names = [ 'square', 'pointRight', + 'stepForward', 'gears', 'file', 'fullScreen', @@ -8027,7 +8806,7 @@ SymbolMorph.prototype.init = function ( this.shadowOffset = shadowOffset || new Point(0, 0); this.shadowColor = shadowColor || null; - SymbolMorph.uber.init.call(this); + SymbolMorph.uber.init.call(this, true); // silently this.color = color || new Color(0, 0, 0); this.drawNew(); }; @@ -8039,7 +8818,7 @@ SymbolMorph.prototype.setLabelColor = function ( shadowColor, shadowOffset ) { - this.shadowOffset = shadowOffset; + this.shadowOffset = shadowOffset || new Point(); this.shadowColor = shadowColor; this.setColor(textColor); }; @@ -8086,6 +8865,8 @@ SymbolMorph.prototype.symbolCanvasColored = function (aColor) { return this.drawSymbolStop(canvas, aColor); case 'pointRight': return this.drawSymbolPointRight(canvas, aColor); + case 'stepForward': + return this.drawSymbolStepForward(canvas, aColor); case 'gears': return this.drawSymbolGears(canvas, aColor); case 'file': @@ -8229,6 +9010,28 @@ SymbolMorph.prototype.drawSymbolPointRight = function (canvas, color) { return canvas; }; +SymbolMorph.prototype.drawSymbolStepForward = function (canvas, color) { + // answer a canvas showing a right-pointing triangle + // followed by a vertical bar + var ctx = canvas.getContext('2d'); + + ctx.fillStyle = color.toString(); + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.lineTo(canvas.width * 0.75, Math.round(canvas.height / 2)); + ctx.lineTo(0, canvas.height); + ctx.lineTo(0, 0); + ctx.closePath(); + ctx.fill(); + ctx.fillRect( + canvas.width * 0.75, + 0, + canvas.width * 0.25, + canvas.height + ); + return canvas; +}; + SymbolMorph.prototype.drawSymbolGears = function (canvas, color) { // answer a canvas showing gears var ctx = canvas.getContext('2d'), @@ -9259,7 +10062,7 @@ function ColorSlotMorph(clr) { } ColorSlotMorph.prototype.init = function (clr) { - ColorSlotMorph.uber.init.call(this); + ColorSlotMorph.uber.init.call(this, null, true); // silently this.setColor(clr || new Color(145, 26, 68)); }; @@ -9437,7 +10240,7 @@ MultiArgMorph.prototype.init = function ( this.shadowOffset = shadowOffset || null; this.canBeEmpty = true; - MultiArgMorph.uber.init.call(this); + MultiArgMorph.uber.init.call(this, null, true); // silently // MultiArgMorphs are transparent by default b/c of zebra coloring this.alpha = isTransparent === false ? 1 : 0; @@ -9613,7 +10416,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) { @@ -9648,7 +10452,13 @@ MultiArgMorph.prototype.removeInput = function () { // MultiArgMorph events: MultiArgMorph.prototype.mouseClickLeft = function (pos) { - // if the key is pressed, repeat action 5 times + // prevent expansion in the palette + // (because it can be hard or impossible to collapse again) + if (!this.parentThatIsA(ScriptsMorph)) { + this.escalateEvent('mouseClickLeft', pos); + return; + } + // if the key is pressed, repeat action 3 times var arrows = this.arrows(), leftArrow = arrows.children[0], rightArrow = arrows.children[1], @@ -9825,7 +10635,7 @@ ArgLabelMorph.prototype.init = function (argMorph, labelTxt) { var label; this.labelText = localize(labelTxt || 'input list:'); - ArgLabelMorph.uber.init.call(this); + ArgLabelMorph.uber.init.call(this, null, true); // silently this.isStatic = true; // I cannot be exchanged @@ -9951,14 +10761,17 @@ function FunctionSlotMorph(isPredicate) { this.init(isPredicate); } -FunctionSlotMorph.prototype.init = function (isPredicate) { - FunctionSlotMorph.uber.init.call(this); +FunctionSlotMorph.prototype.init = function (isPredicate, silently) { + FunctionSlotMorph.uber.init.call(this, null, true); // silently this.isPredicate = isPredicate || false; this.color = this.rfColor; - this.setExtent(new Point( - (this.fontSize + this.edge * 2) * 2, - this.fontSize + this.edge * 2 - )); + this.setExtent( + new Point( + (this.fontSize + this.edge * 2) * 2, + this.fontSize + this.edge * 2 + ), + silently + ); }; FunctionSlotMorph.prototype.getSpec = function () { @@ -10333,7 +11146,7 @@ function ReporterSlotMorph(isPredicate) { } ReporterSlotMorph.prototype.init = function (isPredicate) { - ReporterSlotMorph.uber.init.call(this, isPredicate); + ReporterSlotMorph.uber.init.call(this, isPredicate, true); this.add(this.emptySlot()); this.fixLayout(); }; @@ -10424,7 +11237,7 @@ function RingReporterSlotMorph(isPredicate) { } RingReporterSlotMorph.prototype.init = function (isPredicate) { - RingReporterSlotMorph.uber.init.call(this, isPredicate); + RingReporterSlotMorph.uber.init.call(this, isPredicate, true); this.alpha = RingMorph.prototype.alpha; this.contrast = RingMorph.prototype.contrast; this.isHole = true; @@ -10983,7 +11796,13 @@ CommentMorph.prototype.userMenu = function () { menu.addItem( "comment pic...", function () { - window.open(myself.fullImage().toDataURL()); + var ide = myself.parentThatIsA(IDE_Morph); + ide.saveCanvasAs( + myself.fullImageClassic(), + ide.projetName || localize('Untitled') + ' ' + + localize('comment pic'), + true // request new window + ); }, 'open a new window\nwith a picture of this comment' ); @@ -11030,7 +11849,7 @@ CommentMorph.prototype.snap = function (hand) { var scripts = this.parent, target; - if (!scripts instanceof ScriptsMorph) { + if (!(scripts instanceof ScriptsMorph)) { return null; } @@ -11307,6 +12126,10 @@ ScriptFocusMorph.prototype.trigger = function () { current.mouseClickLeft(); return; } + if (current instanceof BooleanSlotMorph) { + current.toggleValue(); + return; + } if (current instanceof InputSlotMorph) { if (!current.isReadOnly) { delete this.fps; @@ -11349,6 +12172,10 @@ ScriptFocusMorph.prototype.deleteLastElement = function () { if (current.arrows().children[0].isVisible) { current.removeInput(); } + } else if (current instanceof BooleanSlotMorph) { + if (!current.isStatic) { + current.setContents(null); + } } else if (current instanceof ReporterBlockMorph) { if (!current.isTemplate) { this.lastElement(); @@ -11370,7 +12197,7 @@ ScriptFocusMorph.prototype.deleteLastElement = function () { }; ScriptFocusMorph.prototype.insertBlock = function (block) { - var pb; + var pb, stage, ide; block.isTemplate = false; block.isDraggable = true; @@ -11433,6 +12260,28 @@ ScriptFocusMorph.prototype.insertBlock = function (block) { this.editor.adjustBounds(); // block.scrollIntoView(); this.fixLayout(); + + // register generic hat blocks + if (block.selector === 'receiveCondition') { + if (this.editor.owner) { + stage = this.editor.owner.parentThatIsA(StageMorph); + if (stage) { + stage.enableCustomHatBlocks = true; + stage.threads.pauseCustomHatBlocks = false; + ide = stage.parentThatIsA(IDE_Morph); + if (ide) { + ide.controlBar.stopButton.refresh(); + } + } + } + } + + // experimental: if the inserted block has inputs, go to the first one + if (block.inputs && block.inputs().length) { + this.element = block; + this.atEnd = false; + this.nextElement(); + } }; ScriptFocusMorph.prototype.insertVariableGetter = function () { @@ -11675,6 +12524,7 @@ ScriptFocusMorph.prototype.items = function () { !(each instanceof TemplateSlotMorph) && (!each.isStatic || each.choices || + each instanceof BooleanSlotMorph || each instanceof RingMorph || each instanceof MultiArgMorph || each instanceof CommandSlotMorph); @@ -11838,7 +12688,7 @@ ScriptFocusMorph.prototype.reactToKeyEvent = function (key) { default: types = this.blockTypes(); if (!(this.element instanceof ScriptsMorph) && - contains(types, 'reporter')) { + types && contains(types, 'reporter')) { vNames = Object.keys(this.element.getVarNamesDict()); } if (types) { diff --git a/byob.js b/byob.js index 13392b5a..d56ed8d5 100644 --- a/byob.js +++ b/byob.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -51,6 +51,7 @@ BlockEditorMorph BlockExportDialogMorph BlockImportDialogMorph + BlockRemovalDialogMorph InputSlotDialogMorph VariableDialogMorph @@ -90,6 +91,7 @@ VariableDialogMorph BlockExportDialogMorph BlockImportDialogMorph + BlockRemovalDialogMorph */ @@ -97,16 +99,16 @@ StringMorph, Color, DialogBoxMorph, ScriptsMorph, ScrollFrameMorph, Point, HandleMorph, HatBlockMorph, BlockMorph, detect, List, Process, AlignmentMorph, ToggleMorph, InputFieldMorph, ReporterBlockMorph, -Context, StringMorph, nop, newCanvas, radians, BoxMorph, -ArrowMorph, PushButtonMorph, contains, InputSlotMorph, ShadowMorph, -ToggleButtonMorph, IDE_Morph, MenuMorph, copy, ToggleElementMorph, -Morph, fontHeight, StageMorph, SyntaxElementMorph, SnapSerializer, -CommentMorph, localize, CSlotMorph, SpeechBubbleMorph, MorphicPreferences, -SymbolMorph, isNil, CursorMorph*/ +StringMorph, nop, newCanvas, radians, BoxMorph, ArrowMorph, PushButtonMorph, +contains, InputSlotMorph, ToggleButtonMorph, IDE_Morph, MenuMorph, copy, +ToggleElementMorph, Morph, fontHeight, StageMorph, SyntaxElementMorph, +SnapSerializer, CommentMorph, localize, CSlotMorph, SpeechBubbleMorph, +MorphicPreferences, SymbolMorph, isNil, CursorMorph, VariableFrame, +WatcherMorph, Variable*/ // Global stuff //////////////////////////////////////////////////////// -modules.byob = '2015-July-28'; +modules.byob = '2016-September-27'; // Declarations @@ -125,6 +127,7 @@ var VariableDialogMorph; var JaggedBlockMorph; var BlockExportDialogMorph; var BlockImportDialogMorph; +var BlockRemovalDialogMorph; // CustomBlockDefinition /////////////////////////////////////////////// @@ -139,12 +142,15 @@ 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 // don't serialize (not needed for functionality): this.receiver = receiver || null; // for serialization only (pointer) + this.editorDimensions = null; // a rectangle, last bounds of the editor + this.cachedIsRecursive = null; // for automatic yielding } // CustomBlockDefinition instantiating blocks @@ -338,10 +344,40 @@ CustomBlockDefinition.prototype.parseSpec = function (spec) { return parts; }; +CustomBlockDefinition.prototype.isDirectlyRecursive = function () { + var myspec; + if (this.cachedIsRecursive !== null) { + return this.cachedIsRecursive; + } + if (!this.body) { + this.cachedIsRecursive = false; + } else { + myspec = this.spec; + this.cachedIsRecursive = this.body.expression.anyChild( + function (morph) { + return morph instanceof BlockMorph && + morph.definition && + morph.definition.spec === myspec; + } + ); + } + return this.cachedIsRecursive; +}; + // CustomBlockDefinition picturing CustomBlockDefinition.prototype.scriptsPicture = function () { - var scripts, proto, block, comment; + return this.scriptsModel().scriptsPicture(); +}; + +CustomBlockDefinition.prototype.sortedElements = function () { + return this.scriptsModel().sortedElements(); +}; + +CustomBlockDefinition.prototype.scriptsModel = function () { + // answer a restored scripting area for the sake + // of creating script pictures + var scripts, proto, block, comment, template; scripts = new ScriptsMorph(); scripts.cleanUpMargin = 10; @@ -370,9 +406,12 @@ CustomBlockDefinition.prototype.scriptsPicture = function () { proto.allComments().forEach(function (comment) { comment.align(proto); }); - proto.children[0].fixLayout(); + template = proto.parts()[0]; + template.fixLayout(); + template.forceNormalColoring(); + template.fixBlockColor(proto, true); scripts.fixMultiArgs(); - return scripts.scriptsPicture(); + return scripts; }; // CustomCommandBlockMorph ///////////////////////////////////////////// @@ -392,17 +431,29 @@ function CustomCommandBlockMorph(definition, isProto) { CustomCommandBlockMorph.prototype.init = function (definition, isProto) { this.definition = definition; // mandatory this.isPrototype = isProto || false; // optional - - CustomCommandBlockMorph.uber.init.call(this); - + 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.refresh = function () { +CustomCommandBlockMorph.prototype.initializeVariables = function (oldVars) { + var myself = this; + this.variables = new VariableFrame(); + this.definition.variableNames.forEach(function (name) { + var v = oldVars ? oldVars[name] : null; + myself.variables.addVar( + name, + v instanceof Variable ? v.value : null + ); + }); +}; + +CustomCommandBlockMorph.prototype.refresh = function (silently) { var def = this.definition, newSpec = this.isPrototype ? def.spec : def.blockSpec(), @@ -416,7 +467,7 @@ CustomCommandBlockMorph.prototype.refresh = function () { } else { this.fixBlockColor(); } - this.setSpec(newSpec); + this.setSpec(newSpec, silently); this.fixLabelColor(); this.restoreInputs(oldInputs); } else { // update all input slots' drop-downs @@ -435,6 +486,15 @@ CustomCommandBlockMorph.prototype.refresh = function () { inp.setContents(def.inputNames()[idx]); } }); + + // initialize block vars + // preserve values of unchanged variable names + this.initializeVariables(this.variables.vars); + + // make (double) sure I'm colored correctly + this.forceNormalColoring(); + this.drawNew(); + this.fixBlockColor(null, true); }; CustomCommandBlockMorph.prototype.restoreInputs = function (oldInputs) { @@ -449,7 +509,7 @@ CustomCommandBlockMorph.prototype.restoreInputs = function (oldInputs) { old = oldInputs[i]; if (old instanceof ReporterBlockMorph && (!(inp instanceof TemplateSlotMorph))) { - myself.silentReplaceInput(inp, old); + myself.silentReplaceInput(inp, old.fullCopy()); } else if (old instanceof InputSlotMorph && inp instanceof InputSlotMorph) { inp.setContents(old.evaluate()); @@ -658,7 +718,7 @@ CustomCommandBlockMorph.prototype.mouseClickLeft = function () { }; CustomCommandBlockMorph.prototype.edit = function () { - var myself = this, block, hat; + var myself = this, editor, block, hat; if (this.isPrototype) { block = this.definition.blockInstance(); @@ -683,7 +743,11 @@ CustomCommandBlockMorph.prototype.edit = function () { myself.isInUse() ); } else { - new BlockEditorMorph(this.definition, this.receiver()).popUp(); + Morph.prototype.trackChanges = false; + editor = new BlockEditorMorph(this.definition, this.receiver()); + editor.popUp(); + Morph.prototype.trackChanges = true; + editor.changed(); } }; @@ -726,25 +790,97 @@ CustomCommandBlockMorph.prototype.attachTargets = function () { }; CustomCommandBlockMorph.prototype.isInUse = function () { - // anser true if an instance of my definition is found + // answer true if an instance of my definition is found // in any of my receiver's scripts or block definitions - return this.receiver().usesBlockInstance(this.definition); + var def = this.definition, + ide = this.receiver().parentThatIsA(IDE_Morph); + if (def.isGlobal && ide) { + return ide.sprites.asArray().concat([ide.stage]).some( + function (any, idx) { + return any.usesBlockInstance(def, false, idx); + } + ); + } + return this.receiver().usesBlockInstance(def); }; // 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); menu.addItem( "script pic...", function () { - window.open(this.topBlock().fullImage().toDataURL()); + var ide = this.world().children[0]; + ide.saveCanvasAs( + this.topBlock().scriptPic(), + ide.projectName || localize('Untitled') + ' ' + + localize('script pic'), + true // request opening a new window + ); }, '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) { @@ -755,14 +891,20 @@ 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; }; CustomCommandBlockMorph.prototype.exportBlockDefinition = function () { - var xml = new SnapSerializer().serialize(this.definition); - window.open('data:text/xml,' + encodeURIComponent(xml)); + var xml = new SnapSerializer().serialize(this.definition), + ide = this.parentThatIsA(IDE_Morph); + + ide.saveXMLAs(xml, this.spec); }; CustomCommandBlockMorph.prototype.deleteBlockDefinition = function () { @@ -815,7 +957,7 @@ CustomCommandBlockMorph.prototype.mouseEnter = function () { comment.contents.lines.forEach(function (line) { help = help + '\n' + line; }); - this.bubbleHelp( + this.popUpbubbleHelp( help.substr(1), this.definition.comment.color ); @@ -828,20 +970,6 @@ CustomCommandBlockMorph.prototype.mouseLeave = function () { } }; -// CustomCommandBlockMorph bubble help: - -CustomCommandBlockMorph.prototype.bubbleHelp = function (contents, color) { - var myself = this; - this.fps = 2; - this.step = function () { - if (this.bounds.containsPoint(this.world().hand.position())) { - myself.popUpbubbleHelp(contents, color); - } - myself.fps = 0; - delete myself.step; - }; -}; - CustomCommandBlockMorph.prototype.popUpbubbleHelp = function ( contents, color @@ -913,18 +1041,21 @@ CustomReporterBlockMorph.prototype.init = function ( ) { this.definition = definition; // mandatory this.isPrototype = isProto || false; // optional - - CustomReporterBlockMorph.uber.init.call(this, isPredicate); - + 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); + CustomCommandBlockMorph.prototype.refresh.call(this, true); if (!this.isPrototype) { this.isPredicate = (this.definition.type === 'predicate'); } @@ -1448,7 +1579,7 @@ BlockDialogMorph.prototype.createScopeButtons = function () { var myself = this; this.addScopeButton( - function () {myself.setScope('gobal'); }, + function () {myself.setScope('global'); }, "for all sprites", function () {return myself.isGlobal; } ); @@ -1481,7 +1612,7 @@ BlockDialogMorph.prototype.addScopeButton = function (action, label, query) { BlockDialogMorph.prototype.setScope = function (varType) { - this.isGlobal = (varType === 'gobal'); + this.isGlobal = (varType === 'global'); this.scopes.children.forEach(function (c) { c.refresh(); }); @@ -1620,6 +1751,15 @@ BlockDialogMorph.prototype.fixLayout = function () { } }; +BlockDialogMorph.prototype.accept = function () { + if ((this.body instanceof InputFieldMorph) && + (this.normalizeSpaces(this.body.getValue()) === '')) { + this.edit(); + } else { + BlockDialogMorph.uber.accept.call(this); + } +}; + // BlockEditorMorph //////////////////////////////////////////////////// // BlockEditorMorph inherits from DialogBoxMorph: @@ -1635,7 +1775,9 @@ function BlockEditorMorph(definition, target) { } BlockEditorMorph.prototype.init = function (definition, target) { - var scripts, proto, scriptsFrame, block, comment, myself = this; + var scripts, proto, scriptsFrame, block, comment, myself = this, + isLive = Process.prototype.enableLiveCoding || + Process.prototype.enableSingleStepping; // additional properties: this.definition = definition; @@ -1656,6 +1798,7 @@ BlockEditorMorph.prototype.init = function (definition, target) { // create scripting area scripts = new ScriptsMorph(target); + scripts.rejectsHats = true; scripts.isDraggable = false; scripts.color = IDE_Morph.prototype.groupColor; scripts.cachedTexture = IDE_Morph.prototype.scriptsPaneTexture; @@ -1663,19 +1806,19 @@ BlockEditorMorph.prototype.init = function (definition, target) { proto = new PrototypeHatBlockMorph(this.definition); proto.setPosition(scripts.position().add(10)); - if (definition.comment !== null) { comment = definition.comment.fullCopy(); proto.comment = comment; comment.block = proto; } - if (definition.body !== null) { - proto.nextBlock(definition.body.expression.fullCopy()); + proto.nextBlock(isLive ? definition.body.expression + : definition.body.expression.fullCopy() + ); } - scripts.add(proto); proto.fixBlockColor(null, true); + proto.drawNew(); this.definition.scripts.forEach(function (element) { block = element.fullCopy(); @@ -1701,13 +1844,18 @@ BlockEditorMorph.prototype.init = function (definition, target) { this.addBody(scriptsFrame); this.addButton('ok', 'OK'); - this.addButton('updateDefinition', 'Apply'); - this.addButton('cancel', 'Cancel'); + if (!isLive) { + this.addButton('updateDefinition', 'Apply'); + this.addButton('cancel', 'Cancel'); + } - this.setExtent(new Point(375, 300)); + this.setExtent(new Point(375, 300)); // normal initial extent this.fixLayout(); - proto.children[0].fixLayout(); scripts.fixMultiArgs(); + + block = proto.parts()[0]; + block.forceNormalColoring(); + block.fixBlockColor(proto, true); }; BlockEditorMorph.prototype.popUp = function () { @@ -1715,6 +1863,7 @@ BlockEditorMorph.prototype.popUp = function () { if (world) { BlockEditorMorph.uber.popUp.call(this, world); + this.setInitialDimensions(); this.handle = new HandleMorph( this, 280, @@ -1722,9 +1871,18 @@ BlockEditorMorph.prototype.popUp = function () { this.corner, this.corner ); + world.keyboardReceiver = null; } }; +BlockEditorMorph.prototype.justDropped = function () { + // override the inherited default behavior, which is to + // give keyboard focus to dialog boxes, as in this case + // we want Snap-global keyboard-shortcuts like ctrl-f + // to still work + nop(); +}; + // BlockEditorMorph ops BlockEditorMorph.prototype.accept = function (origin) { @@ -1828,7 +1986,10 @@ 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(); + this.definition.cachedIsRecursive = null; // flush the cache, don't update this.body.contents.children.forEach(function (morph) { if (morph instanceof PrototypeHatBlockMorph) { @@ -1903,8 +2064,36 @@ 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 () { + var world = this.world(), + mex = world.extent().subtract(new Point(this.padding, this.padding)), + th = fontHeight(this.titleFontSize) + this.titlePadding * 2, + bh = this.buttons.height(); + + if (this.definition.editorDimensions) { + this.setPosition(this.definition.editorDimensions.origin); + this.setExtent(this.definition.editorDimensions.extent().min(mex)); + this.keepWithin(world); + return; + } + this.setExtent( + this.body.contents.extent().add( + new Point(this.padding, this.padding + th + bh) + ).min(mex) + ); + this.setCenter(this.world().center()); +}; + BlockEditorMorph.prototype.fixLayout = function () { var th = fontHeight(this.titleFontSize) + this.titlePadding * 2; @@ -1949,7 +2138,8 @@ function PrototypeHatBlockMorph(definition) { } PrototypeHatBlockMorph.prototype.init = function (definition) { - var proto = definition.prototypeInstance(); + var proto = definition.prototypeInstance(), + vars; this.definition = definition; @@ -1962,8 +2152,17 @@ 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(); + proto.fixBlockColor(this, true); }; PrototypeHatBlockMorph.prototype.mouseClickLeft = function () { @@ -1975,11 +2174,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 @@ -1988,7 +2187,7 @@ PrototypeHatBlockMorph.prototype.fixBlockColor = function ( nearestBlock, isForced ) { - var nearest = this.children[0] || nearestBlock; + var nearest = this.parts()[0] || nearestBlock; if (!this.zebraContrast && !isForced) { return; @@ -2011,6 +2210,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: @@ -2249,8 +2467,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, @@ -2371,21 +2592,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 @@ -2472,6 +2710,7 @@ InputSlotDialogMorph.prototype.init = function ( this.isExpanded = false; this.category = category || 'other'; this.cachedRadioButton = null; // "template" for radio button backgrounds + this.noDelete = false; // initialize inherited properties: BlockDialogMorph.uber.init.call( @@ -2590,8 +2829,9 @@ InputSlotDialogMorph.prototype.getInput = function () { this.fragment.labelString = lbl; this.fragment.defaultValue = this.slots.defaultInputField.getValue(); return lbl; + } else if (!this.noDelete) { + this.fragment.isDeleted = true; } - this.fragment.isDeleted = true; return null; }; @@ -2676,6 +2916,8 @@ InputSlotDialogMorph.prototype.open = function ( this.addButton('ok', 'OK'); if (!noDeleteButton) { this.addButton('deleteFragment', 'Delete'); + } else { + this.noDelete = true; } this.addButton('cancel', 'Cancel'); this.fixLayout(); @@ -3068,7 +3310,7 @@ VariableDialogMorph.prototype.createTypeButtons = function () { var myself = this; this.addTypeButton( - function () {myself.setType('gobal'); }, + function () {myself.setType('global'); }, "for all sprites", function () {return myself.isGlobal; } ); @@ -3083,7 +3325,7 @@ VariableDialogMorph.prototype.addTypeButton = BlockDialogMorph.prototype.addTypeButton; VariableDialogMorph.prototype.setType = function (varType) { - this.isGlobal = (varType === 'gobal'); + this.isGlobal = (varType === 'global'); this.types.children.forEach(function (c) { c.refresh(); }); @@ -3304,17 +3546,21 @@ BlockExportDialogMorph.prototype.selectNone = function () { // BlockExportDialogMorph ops BlockExportDialogMorph.prototype.exportBlocks = function () { - var str = encodeURIComponent( - this.serializer.serialize(this.blocks) - ); + var str = this.serializer.serialize(this.blocks), + ide = this.world().children[0]; + if (this.blocks.length > 0) { - window.open('data:text/xml,' + str - + ''); + + ''; + ide.saveXMLAs( + str, + ide.projectName || localize('Untitled') + ' ' + localize('blocks') + ); } else { new DialogBoxMorph().inform( 'Export blocks', @@ -3420,3 +3666,96 @@ BlockImportDialogMorph.prototype.importBlocks = function (name) { BlockImportDialogMorph.prototype.fixLayout = BlockEditorMorph.prototype.fixLayout; + +// BlockRemovalDialogMorph /////////////////////////////////////////////////// + +// BlockRemovalDialogMorph inherits from DialogBoxMorph +// and pseudo-inherits from BlockExportDialogMorph: + +BlockRemovalDialogMorph.prototype = new DialogBoxMorph(); +BlockRemovalDialogMorph.prototype.constructor = BlockImportDialogMorph; +BlockRemovalDialogMorph.uber = DialogBoxMorph.prototype; + +// BlockRemovalDialogMorph constants: + +BlockRemovalDialogMorph.prototype.key = 'blockRemove'; + +// BlockRemovalDialogMorph instance creation: + +function BlockRemovalDialogMorph(blocks, target) { + this.init(blocks, target); +} + +BlockRemovalDialogMorph.prototype.init = function (blocks, target) { + var myself = this; + + // additional properties: + this.blocks = blocks.slice(0); + this.handle = null; + + // initialize inherited properties: + BlockExportDialogMorph.uber.init.call( + this, + target, + function () {myself.removeBlocks(); }, + null // environment + ); + + // override inherited properites: + this.labelString = localize('Remove unused blocks') + + (name ? ': ' : '') + + name || ''; + this.createLabel(); + + // build contents + this.buildContents(); +}; + +BlockRemovalDialogMorph.prototype.buildContents + = BlockExportDialogMorph.prototype.buildContents; + +BlockRemovalDialogMorph.prototype.popUp + = BlockExportDialogMorph.prototype.popUp; + +// BlockRemovalDialogMorph menu + +BlockRemovalDialogMorph.prototype.userMenu + = BlockExportDialogMorph.prototype.userMenu; + +BlockRemovalDialogMorph.prototype.selectAll + = BlockExportDialogMorph.prototype.selectAll; + +BlockRemovalDialogMorph.prototype.selectNone + = BlockExportDialogMorph.prototype.selectNone; + +// BlockRemovalDialogMorph ops + +BlockRemovalDialogMorph.prototype.removeBlocks = function () { + var ide = this.target.parentThatIsA(IDE_Morph); + if (!ide) {return; } + if (this.blocks.length > 0) { + this.blocks.forEach(function (def) { + var idx = ide.stage.globalBlocks.indexOf(def); + if (idx !== -1) { + ide.stage.globalBlocks.splice(idx, 1); + } + }); + ide.flushPaletteCache(); + ide.refreshPalette(); + ide.showMessage( + this.blocks.length + ' ' + localize('unused block(s) removed'), + 2 + ); + } else { + new DialogBoxMorph().inform( + 'Remove unused blocks', + 'no blocks were selected', + this.world() + ); + } +}; + +// BlockRemovalDialogMorph layout + +BlockRemovalDialogMorph.prototype.fixLayout + = BlockEditorMorph.prototype.fixLayout; diff --git a/cloud.js b/cloud.js index ed7233f9..28b2a6c5 100644 --- a/cloud.js +++ b/cloud.js @@ -30,7 +30,7 @@ /*global modules, IDE_Morph, SnapSerializer, hex_sha512, alert, nop, localize*/ -modules.cloud = '2015-January-12'; +modules.cloud = '2015-December-15'; // Global stuff @@ -65,7 +65,7 @@ Cloud.prototype.hasProtocol = function () { }; Cloud.prototype.setRoute = function (username) { - var routes = 10, + var routes = 20, userNum = 0, i; @@ -145,13 +145,12 @@ Cloud.prototype.getPublicProject = function ( // where the values are url-component encoded // callBack is a single argument function, errorCall take two args var request = new XMLHttpRequest(), - responseList, myself = this; try { request.open( "GET", (this.hasProtocol() ? '' : 'http://') - + this.url + 'Public' + + this.url + 'RawPublic' + '?' + id, true @@ -170,12 +169,9 @@ Cloud.prototype.getPublicProject = function ( request.responseText ); } else { - responseList = myself.parseResponse( - request.responseText - ); callBack.call( null, - responseList[0].SourceCode + request.responseText ); } } else { @@ -330,7 +326,9 @@ Cloud.prototype.reconnect = function ( Cloud.prototype.saveProject = function (ide, callBack, errorCall) { var myself = this, pdata, - media; + media, + size, + mediaSize; ide.serializer.isCollectingMedia = true; pdata = ide.serializer.serialize(ide.stage); @@ -339,6 +337,19 @@ Cloud.prototype.saveProject = function (ide, callBack, errorCall) { ide.serializer.isCollectingMedia = false; ide.serializer.flushMedia(); + mediaSize = media ? media.length : 0; + size = pdata.length + mediaSize; + if (mediaSize > 10485760) { + new DialogBoxMorph().inform( + 'Snap!Cloud - Cannot Save Project', + 'The media inside this project exceeds 10 MB.\n' + + 'Please reduce the size of costumes or sounds.\n', + ide.world(), + ide.cloudIcon(null, new Color(180, 0, 0)) + ); + throw new Error('Project media exceeds 10 MB size limit'); + } + // check if serialized data can be parsed back again try { ide.serializer.parse(pdata); @@ -357,6 +368,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( @@ -544,9 +556,13 @@ Cloud.prototype.callService = function ( if (serviceName === 'login') { myself.api = myself.parseAPI(request.responseText); } - responseList = myself.parseResponse( - request.responseText - ); + if (serviceName === 'getRawProject') { + responseList = request.responseText; + } else { + responseList = myself.parseResponse( + request.responseText + ); + } callBack.call(null, responseList, service.url); } }; diff --git a/gui.js b/gui.js index d93d4282..4fe38639 100644 --- a/gui.js +++ b/gui.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -44,6 +44,7 @@ CostumeIconMorph WardrobeMorph StageHandleMorph; + PaletteHandleMorph; credits @@ -54,23 +55,24 @@ */ -/*global modules, Morph, SpriteMorph, BoxMorph, SyntaxElementMorph, Color, -ListWatcherMorph, isString, TextMorph, newCanvas, useBlurredShadows, -radians, VariableFrame, StringMorph, Point, SliderMorph, MenuMorph, -morphicVersion, DialogBoxMorph, ToggleButtonMorph, contains, -ScrollFrameMorph, StageMorph, PushButtonMorph, InputFieldMorph, FrameMorph, -Process, nop, SnapSerializer, ListMorph, detect, AlignmentMorph, TabMorph, -Costume, CostumeEditorMorph, MorphicPreferences, touchScreenSettings, -standardSettings, Sound, BlockMorph, ToggleMorph, InputSlotDialogMorph, -ScriptsMorph, isNil, SymbolMorph, BlockExportDialogMorph, -BlockImportDialogMorph, SnapTranslator, localize, List, InputSlotMorph, -SnapCloud, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512, -sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio, -SpeechBubbleMorph, ScriptFocusMorph*/ +/*global modules, Morph, SpriteMorph, SyntaxElementMorph, Color, +ListWatcherMorph, TextMorph, newCanvas, useBlurredShadows, VariableFrame, +StringMorph, Point, MenuMorph, morphicVersion, DialogBoxMorph, +ToggleButtonMorph, contains, ScrollFrameMorph, StageMorph, PushButtonMorph, +InputFieldMorph, FrameMorph, Process, nop, SnapSerializer, ListMorph, detect, +AlignmentMorph, TabMorph, Costume, MorphicPreferences, Sound, BlockMorph, +ToggleMorph, InputSlotDialogMorph, ScriptsMorph, isNil, SymbolMorph, +BlockExportDialogMorph, BlockImportDialogMorph, SnapTranslator, localize, +List, ArgMorph, SnapCloud, Uint8Array, HandleMorph, SVG_Costume, +fontHeight, hex_sha512, sb, CommentMorph, CommandBlockMorph, +BlockLabelPlaceHolderMorph, Audio, SpeechBubbleMorph, ScriptFocusMorph, +XML_Element, WatcherMorph, BlockRemovalDialogMorph, saveAs, TableMorph, +isSnapObject, isRetinaEnabled, disableRetinaSupport, enableRetinaSupport, +isRetinaSupported, SliderMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2015-July-28'; +modules.gui = '2016-November-10'; // Declarations @@ -83,17 +85,7 @@ var WardrobeMorph; var SoundIconMorph; var JukeboxMorph; var StageHandleMorph; - -// Get the full url without "snap.html" -var baseURL = (function getPath(location) { - var origin, path, slash; - path = location.pathname; // starts with a / - origin = location.origin; // has no trailing / - slash = path.lastIndexOf('/'); - path = path.slice(0, slash + 1); // keep a trailing / - return origin + path; -}(window.location)); - +var PaletteHandleMorph; // IDE_Morph /////////////////////////////////////////////////////////// @@ -231,10 +223,12 @@ IDE_Morph.prototype.init = function (isAutoFill) { this.projectName = ''; this.projectNotes = ''; + this.logoURL = this.resourceURL('snap_logo_sm.png'); this.logo = null; this.controlBar = null; this.categories = null; this.palette = null; + this.paletteHandle = null; this.spriteBar = null; this.spriteEditor = null; this.stage = null; @@ -242,13 +236,14 @@ IDE_Morph.prototype.init = function (isAutoFill) { this.corralBar = null; this.corral = null; - this.isAutoFill = isAutoFill || true; + this.isAutoFill = isAutoFill === undefined ? true : isAutoFill; this.isAppMode = false; this.isSmallStage = false; this.filePicker = null; this.hasChangedMedia = false; this.isAnimating = true; + this.paletteWidth = 200; // initially same as logo width this.stageRatio = 1; // for IDE animations, e.g. when zooming this.loadNewProject = false; // flag when starting up translated @@ -279,19 +274,6 @@ IDE_Morph.prototype.openIn = function (world) { } } - SnapCloud.isloggedin(function () - { - str = SnapCloud.encodeDict( - { - username: SnapCloud.username - } - ); - localStorage['-snap-user'] = str; - myself.source = 'cloud'; - myself.showMessage('now connected.', 2); - }, myself.cloudError() - ); - this.buildPanes(); world.add(this); world.userMenu = this.userMenu; @@ -482,6 +464,7 @@ IDE_Morph.prototype.openIn = function (world) { } if (this.userLanguage) { + this.loadNewProject = true; this.setLanguage(this.userLanguage, interpretUrlAnchors); } else { interpretUrlAnchors.call(this); @@ -510,7 +493,7 @@ IDE_Morph.prototype.createLogo = function () { } this.logo = new Morph(); - this.logo.texture = 'snap_logo_sm.png'; + this.logo.texture = this.logoURL; this.logo.drawNew = function () { this.image = newCanvas(this.extent()); var context = this.image.getContext('2d'), @@ -550,11 +533,10 @@ IDE_Morph.prototype.createLogo = function () { }; IDE_Morph.prototype.createControlBar = function () { - - // assumes the logo has already been created var padding = 5, button, + slider, stopButton, pauseButton, startButton, @@ -571,7 +553,6 @@ IDE_Morph.prototype.createControlBar = function () { ], myself = this; - if (this.controlBar) { this.controlBar.destroy(); } @@ -649,11 +630,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]; @@ -667,13 +659,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), @@ -725,6 +719,23 @@ IDE_Morph.prototype.createControlBar = function () { this.controlBar.add(startButton); this.controlBar.startButton = startButton; + // steppingSlider + slider = new SliderMorph( + 61, + 1, + Process.prototype.flashTime * 100 + 1, + 6, + 'horizontal' + ); + slider.action = function (num) { + Process.prototype.flashTime = (num - 1) / 100; + myself.controlBar.refreshResumeSymbol(); + }; + slider.alpha = MorphicPreferences.isFlat ? 0.1 : 0.3; + slider.setExtent(new Point(50, 14)); + this.controlBar.add(slider); + this.controlBar.steppingSlider = slider; + // projectButton button = new PushButtonMorph( this, @@ -821,6 +832,9 @@ IDE_Morph.prototype.createControlBar = function () { } ); + slider.setCenter(myself.controlBar.center()); + slider.setRight(stageSizeButton.left() - padding); + settingsButton.setCenter(myself.controlBar.center()); settingsButton.setLeft(this.left()); @@ -830,9 +844,41 @@ IDE_Morph.prototype.createControlBar = function () { projectButton.setCenter(myself.controlBar.center()); projectButton.setRight(cloudButton.left() - padding); + this.refreshSlider(); this.updateLabel(); }; + this.controlBar.refreshSlider = function () { + if (Process.prototype.enableSingleStepping && !myself.isAppMode) { + slider.drawNew(); + slider.show(); + } else { + slider.hide(); + } + this.refreshResumeSymbol(); + }; + + this.controlBar.refreshResumeSymbol = function () { + var pauseSymbols; + if (Process.prototype.enableSingleStepping && + Process.prototype.flashTime > 0.5) { + myself.stage.threads.pauseAll(myself.stage); + pauseSymbols = [ + new SymbolMorph('pause', 12), + new SymbolMorph('stepForward', 14) + ]; + } else { + pauseSymbols = [ + new SymbolMorph('pause', 12), + new SymbolMorph('pointRight', 14) + ]; + } + pauseButton.labelString = pauseSymbols; + pauseButton.createLabel(); + pauseButton.fixLayout(); + pauseButton.refresh(); + }; + this.controlBar.updateLabel = function () { var suffix = myself.world().isDevMode ? ' - ' + localize('development mode') : ''; @@ -863,7 +909,6 @@ IDE_Morph.prototype.createControlBar = function () { }; IDE_Morph.prototype.createCategories = function () { - // assumes the logo has already been created var myself = this; if (this.categories) { @@ -871,7 +916,7 @@ IDE_Morph.prototype.createCategories = function () { } this.categories = new Morph(); this.categories.color = this.groupColor; - this.categories.silentSetWidth(this.logo.width()); // width is fixed + this.categories.silentSetWidth(this.paletteWidth); function addCategoryButton(category) { var labelWidth = 75, @@ -919,7 +964,7 @@ IDE_Morph.prototype.createCategories = function () { buttonHeight = myself.categories.children[0].height(), border = 3, rows = Math.ceil((myself.categories.children.length) / 2), - xPadding = (myself.categories.width() + xPadding = (200 // myself.logo.width() - border - buttonWidth * 2) / 3, yPadding = 2, @@ -975,6 +1020,7 @@ IDE_Morph.prototype.createPalette = function (forSearching) { } this.palette.isDraggable = false; this.palette.acceptsDrops = true; + this.palette.enableAutoScrolling = false; this.palette.contents.acceptsDrops = false; this.palette.reactToDropOf = function (droppedMorph) { @@ -998,6 +1044,13 @@ IDE_Morph.prototype.createPalette = function (forSearching) { return this.palette; }; +IDE_Morph.prototype.createPaletteHandle = function () { + // assumes that the palette has already been created + if (this.paletteHandle) {this.paletteHandle.destroy(); } + this.paletteHandle = new PaletteHandleMorph(this.categories); + this.add(this.paletteHandle); +}; + IDE_Morph.prototype.createStage = function () { // assumes that the logo pane has already been created if (this.stage) {this.stage.destroy(); } @@ -1386,6 +1439,8 @@ IDE_Morph.prototype.createCorral = function () { var frame, template, padding = 5, myself = this; this.createStageHandle(); + this.createPaletteHandle(); + if (this.corral) { this.corral.destroy(); } @@ -1413,8 +1468,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; @@ -1487,7 +1544,8 @@ IDE_Morph.prototype.createCorral = function () { IDE_Morph.prototype.fixLayout = function (situation) { // situation is a string, i.e. // 'selectSprite' or 'refreshPalette' or 'tabEditor' - var padding = this.padding; + var padding = this.padding, + maxPaletteWidth; Morph.prototype.trackChanges = false; @@ -1500,12 +1558,14 @@ IDE_Morph.prototype.fixLayout = function (situation) { // categories this.categories.setLeft(this.logo.left()); this.categories.setTop(this.logo.bottom()); + this.categories.setWidth(this.paletteWidth); } // palette this.palette.setLeft(this.logo.left()); this.palette.setTop(this.categories.bottom()); this.palette.setHeight(this.bottom() - this.palette.top()); + this.palette.setWidth(this.paletteWidth); if (situation !== 'refreshPalette') { // stage @@ -1520,11 +1580,21 @@ IDE_Morph.prototype.fixLayout = function (situation) { this.stage.setScale(this.isSmallStage ? this.stageRatio : 1); this.stage.setTop(this.logo.bottom() + padding); this.stage.setRight(this.right()); + maxPaletteWidth = this.width() - + this.stage.width() - + this.spriteBar.tabBar.width() - + (this.padding * 2); + if (this.paletteWidth > maxPaletteWidth) { + this.paletteWidth = maxPaletteWidth; + this.fixLayout(); + } this.stageHandle.fixLayout(); + this.paletteHandle.fixLayout(); } // spriteBar - this.spriteBar.setPosition(this.logo.bottomRight().add(padding)); + this.spriteBar.setLeft(this.paletteWidth + padding); + this.spriteBar.setTop(this.logo.bottom() + padding); this.spriteBar.setExtent(new Point( Math.max(0, this.stage.left() - padding - this.spriteBar.left()), this.categories.bottom() - this.spriteBar.top() - padding @@ -1593,8 +1663,8 @@ IDE_Morph.prototype.setExtent = function (point) { ext = point.max(minExt); // adjust stage ratio if necessary - maxWidth = ext.x - (this.spriteBar.tabBar.fullBounds().right() - - this.left()); + maxWidth = ext.x - + (200 + this.spriteBar.tabBar.width() + (this.padding * 2)); minWidth = SpriteIconMorph.prototype.thumbSize.x * 3; maxHeight = (ext.y - SpriteIconMorph.prototype.thumbSize.y * 3.5); minRatio = minWidth / this.stage.dimensions.x; @@ -1654,10 +1724,6 @@ IDE_Morph.prototype.droppedSVG = function (anImage, name) { this.currentSprite.wearCostume(costume); this.spriteBar.tabBar.tabTo('costumes'); this.hasChangedMedia = true; - this.showMessage( - 'SVG costumes are\nnot yet fully supported\nin every browser', - 2 - ); }; IDE_Morph.prototype.droppedAudio = function (anAudio, name) { @@ -1669,9 +1735,11 @@ IDE_Morph.prototype.droppedAudio = function (anAudio, name) { IDE_Morph.prototype.droppedText = function (aString, name) { var lbl = name ? name.split('.')[0] : ''; if (aString.indexOf(' 0) { - libMenu.addItem( - line.substring(line.indexOf('\t') + 1), - function () { - loadLib( - line.substring(0, line.indexOf('\t')) - ); - } - ); - } - }); - - libMenu.popup(world, pos); - }, + ), 'Select categories of additional blocks to add to this project.' ); menu.addItem( localize(graphicsName) + '...', function () { - var dir = graphicsName, - names = myself.getCostumesList(dir), - libMenu = new MenuMorph( - myself, - localize('Import') + ' ' + localize(dir) - ); - - function loadCostume(name) { - var url = dir + '/' + name, - img = new Image(); - img.onload = function () { - var canvas = newCanvas(new Point(img.width, img.height)); - canvas.getContext('2d').drawImage(img, 0, 0); - myself.droppedImage(canvas, name); - }; - img.src = url; - } - - names.forEach(function (line) { - if (line.length > 0) { - libMenu.addItem( - line, - function () {loadCostume(line); } - ); - } - }); - libMenu.popup(world, pos); + myself.importMedia(graphicsName); }, 'Select a costume from the media library' ); menu.addItem( localize('Sounds') + '...', - function () { - var names = this.getCostumesList('Sounds'), - libMenu = new MenuMorph(this, 'Import sound'); - - function loadSound(name) { - var url = 'Sounds/' + name, + createMediaMenu( + 'Sounds', + function loadSound(file, name) { + var url = myself.resourceURL('Sounds', file), audio = new Audio(); audio.src = url; audio.load(); myself.droppedAudio(audio, name); } - - names.forEach(function (line) { - if (line.length > 0) { - libMenu.addItem( - line, - function () {loadSound(line); } - ); - } - }); - libMenu.popup(world, pos); - }, + ), 'Select a sound from the media library' ); menu.popup(world, pos); }; -IDE_Morph.prototype.getCostumesList = function (dirname) { - var dir, - costumes = []; +IDE_Morph.prototype.resourceURL = function () { + // Take in variadic inputs that represent an a nested folder structure. + // Method can be easily overridden if running in a custom location. + // Default Snap! simply returns a path (relative to snap.html) + var args = Array.prototype.slice.call(arguments, 0); + return args.join('/'); +}; - dir = this.getURL(dirname); - dir.split('\n').forEach( - function (line) { - var startIdx = line.search(new RegExp('href="[^./?].*"')), - endIdx, - name; +IDE_Morph.prototype.getMediaList = function (dirname) { + // Return a list of files in a directory based on the contents file + var url, data; - if (startIdx > 0) { - name = line.substring(startIdx + 6); - endIdx = name.search(new RegExp('"')); - name = name.substring(0, endIdx); - costumes.push(name); - } - } - ); - costumes.sort(function (x, y) { - return x < y ? -1 : 1; + url = this.resourceURL(dirname, dirname.toUpperCase()); + data = this.parseResourceFile(this.getURL(url)); + + data.sort(function (x, y) { + return x.name.toLowerCase() < y.name.toLowerCase() ? -1 : 1; }); - return costumes; + + return data; +}; + +IDE_Morph.prototype.parseResourceFile = function (text) { + // A Resource File lists all the files that could be loaded in a submenu + // Examples are libraries/LIBRARIES, Costumes/COSTUMES, etc + // A File is very simple: + // A "//" starts a comment line, that is ignored. + // All lines have 3 fields: file-name, Display Name, Help Text + // These fields are delimited by tabs. + var parts, + items = [], + comment = '//', + delimter = '\t'; + + text = text.split(/\n|\r\n/); + + text.map(function (line) { + return line.trim(); + }).filter(function (line) { + return line.length > 0 && line[0] !== comment; + }).forEach(function (line) { + parts = line.split(delimter); + parts = parts.map(function (str) { return str.trim(); }); + + if (parts.length < 2) { + return; + } + + items.push({ + file: parts[0], + name: parts[1], + help: parts.length > 2 ? parts[2] : '' + }); + }); + + return items; +}; + +IDE_Morph.prototype.importMedia = function (mediaType) { + // open a dialog box letting the user browse available "built-in" + // costumes or backgrounds + + var dialog = new DialogBoxMorph().withKey('import' + mediaType), + frame = new ScrollFrameMorph(), + selectedIcon = null, + turtle = new SymbolMorph('turtle', 60), + items = this.getMediaList(mediaType), + myself = this, + world = this.world(), + handle; + + frame.acceptsDrops = false; + frame.contents.acceptsDrops = false; + frame.color = myself.groupColor; + frame.fixLayout = nop; + dialog.labelString = mediaType; + dialog.createLabel(); + dialog.addBody(frame); + dialog.addButton('ok', 'Import'); + dialog.addButton('cancel', 'Cancel'); + + dialog.ok = function () { + if (selectedIcon) { + myself.droppedImage( + selectedIcon.object.contents, + selectedIcon.labelString + ); + } + }; + + dialog.fixLayout = function () { + var th = fontHeight(this.titleFontSize) + this.titlePadding * 2, + x = 0, + y = 0, + fp, fw; + this.buttons.fixLayout(); + this.body.setPosition(this.position().add(new Point( + this.padding, + th + this.padding + ))); + this.body.setExtent(new Point( + this.width() - this.padding * 2, + this.height() - this.padding * 3 - th - this.buttons.height() + )); + fp = this.body.position(); + fw = this.body.width(); + frame.contents.children.forEach(function (icon) { + icon.setPosition(fp.add(new Point(x, y))); + x += icon.width(); + if (x + icon.width() > fw) { + x = 0; + y += icon.height(); + } + }); + frame.contents.adjustBounds(); + this.label.setCenter(this.center()); + this.label.setTop(this.top() + (th - this.label.height()) / 2); + this.buttons.setCenter(this.center()); + this.buttons.setBottom(this.bottom() - this.padding); + }; + + items.forEach(function (item) { + var url = myself.resourceURL(mediaType, item.file), + img = new Image(), + icon = new CostumeIconMorph( + new Costume(turtle.image, item.name) + ); + icon.isDraggable = false; + icon.userMenu = nop; + icon.action = function () { + if (selectedIcon === icon) return; + var prevSelected = selectedIcon; + selectedIcon = icon; + if (prevSelected) prevSelected.refresh(); + }; + icon.doubleClickAction = dialog.ok; + icon.query = function () { + return icon === selectedIcon; + }; + frame.addContents(icon); + img.onload = function () { + var canvas = newCanvas(new Point(img.width, img.height), true); + canvas.getContext('2d').drawImage(img, 0, 0); + icon.object = new Costume(canvas, item.name); + icon.refresh(); + }; + img.src = url; + }); + dialog.popUp(world); + dialog.setExtent(new Point(400, 300)); + dialog.setCenter(world.center()); + dialog.drawNew(); + + handle = new HandleMorph( + dialog, + 300, + 280, + dialog.corner, + dialog.corner + ); }; // IDE_Morph menu actions @@ -2706,15 +3049,16 @@ IDE_Morph.prototype.aboutSnap = function () { module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn, world = this.world(); - aboutTxt = 'Snap! 4.0.2\nBuild Your Own Blocks\n\n' - + 'Copyright \u24B8 2015 Jens M\u00F6nig and ' + aboutTxt = 'Snap! 4.0.9.2\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' + 'Snap! is developed by the University of California, Berkeley\n' - + ' with support from the National Science Foundation, ' - + 'MioSoft, \n' - + 'and the Communications Design Group at SAP Labs. \n' + + ' with support from the National Science Foundation (NSF), ' + + 'MioSoft, \n' + + 'the Communications Design Group (CDG) at SAP Labs, and the\n' + + 'Human Advancement Research Community (HARC) at YC Research.\n' + 'The design of Snap! is influenced and inspired by Scratch,\n' + 'from the Lifelong Kindergarten group at the MIT Media Lab\n\n' @@ -2736,15 +3080,21 @@ IDE_Morph.prototype.aboutSnap = function () { + 'You should have received a copy of the\n' + 'GNU Affero General Public License along with this program.\n' - + 'If not, see http://www.gnu.org/licenses/'; + + 'If not, see http://www.gnu.org/licenses/\n\n' + + + 'Want to use Snap! but scared by the open-source license?\n' + + 'Get in touch with us, we\'ll make it work.'; creditsTxt = localize('Contributors') + '\n\nNathan Dinsmore: Saving/Loading, Snap-Logo Design, ' + '\ncountless bugfixes and optimizations' + '\nKartik Chandra: Paint Editor' + '\nMichael Ball: Time/Date UI, many bugfixes' - + '\n"Ava" Yuan Yuan: Graphic Effects' + + '\nBartosz Leper: Retina Display Support' + + '\nBernat Romagosa: Countless contributions' + + '\n"Ava" Yuan Yuan, Dylan Servilla: Graphic Effects' + '\nKyle Hotchkiss: Block search design' + + '\nBrian Broll: Many bugfixes and optimizations' + '\nIan Reynolds: UI Design, Event Bindings, ' + 'Sound primitives' + '\nIvan Motyashov: Initial Squeak Porting' @@ -2924,7 +3274,9 @@ IDE_Morph.prototype.newProject = function () { StageMorph.prototype.codeHeaders = {}; StageMorph.prototype.enableCodeMapping = false; StageMorph.prototype.enableInheritance = false; + StageMorph.prototype.enableSublistIDs = false; SpriteMorph.prototype.useFlatLineEnds = false; + Process.prototype.enableLiveCoding = false; this.setProjectName(''); this.projectNotes = ''; this.createStage(); @@ -2949,7 +3301,6 @@ IDE_Morph.prototype.save = function () { } }; - IDE_Morph.prototype.saveProject = function (name) { var myself = this; this.nextSteps([ @@ -2962,6 +3313,7 @@ IDE_Morph.prototype.saveProject = function (name) { ]); }; +// Serialize a project and save to the browser. IDE_Morph.prototype.rawSaveProject = function (name) { var str; if (name) { @@ -2984,59 +3336,28 @@ IDE_Morph.prototype.rawSaveProject = function (name) { } }; -IDE_Morph.prototype.saveProjectToDisk = function () { - var data, - link = document.createElement('a'); - if (Process.prototype.isCatchingErrors) { - try { - data = this.serializer.serialize(this.stage); - link.setAttribute('href', 'data:text/xml,' + data); - link.setAttribute('download', this.projectName + '.xml'); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } catch (err) { - this.showMessage('Saving failed: ' + err); - } - } else { - data = this.serializer.serialize(this.stage); - link.setAttribute('href', 'data:text/xml,' + data); - link.setAttribute('download', this.projectName + '.xml'); - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - } -}; +IDE_Morph.prototype.exportProject = function (name, plain, newWindow) { + // Export project XML, saving a file to disk + // newWindow requests displaying the project in a new tab. + var menu, str, dataPrefix; -IDE_Morph.prototype.exportProject = function (name, plain) { - var menu, str; if (name) { this.setProjectName(name); - if (Process.prototype.isCatchingErrors) { - try { - menu = this.showMessage('Exporting'); - str = encodeURIComponent( - this.serializer.serialize(this.stage) - ); - this.setURL('#open:' + str); - window.open('data:text/' - + (plain ? 'plain,' + str : 'xml,' + str)); - menu.destroy(); - this.showMessage('Exported!', 1); - } catch (err) { - this.showMessage('Export failed: ' + err); - } - } else { + dataPrefix = 'data:text/' + plain ? 'plain,' : 'xml,'; + try { menu = this.showMessage('Exporting'); - str = encodeURIComponent( - this.serializer.serialize(this.stage) - ); - this.setURL('#open:' + str); - window.open('data:text/' - + (plain ? 'plain,' + str : 'xml,' + str)); + str = this.serializer.serialize(this.stage); + this.setURL('#open:' + dataPrefix + encodeURIComponent(str)); + this.saveXMLAs(str, name, newWindow); menu.destroy(); this.showMessage('Exported!', 1); + } catch (err) { + if (Process.prototype.isCatchingErrors) { + this.showMessage('Export failed: ' + err); + } else { + throw err; + } } } }; @@ -3056,17 +3377,54 @@ IDE_Morph.prototype.exportGlobalBlocks = function () { } }; +IDE_Morph.prototype.removeUnusedBlocks = function () { + var targets = this.sprites.asArray().concat([this.stage]), + globalBlocks = this.stage.globalBlocks, + unused = [], + isDone = false, + found; + + function scan() { + return globalBlocks.filter(function (def) { + if (contains(unused, def)) {return false; } + return targets.every(function (each, trgIdx) { + return !(each.usesBlockInstance(def, true, trgIdx, unused)); + }); + }); + } + + while (!isDone) { + found = scan(); + if (found.length) { + unused = unused.concat(found); + } else { + isDone = true; + } + } + if (unused.length > 0) { + new BlockRemovalDialogMorph( + unused, + this.stage + ).popUp(this.world()); + } else { + this.inform( + 'Remove unused blocks', + 'there are currently no unused\n' + + 'global custom blocks in this project' + ); + } +}; + IDE_Morph.prototype.exportSprite = function (sprite) { - var str = encodeURIComponent( - this.serializer.serialize(sprite.allParts()) - ); - window.open('data:text/xml,' + str - + ''); + + ''; + this.saveXMLAs(str, sprite.name); }; IDE_Morph.prototype.exportScriptsPicture = function () { @@ -3115,8 +3473,289 @@ IDE_Morph.prototype.exportScriptsPicture = function () { y += padding; y += each.height; }); + this.saveCanvasAs(pic, this.projectName || localize('Untitled'), true); +}; - window.open(pic.toDataURL()); +IDE_Morph.prototype.exportProjectSummary = function (useDropShadows) { + var html, head, meta, css, body, pname, notes, toc, globalVars, + stage = this.stage; + + function addNode(tag, node, contents) { + if (!node) {node = body; } + return new XML_Element(tag, contents, node); + } + + function add(contents, tag, node) { + if (!tag) {tag = 'p'; } + if (!node) {node = body; } + return new XML_Element(tag, contents, node); + } + + function addImage(canvas, node, inline) { + if (!node) {node = body; } + var para = !inline ? addNode('p', node) : null, + pic = addNode('img', para || node); + pic.attributes.src = canvas.toDataURL(); + return pic; + } + + function addVariables(varFrame) { + var names = varFrame.names().sort(), + isFirst = true, + ul; + if (names.length) { + add(localize('Variables'), 'h3'); + names.forEach(function (name) { + /* + addImage( + SpriteMorph.prototype.variableBlock(name).scriptPic() + ); + */ + var watcher, listMorph, li, img; + + if (isFirst) { + ul = addNode('ul'); + isFirst = false; + } + li = addNode('li', ul); + watcher = new WatcherMorph( + name, + SpriteMorph.prototype.blockColor.variables, + varFrame, + name + ); + listMorph = watcher.cellMorph.contentsMorph; + if (listMorph instanceof ListWatcherMorph) { + listMorph.expand(); + } + img = addImage(watcher.fullImageClassic(), li); + img.attributes.class = 'script'; + }); + } + } + + function addBlocks(definitions) { + if (definitions.length) { + add(localize('Blocks'), 'h3'); + SpriteMorph.prototype.categories.forEach(function (category) { + var isFirst = true, + ul; + definitions.forEach(function (def) { + var li, blockImg; + if (def.category === category) { + if (isFirst) { + add( + localize( + category[0].toUpperCase().concat( + category.slice(1) + ) + ), + 'h4' + ); + ul = addNode('ul'); + isFirst = false; + } + li = addNode('li', ul); + blockImg = addImage( + def.templateInstance().scriptPic(), + li + ); + blockImg.attributes.class = 'script'; + def.sortedElements().forEach(function (script) { + var defImg = addImage( + script instanceof BlockMorph ? + script.scriptPic() + : script.fullImageClassic(), + li + ); + defImg.attributes.class = 'script'; + }); + } + }); + }); + } + } + + pname = this.projectName || localize('untitled'); + + html = new XML_Element('html'); + // html.attributes.contenteditable = 'true'; + + head = addNode('head', html); + + meta = addNode('meta', head); + meta.attributes['http-equiv'] = 'Content-Type'; + meta.attributes.content = 'text/html; charset=UTF-8'; + + if (useDropShadows) { + css = 'img {' + + 'vertical-align: top;' + + 'filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.5));' + + '-webkit-filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.5));' + + '-ms-filter: drop-shadow(2px 2px 4px rgba(0,0,0,0.5));' + + '}' + + '.toc {' + + 'vertical-align: middle;' + + 'padding: 2px 1em 2px 1em;' + + '}'; + } else { + css = 'img {' + + 'vertical-align: top;' + + '}' + + '.toc {' + + 'vertical-align: middle;' + + 'padding: 2px 1em 2px 1em;' + + '}' + + '.sprite {' + + 'border: 1px solid lightgray;' + + '}'; + } + addNode('style', head, css); + + add(pname, 'title', head); + + body = addNode('body', html); + add(pname, 'h1'); + + /* + if (SnapCloud.username) { + add(localize('by ') + SnapCloud.username); + } + */ + if (location.hash.indexOf('#present:') === 0) { + add(location.toString(), 'a', body).attributes.href = + location.toString(); + addImage( + stage.thumbnail(stage.dimensions) + ).attributes.class = 'sprite'; + add(this.serializer.app, 'h4'); + } else { + add(this.serializer.app, 'h4'); + addImage( + stage.thumbnail(stage.dimensions) + ).attributes.class = 'sprite'; + } + + // project notes + notes = Process.prototype.reportTextSplit(this.projectNotes, 'line'); + notes.asArray().forEach( + function (paragraph) {add(paragraph); } + ); + + // table of contents + add(localize('Contents'), 'h4'); + toc = addNode('ul'); + + // sprites & stage + this.sprites.asArray().concat([stage]).forEach(function (sprite) { + var tocEntry = addNode('li', toc), + scripts = sprite.scripts.sortedElements(), + cl = sprite.costumes.length(), + pic, + ol; + + addNode('hr'); + addImage( + sprite.thumbnail(new Point(40, 40)), + tocEntry, + true + ).attributes.class = 'toc'; + add(sprite.name, 'a', tocEntry).attributes.href = '#' + sprite.name; + + add(sprite.name, 'h2').attributes.id = sprite.name; + // if (sprite instanceof SpriteMorph || sprite.costume) { + pic = addImage( + sprite.thumbnail(sprite.extent().divideBy(stage.scale)) + ); + pic.attributes.class = 'sprite'; + if (sprite instanceof SpriteMorph) { + if (sprite.exemplar) { + addImage( + sprite.exemplar.thumbnail(new Point(40, 40)), + add(localize('Kind of') + ' ' + sprite.exemplar.name), + true + ).attributes.class = 'toc'; + } + if (sprite.anchor) { + addImage( + sprite.anchor.thumbnail(new Point(40, 40)), + add(localize('Part of') + ' ' + sprite.anchor.name), + true + ).attributes.class = 'toc'; + } + if (sprite.parts.length) { + add(localize('Parts'), 'h3'); + ol = addNode('ul'); + sprite.parts.forEach(function (part) { + var li = addNode('li', ol, part.name); + addImage(part.thumbnail(new Point(40, 40)), li, true) + .attributes.class = 'toc'; + }); + } + } + + // costumes + if (cl > 1 || (sprite.getCostumeIdx() !== cl)) { + add(localize('Costumes'), 'h3'); + ol = addNode('ol'); + sprite.costumes.asArray().forEach(function (costume) { + var li = addNode('li', ol, costume.name); + addImage(costume.thumbnail(new Point(40, 40)), li, true) + .attributes.class = 'toc'; + }); + } + + // sounds + if (sprite.sounds.length()) { + add(localize('Sounds'), 'h3'); + ol = addNode('ol'); + sprite.sounds.asArray().forEach(function (sound) { + add(sound.name, 'li', ol); + }); + } + + // variables + addVariables(sprite.variables); + + // scripts + if (scripts.length) { + add(localize('Scripts'), 'h3'); + scripts.forEach(function (script) { + var img = addImage(script instanceof BlockMorph ? + script.scriptPic() + : script.fullImageClassic()); + img.attributes.class = 'script'; + }); + } + + // custom blocks + addBlocks(sprite.customBlocks); + }); + + // globals + globalVars = stage.globalVariables(); + if (Object.keys(globalVars.vars).length || stage.globalBlocks.length) { + addNode('hr'); + add( + localize('For all Sprites'), + 'a', + addNode('li', toc) + ).attributes.href = '#global'; + add(localize('For all Sprites'), 'h2').attributes.id = 'global'; + + // variables + addVariables(globalVars); + + // custom blocks + addBlocks(stage.globalBlocks); + } + + this.saveFileAs( + '' + html.toString(), + 'text/html;charset=utf-8,', + pname, + true // request opening a new window. + ); }; IDE_Morph.prototype.openProjectString = function (str) { @@ -3144,6 +3783,8 @@ IDE_Morph.prototype.rawOpenProjectString = function (str) { StageMorph.prototype.codeHeaders = {}; StageMorph.prototype.enableCodeMapping = false; StageMorph.prototype.enableInheritance = false; + StageMorph.prototype.enableSublistIDs = false; + Process.prototype.enableLiveCoding = false; if (Process.prototype.isCatchingErrors) { try { this.serializer.openProject( @@ -3164,10 +3805,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 () { @@ -3186,6 +3828,8 @@ IDE_Morph.prototype.rawOpenCloudDataString = function (str) { StageMorph.prototype.codeHeaders = {}; 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); @@ -3315,11 +3959,130 @@ IDE_Morph.prototype.openProject = function (name) { }; IDE_Morph.prototype.setURL = function (str) { + // Set the URL to a project's XML contents if (this.projectsInURLs) { location.hash = str; } }; +IDE_Morph.prototype.saveFileAs = function ( + contents, + fileType, + fileName, + newWindow // (optional) defaults to false. +) { + /** Allow for downloading a file to a disk or open in a new tab. + This relies the FileSaver.js library which exports saveAs() + Two utility methods saveImageAs and saveXMLAs should be used first. + 1. Opening a new window uses standard URI encoding. + 2. downloading a file uses Blobs. + - every other combo is unsupposed. + */ + var blobIsSupported = false, + world = this.world(), + fileExt, + dataURI, dialog; + + // fileType is a /; format. + fileExt = fileType.split('/')[1].split(';')[0]; + // handle text/plain as a .txt file + fileExt = '.' + (fileExt === 'plain' ? 'txt' : fileExt); + + // This is a workaround for a known Chrome crash with large URLs + function exhibitsChomeBug(contents) { + var MAX_LENGTH = 2e6, + isChrome = navigator.userAgent.indexOf('Chrome') !== -1; + return isChrome && contents.length > MAX_LENGTH; + } + + function dataURItoBlob(text, mimeType) { + var i, + data = text, + components = text.split(','), + hasTypeStr = text.indexOf('data:') === 0; + // Convert to binary data, in format Blob() can use. + if (hasTypeStr && components[0].indexOf('base64') > -1) { + text = atob(components[1]); + data = new Uint8Array(text.length); + i = text.length; + while (i--) { + data[i] = text.charCodeAt(i); + } + } + return new Blob([data], {type: mimeType }); + } + + function dataURLFormat(text) { + var hasTypeStr = text.indexOf('data:') === 0; + if (hasTypeStr) {return text; } + return 'data:' + fileType + ',' + encodeURIComponent(text); + } + + try { + blobIsSupported = !!new Blob(); + } catch (e) {} + + if (newWindow) { + // Blob URIs need a custom URL to be displayed in a new window + if (contents instanceof Blob) { + dataURI = URL.createObjectURL(contents); + } else { + dataURI = dataURLFormat(contents); + } + + // Detect crashing errors - fallback to downloading if necessary + if (!exhibitsChomeBug(dataURI)) { + window.open(dataURI, fileName); + // Blob URIs should be "cleaned up" to reduce memory. + if (contents instanceof Blob) { + URL.revokeObjectURL(dataURI); + } + } else { + // (recursively) call this defauling newWindow to false + this.showMessage('download to disk text'); + this.saveFileAs(contents, fileType, fileName); + } + } else if (blobIsSupported) { + if (!(contents instanceof Blob)) { + contents = dataURItoBlob(contents, fileType); + } + // download a file and delegate to FileSaver + // false: Do not preprend a BOM to the file. + saveAs(contents, fileName + fileExt, false); + } else { + dialog = new DialogBoxMorph(); + dialog.inform( + localize('Could not export') + ' ' + fileName, + 'unable to export text', + world + ); + dialog.fixLayout(); + dialog.drawNew(); + } +}; + +IDE_Morph.prototype.saveCanvasAs = function (canvas, fileName, newWindow) { + // Export a Canvas object as a PNG image + // Note: This commented out due to poor browser support. + // cavas.toBlob() is currently supported in Firefox, IE, Chrome but + // browsers prevent easily saving the generated files. + // Do not re-enable without revisiting issue #1191 + // if (canvas.toBlob) { + // var myself = this; + // canvas.toBlob(function (blob) { + // myself.saveFileAs(blob, 'image/png', fileName, newWindow); + // }); + // return; + // } + + this.saveFileAs(canvas.toDataURL(), 'image/png', fileName, newWindow); +}; + +IDE_Morph.prototype.saveXMLAs = function(xml, fileName, newWindow) { + // wrapper to saving XML files with a proper type tag. + this.saveFileAs(xml, 'text/xml;chartset=utf-8', fileName, newWindow); +}; + IDE_Morph.prototype.switchToUserMode = function () { var world = this.world(); @@ -3342,7 +4105,11 @@ IDE_Morph.prototype.switchToUserMode = function () { // onto the World in user-mode world.reactToDropOf = function (morph) { if (!(morph instanceof DialogBoxMorph)) { - world.hand.grab(morph); + if (world.hand.grabOrigin) { + morph.slideBackTo(world.hand.grabOrigin); + } else { + world.hand.grab(morph); + } } }; this.showMessage('entering user mode', 1); @@ -3422,7 +4189,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; @@ -3497,8 +4264,8 @@ IDE_Morph.prototype.toggleInputSliders = function () { }; IDE_Morph.prototype.toggleSliderExecute = function () { - InputSlotMorph.prototype.executeOnSliderEdit = - !InputSlotMorph.prototype.executeOnSliderEdit; + ArgMorph.prototype.executeOnSliderEdit = + !ArgMorph.prototype.executeOnSliderEdit; }; IDE_Morph.prototype.toggleAppMode = function (appMode) { @@ -3509,6 +4276,7 @@ IDE_Morph.prototype.toggleAppMode = function (appMode) { this.controlBar.projectButton, this.controlBar.settingsButton, this.controlBar.stageSizeButton, + this.paletteHandle, this.stageHandle, this.corral, this.corralBar, @@ -3568,9 +4336,9 @@ IDE_Morph.prototype.toggleAppMode = function (appMode) { this.setExtent(this.world().extent()); // resume trackChanges }; -IDE_Morph.prototype.toggleStageSize = function (isSmall) { +IDE_Morph.prototype.toggleStageSize = function (isSmall, forcedRatio) { var myself = this, - smallRatio = 0.5, + smallRatio = forcedRatio || 0.5, world = this.world(), shiftClicked = (world.currentKey === 16), altClicked = (world.currentKey === 18); @@ -3590,7 +4358,7 @@ IDE_Morph.prototype.toggleStageSize = function (isSmall) { myself.stageRatio = targetRatio; delete myself.step; myself.fps = 0; - myself.isSmallStage = !(targetRatio === 1); + myself.isSmallStage = (targetRatio !== 1); myself.controlBar.stageSizeButton.refresh(); } else { count += 1; @@ -3628,6 +4396,29 @@ IDE_Morph.prototype.toggleStageSize = function (isSmall) { } }; +IDE_Morph.prototype.setPaletteWidth = function (newWidth) { + var count = 1, + steps = this.isAnimating ? 5 : 1, + world = this.world(), + myself = this; + + newWidth = Math.max(newWidth, 200); + this.fps = 30; + this.step = function () { + var diff; + if (count >= steps) { + myself.paletteWidth = newWidth; + delete myself.step; + myself.fps = 0; + } else { + count += 1; + diff = (myself.paletteWidth - newWidth) / 2; + myself.paletteWidth -= diff; + } + myself.setExtent(world.extent()); + }; +}; + IDE_Morph.prototype.createNewProject = function () { var myself = this; this.confirm( @@ -3659,7 +4450,10 @@ IDE_Morph.prototype.languageMenu = function () { menu.addItem( (SnapTranslator.language === lang ? '\u2713 ' : ' ') + SnapTranslator.languageName(lang), - function () {myself.setLanguage(lang); } + function () { + myself.loadNewProject = false; + myself.setLanguage(lang); + } ); }); menu.popup(world, pos); @@ -3667,7 +4461,7 @@ IDE_Morph.prototype.languageMenu = function () { IDE_Morph.prototype.setLanguage = function (lang, callback) { var translation = document.getElementById('language'), - src = 'lang-' + lang + '.js', + src = this.resourceURL('lang-' + lang + '.js'), myself = this; SnapTranslator.unload(); if (translation) { @@ -3686,7 +4480,8 @@ IDE_Morph.prototype.setLanguage = function (lang, callback) { }; IDE_Morph.prototype.reflectLanguage = function (lang, callback) { - var projectData; + var projectData, + urlBar = location.hash; SnapTranslator.language = lang; if (!this.loadNewProject) { if (Process.prototype.isCatchingErrors) { @@ -3706,6 +4501,7 @@ IDE_Morph.prototype.reflectLanguage = function (lang, callback) { this.fixLayout(); if (this.loadNewProject) { this.newProject(); + location.hash = urlBar; } else { this.openProjectString(projectData); } @@ -3768,6 +4564,7 @@ IDE_Morph.prototype.userSetBlocksScale = function () { block.drawNew(); block.setSpec(block.blockSpec); }); + scrpt.changed(); }; new DialogBoxMorph( @@ -3877,11 +4674,32 @@ IDE_Morph.prototype.setStageExtent = function (aPoint) { } }; +// IDE_Morph dragging threshold (internal feature) + +IDE_Morph.prototype.userSetDragThreshold = function () { + new DialogBoxMorph( + this, + function (num) { + MorphicPreferences.grabThreshold = Math.min( + Math.max(+num, 0), + 200 + ); + }, + this + ).prompt( + "Dragging threshold", + MorphicPreferences.grabThreshold.toString(), + this.world(), + null, // pic + null, // choices + null, // read only + true // numeric + ); +}; + // IDE_Morph cloud interface IDE_Morph.prototype.initializeCloud = function () { - //SnapCloud.reconnect(); - console.log("init cloud"); var myself = this, world = this.world(); new DialogBoxMorph( @@ -3891,16 +4709,17 @@ IDE_Morph.prototype.initializeCloud = function () { str; SnapCloud.login( user.username, - user.password, //pwh, + pwh, function () { - str = SnapCloud.encodeDict( - { - username: user.username, - } - ); - localStorage['-snap-user'] = str; - console.log(user.username) - SnapCloud.username = user.username; + if (user.choice) { + str = SnapCloud.encodeDict( + { + username: user.username, + password: pwh + } + ); + localStorage['-snap-user'] = str; + } myself.source = 'cloud'; myself.showMessage('now connected.', 2); }, @@ -3914,7 +4733,7 @@ IDE_Morph.prototype.initializeCloud = function () { null, null, null, - null, //'stay signed in on this computer\nuntil logging out', + 'stay signed in on this computer\nuntil logging out', world, myself.cloudIcon(), myself.cloudMsg @@ -3922,16 +4741,13 @@ IDE_Morph.prototype.initializeCloud = function () { }; IDE_Morph.prototype.createCloudAccount = function () { - - window.open('http://' + window.location.hostname + '/signup'); -/* var myself = this, world = this.world(); - +/* // force-logout, commented out for now: - //delete localStorage['-snap-user']; - //SnapCloud.clear(); - + delete localStorage['-snap-user']; + SnapCloud.clear(); +*/ new DialogBoxMorph( null, function (user) { @@ -3963,7 +4779,6 @@ IDE_Morph.prototype.createCloudAccount = function () { myself.cloudIcon(), myself.cloudMsg ); - */ }; IDE_Morph.prototype.resetCloudPassword = function () { @@ -4008,8 +4823,6 @@ IDE_Morph.prototype.resetCloudPassword = function () { }; IDE_Morph.prototype.changeCloudPassword = function () { - window.open('http://' + window.location.hostname + '/change_password'); - /* var myself = this, world = this.world(); new DialogBoxMorph( @@ -4036,7 +4849,7 @@ IDE_Morph.prototype.changeCloudPassword = function () { world, myself.cloudIcon(), myself.cloudMsg - );*/ + ); }; IDE_Morph.prototype.logout = function () { @@ -4072,33 +4885,19 @@ IDE_Morph.prototype.exportProjectMedia = function (name) { this.serializer.isCollectingMedia = true; if (name) { this.setProjectName(name); - if (Process.prototype.isCatchingErrors) { - try { - menu = this.showMessage('Exporting'); - encodeURIComponent( - this.serializer.serialize(this.stage) - ); - media = encodeURIComponent( - this.serializer.mediaXML(name) - ); - window.open('data:text/xml,' + media); - menu.destroy(); - this.showMessage('Exported!', 1); - } catch (err) { - this.serializer.isCollectingMedia = false; - this.showMessage('Export failed: ' + err); - } - } else { + try { menu = this.showMessage('Exporting'); - encodeURIComponent( - this.serializer.serialize(this.stage) - ); - media = encodeURIComponent( - this.serializer.mediaXML() - ); - window.open('data:text/xml,' + media); + media = this.serializer.mediaXML(name); + this.saveXMLAs(media, this.projectName + ' media'); menu.destroy(); this.showMessage('Exported!', 1); + } catch (err) { + if (Process.prototype.isCatchingErrors) { + this.serializer.isCollectingMedia = false; + this.showMessage('Export failed: ' + err); + } else { + throw err; + } } } this.serializer.isCollectingMedia = false; @@ -4114,10 +4913,8 @@ IDE_Morph.prototype.exportProjectNoMedia = function (name) { if (Process.prototype.isCatchingErrors) { try { menu = this.showMessage('Exporting'); - str = encodeURIComponent( - this.serializer.serialize(this.stage) - ); - window.open('data:text/xml,' + str); + str = this.serializer.serialize(this.stage); + this.saveXMLAs(str, this.projectName); menu.destroy(); this.showMessage('Exported!', 1); } catch (err) { @@ -4126,10 +4923,8 @@ IDE_Morph.prototype.exportProjectNoMedia = function (name) { } } else { menu = this.showMessage('Exporting'); - str = encodeURIComponent( - this.serializer.serialize(this.stage) - ); - window.open('data:text/xml,' + str); + str = this.serializer.serialize(this.stage); + this.saveXMLAs(str, this.projectName); menu.destroy(); this.showMessage('Exported!', 1); } @@ -4146,17 +4941,10 @@ IDE_Morph.prototype.exportProjectAsCloudData = function (name) { if (Process.prototype.isCatchingErrors) { try { menu = this.showMessage('Exporting'); - str = encodeURIComponent( - this.serializer.serialize(this.stage) - ); - media = encodeURIComponent( - this.serializer.mediaXML(name) - ); - dta = encodeURIComponent('') - + str - + media - + encodeURIComponent(''); - window.open('data:text/xml,' + dta); + str = this.serializer.serialize(this.stage); + media = this.serializer.mediaXML(name); + dta = '' + str + media + ''; + this.saveXMLAs(str, this.projectName); menu.destroy(); this.showMessage('Exported!', 1); } catch (err) { @@ -4165,17 +4953,10 @@ IDE_Morph.prototype.exportProjectAsCloudData = function (name) { } } else { menu = this.showMessage('Exporting'); - str = encodeURIComponent( - this.serializer.serialize(this.stage) - ); - media = encodeURIComponent( - this.serializer.mediaXML() - ); - dta = encodeURIComponent('') - + str - + media - + encodeURIComponent(''); - window.open('data:text/xml,' + dta); + str = this.serializer.serialize(this.stage); + media = this.serializer.mediaXML(name); + dta = '' + str + media + ''; + this.saveXMLAs(str, this.projectName); menu.destroy(); this.showMessage('Exported!', 1); } @@ -4302,7 +5083,7 @@ IDE_Morph.prototype.setCloudURL = function () { ); }; -// IDE_Morph synchronous Http data fetching +// IDE_Morph synchronous HTTP data fetching IDE_Morph.prototype.getURL = function (url) { var request = new XMLHttpRequest(), @@ -4315,23 +5096,7 @@ IDE_Morph.prototype.getURL = function (url) { } throw new Error('unable to retrieve ' + url); } catch (err) { - myself.showMessage(err); - return; - } -}; - -IDE_Morph.prototype.getURLsbeOrRelative = function (url) { - var request = new XMLHttpRequest(), - myself = this; - try { - request.open('GET', baseURL + url, false); - request.send(); - if (request.status === 200) { - return request.responseText; - } - return myself.getURL(url); - } catch (err) { - myself.showMessage(err); + myself.showMessage(err.toString()); return; } }; @@ -4389,7 +5154,6 @@ ProjectDialogMorph.uber = DialogBoxMorph.prototype; function ProjectDialogMorph(ide, label) { this.init(ide, label); - } ProjectDialogMorph.prototype.init = function (ide, task) { @@ -4398,7 +5162,7 @@ ProjectDialogMorph.prototype.init = function (ide, task) { // additional properties: this.ide = ide; this.task = task || 'open'; // String describing what do do (open, save) - this.source = 'local' // ide.source || 'local'; // or 'cloud' or 'examples' + this.source = ide.source || 'local'; // or 'cloud' or 'examples' this.projectList = []; // [{name: , thumb: , notes:}] this.handle = null; @@ -4430,7 +5194,6 @@ ProjectDialogMorph.prototype.init = function (ide, task) { this.onNextStep = function () { // yield to show "updating" message myself.setSource(myself.source); }; - }; ProjectDialogMorph.prototype.buildContents = function () { @@ -4688,7 +5451,6 @@ ProjectDialogMorph.prototype.setSource = function (source) { var myself = this, msg; - this.source = source; //this.task === 'save' ? 'local' : source; this.srcBar.children.forEach(function (button) { button.refresh(); @@ -4699,9 +5461,11 @@ ProjectDialogMorph.prototype.setSource = function (source) { this.projectList = []; SnapCloud.getProjectList( function (projectList) { - myself.installCloudProjectList(projectList); + // Don't show cloud projects if user has since switch panes. + if (myself.source === 'cloud') { + myself.installCloudProjectList(projectList); + } msg.destroy(); - console.log("installed"); }, function (err, lbl) { msg.destroy(); @@ -4748,20 +5512,23 @@ ProjectDialogMorph.prototype.setSource = function (source) { if (myself.task === 'open') { src = localStorage['-snap-project-' + item.name]; - xml = myself.ide.serializer.parse(src); - myself.notesText.text = xml.childNamed('notes').contents - || ''; - myself.notesText.drawNew(); - myself.notesField.contents.adjustBounds(); - myself.preview.texture = xml.childNamed('thumbnail').contents - || null; - myself.preview.cachedTexture = null; - myself.preview.drawNew(); + if (src) { + xml = myself.ide.serializer.parse(src); + + myself.notesText.text = xml.childNamed('notes').contents + || ''; + myself.notesText.drawNew(); + myself.notesField.contents.adjustBounds(); + myself.preview.texture = + xml.childNamed('thumbnail').contents || null; + myself.preview.cachedTexture = null; + myself.preview.drawNew(); + } } myself.edit(); }; - } else { // 'examples', 'cloud' is initialized elsewhere + } else { // 'examples'; 'cloud' is initialized elsewhere this.listField.action = function (item) { var src, xml; if (item === undefined) {return; } @@ -4769,7 +5536,7 @@ ProjectDialogMorph.prototype.setSource = function (source) { myself.nameField.setContents(item.name || ''); } src = myself.ide.getURL( - baseURL + 'Examples/' + item.name + '.xml' + myself.ide.resourceURL('Examples', item.file) ); xml = myself.ide.serializer.parse(src); @@ -4815,46 +5582,21 @@ ProjectDialogMorph.prototype.getLocalProjectList = function () { } } projects.sort(function (x, y) { - return x.name < y.name ? -1 : 1; + return x.name.toLowerCase() < y.name.toLowerCase() ? -1 : 1; }); return projects; }; ProjectDialogMorph.prototype.getExamplesProjectList = function () { - var dir, - projects = []; - //alert(baseURL); - - dir = this.ide.getURL(baseURL + 'Examples/'); - dir.split('\n').forEach( - function (line) { - var startIdx = line.search(new RegExp('href=".*xml"')), - endIdx, - name, - dta; - if (startIdx > 0) { - endIdx = line.search(new RegExp('.xml')); - name = line.substring(startIdx + 6, endIdx); - dta = { - name: name, - thumb: null, - notes: null - }; - projects.push(dta); - } - } - ); - projects = projects.sort(function (x, y) { - return x.name.toLowerCase() < y.name.toLowerCase() ? -1 : 1; - }); - return projects; + return this.ide.getMediaList('Examples'); }; ProjectDialogMorph.prototype.installCloudProjectList = function (pl) { var myself = this; this.projectList = pl || []; this.projectList.sort(function (x, y) { - return x.ProjectName < y.ProjectName ? -1 : 1; + return x.ProjectName.toLowerCase() < y.ProjectName.toLowerCase() ? + -1 : 1; }); this.listField.destroy(); @@ -4944,7 +5686,8 @@ ProjectDialogMorph.prototype.openProject = function () { if (this.source === 'cloud') { this.openCloudProject(proj); } else if (this.source === 'examples') { - src = this.ide.getURL(baseURL + 'Examples/' + proj.name + '.xml'); + // Note "file" is a property of the parseResourceFile function. + src = this.ide.getURL(this.ide.resourceURL('Examples', proj.file)); this.ide.openProjectString(src); this.destroy(); } else { // 'local' @@ -4970,11 +5713,17 @@ ProjectDialogMorph.prototype.rawOpenCloudProject = function (proj) { SnapCloud.reconnect( function () { SnapCloud.callService( - 'getProject', + 'getRawProject', function (response) { SnapCloud.disconnect(); + /* + if (myself.world().currentKey === 16) { + myself.ide.download(response); + return; + } + */ myself.ide.source = 'cloud'; - myself.ide.droppedText(response[0].SourceCode); + myself.ide.droppedText(response); if (proj.Public === 'true') { location.hash = '#present:Username=' + encodeURIComponent(SnapCloud.username) + @@ -5152,7 +5901,7 @@ ProjectDialogMorph.prototype.shareProject = function () { encodeURIComponent(usr.toLowerCase()) + '&ProjectName=' + encodeURIComponent(proj.ProjectName); - location.hash = projectId; + location.hash = 'present:' + projectId; } }, myself.ide.cloudError() @@ -5532,7 +6281,12 @@ SpriteIconMorph.prototype.userMenu = function () { menu.addItem( 'pic...', function () { - window.open(myself.object.fullImageClassic().toDataURL()); + var ide = myself.parentThatIsA(IDE_Morph); + ide.saveCanvasAs( + myself.object.fullImageClassic(), + this.object.name, + true + ); }, 'open a new window\nwith a picture of the stage' ); @@ -5632,6 +6386,7 @@ SpriteIconMorph.prototype.prepareToBeGrabbed = function () { var ide = this.parentThatIsA(IDE_Morph), idx; this.mouseClickLeft(); // select me + this.alpha = 0.85; if (ide) { idx = ide.sprites.asArray().indexOf(this.object); ide.sprites.remove(idx + 1); @@ -5640,6 +6395,10 @@ SpriteIconMorph.prototype.prepareToBeGrabbed = function () { } }; +SpriteIconMorph.prototype.justDropped = function () { + this.alpha = 1; +}; + SpriteIconMorph.prototype.wantsDropOf = function (morph) { // allow scripts & media to be copied from one sprite to another // by drag & drop @@ -5831,7 +6590,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 ); } }; @@ -5888,10 +6648,12 @@ CostumeIconMorph.prototype.removeCostume = function () { }; CostumeIconMorph.prototype.exportCostume = function () { + var ide = this.parentThatIsA(IDE_Morph); if (this.object instanceof SVG_Costume) { - window.open(this.object.contents.src); - } else { // rastered Costume - window.open(this.object.contents.toDataURL()); + // don't show SVG costumes in a new tab (shows text) + ide.saveFileAs(this.object.contents.src, 'text/svg', this.object.name); + } else { // rasterized Costume + ide.saveCanvasAs(this.object.contents, this.object.name, true); } }; @@ -6238,7 +7000,7 @@ WardrobeMorph.prototype.removeCostumeAt = function (idx) { WardrobeMorph.prototype.paintNew = function () { var cos = new Costume( - newCanvas(), + newCanvas(null, true), this.sprite.newCostumeName(localize('Untitled')) ), ide = this.parentThatIsA(IDE_Morph), @@ -6731,7 +7493,7 @@ StageHandleMorph.prototype.mouseDownLeft = function (pos) { } else { this.step = null; - ide.isSmallStage = !(ide.stageRatio === 1); + ide.isSmallStage = (ide.stageRatio !== 1); ide.controlBar.stageSizeButton.refresh(); } }; @@ -6749,3 +7511,91 @@ StageHandleMorph.prototype.mouseLeave = function () { this.changed(); }; +StageHandleMorph.prototype.mouseDoubleClick = function () { + this.target.parentThatIsA(IDE_Morph).toggleStageSize(true, 1); +}; + +// PaletteHandleMorph //////////////////////////////////////////////////////// + +// I am a horizontal resizing handle for a blocks palette +// I pseudo-inherit many things from StageHandleMorph + +// PaletteHandleMorph inherits from Morph: + +PaletteHandleMorph.prototype = new Morph(); +PaletteHandleMorph.prototype.constructor = PaletteHandleMorph; +PaletteHandleMorph.uber = Morph.prototype; + +// PaletteHandleMorph instance creation: + +function PaletteHandleMorph(target) { + this.init(target); +} + +PaletteHandleMorph.prototype.init = function (target) { + this.target = target || null; + HandleMorph.uber.init.call(this); + this.color = MorphicPreferences.isFlat ? + new Color(255, 255, 255) : new Color(190, 190, 190); + this.isDraggable = false; + this.noticesTransparentClick = true; + this.setExtent(new Point(12, 50)); +}; + +// PaletteHandleMorph drawing: + +PaletteHandleMorph.prototype.drawNew = + StageHandleMorph.prototype.drawNew; + +PaletteHandleMorph.prototype.drawOnCanvas = + StageHandleMorph.prototype.drawOnCanvas; + +// PaletteHandleMorph layout: + +PaletteHandleMorph.prototype.fixLayout = function () { + if (!this.target) {return; } + var ide = this.target.parentThatIsA(IDE_Morph); + this.setTop(this.target.top() + 10); + this.setRight(this.target.right()); + if (ide) {ide.add(this); } // come to front +}; + +// PaletteHandleMorph stepping: + +PaletteHandleMorph.prototype.step = null; + +PaletteHandleMorph.prototype.mouseDownLeft = function (pos) { + var world = this.world(), + offset = this.right() - pos.x, + ide = this.target.parentThatIsA(IDE_Morph); + + if (!this.target) { + return null; + } + this.step = function () { + var newPos; + if (world.hand.mouseButton) { + newPos = world.hand.bounds.origin.x + offset; + ide.paletteWidth = Math.min( + Math.max(200, newPos), + ide.stageHandle.left() - ide.spriteBar.tabBar.width() + ); + ide.setExtent(world.extent()); + + } else { + this.step = null; + } + }; +}; + +// PaletteHandleMorph events: + +PaletteHandleMorph.prototype.mouseEnter + = StageHandleMorph.prototype.mouseEnter; + +PaletteHandleMorph.prototype.mouseLeave + = StageHandleMorph.prototype.mouseLeave; + +PaletteHandleMorph.prototype.mouseDoubleClick = function () { + this.target.parentThatIsA(IDE_Morph).setPaletteWidth(200); +}; diff --git a/help/foreachof.png b/help/foreachof.png new file mode 100644 index 00000000..03b9d986 Binary files /dev/null and b/help/foreachof.png differ diff --git a/help/list->sentence.png b/help/list->sentence.png deleted file mode 100644 index 5fbf1f8b..00000000 Binary files a/help/list->sentence.png and /dev/null differ diff --git a/help/manual/.gitignore b/help/manual-LaTeX/.gitignore similarity index 100% rename from help/manual/.gitignore rename to help/manual-LaTeX/.gitignore diff --git a/help/manual-LaTeX/common/alonzo-waving.png b/help/manual-LaTeX/common/alonzo-waving.png new file mode 100644 index 00000000..4135a789 Binary files /dev/null and b/help/manual-LaTeX/common/alonzo-waving.png differ diff --git a/help/manual-LaTeX/common/boy1-walking.png b/help/manual-LaTeX/common/boy1-walking.png new file mode 100644 index 00000000..189bdd4f Binary files /dev/null and b/help/manual-LaTeX/common/boy1-walking.png differ diff --git a/help/manual-LaTeX/common/btn-cloud.png b/help/manual-LaTeX/common/btn-cloud.png new file mode 100644 index 00000000..4adb77c1 Binary files /dev/null and b/help/manual-LaTeX/common/btn-cloud.png differ diff --git a/help/manual-LaTeX/common/btn-file.png b/help/manual-LaTeX/common/btn-file.png new file mode 100644 index 00000000..7658b99f Binary files /dev/null and b/help/manual-LaTeX/common/btn-file.png differ diff --git a/help/manual-LaTeX/common/btn-new-sprite.png b/help/manual-LaTeX/common/btn-new-sprite.png new file mode 100644 index 00000000..951bd883 Binary files /dev/null and b/help/manual-LaTeX/common/btn-new-sprite.png differ diff --git a/help/manual-LaTeX/common/btn-settings.png b/help/manual-LaTeX/common/btn-settings.png new file mode 100644 index 00000000..ad28e651 Binary files /dev/null and b/help/manual-LaTeX/common/btn-settings.png differ diff --git a/help/manual-LaTeX/common/btn-start.png b/help/manual-LaTeX/common/btn-start.png new file mode 100644 index 00000000..f8033232 Binary files /dev/null and b/help/manual-LaTeX/common/btn-start.png differ diff --git a/help/manual-LaTeX/common/btn-stop.png b/help/manual-LaTeX/common/btn-stop.png new file mode 100644 index 00000000..62bbd0a7 Binary files /dev/null and b/help/manual-LaTeX/common/btn-stop.png differ diff --git a/help/manual-LaTeX/common/cover-picture.png b/help/manual-LaTeX/common/cover-picture.png new file mode 100644 index 00000000..6bd0abc6 Binary files /dev/null and b/help/manual-LaTeX/common/cover-picture.png differ diff --git a/help/manual-LaTeX/common/defs.tex b/help/manual-LaTeX/common/defs.tex new file mode 100644 index 00000000..86f93565 --- /dev/null +++ b/help/manual-LaTeX/common/defs.tex @@ -0,0 +1,187 @@ +\usepackage[T1]{fontenc} +\usepackage[utf8]{inputenc} +\usepackage{baskervald} % Default font +\usepackage[onehalfspacing]{setspace} +\usepackage{graphicx} +\usepackage{color} +\usepackage{textcomp} +\usepackage[hidelinks, pdfusetitle]{hyperref} +\usepackage{float} +\usepackage{ltxcmds} +\usepackage{fp} +\usepackage{tikz} +\usepackage{etoolbox} +% We frequently prevent page breaks before pictures, and it has a tendency to create a lot of widowed lines. Let's prevent it from happening. +\usepackage[all]{nowidow} +\usepackage{wasysym} + +\usetikzlibrary{calc} +\usetikzlibrary{fit} + +% This macro produces a "Snap!" logo. +% +% Note that in Polish (and other languages), nouns are inflected. The form "Snap!" with suffix looks plain stupid, so the macro takes a suffix as argument. If the suffix is non-empty, no exclamation mark is generated, and the suffix is used instead. +\newcommand{\Snap}[1]{% + \textsf{% + Snap% + \ifx\\#1\\\textit{!\@}% + \else #1% + \fi% + }% +} + +\newcommand{\code}[1]{\textsf{\textbf{#1}}} +\makeatletter +\newcommand{\defaultGraphicsScale}{0.6} +\makeatother + +\renewcommand{\thechapter}{\Roman{chapter}} +\renewcommand{\thesection}{\Alph{section}} +\renewcommand{\thesubsection}{} + +\makeatletter +\edef\defaultFontSize{\f@size} + +% A dirty hack: Apparently, this is the only way to multiply a length by a constant factor in the code below. What a mess. +\newdimen\raiseLength + +\newcommand{\inlinepicraised}[3]{% + {% + \FPeval\fontSizeRatio{\f@size / \defaultFontSize}% + \FPeval\scale{#3 * \fontSizeRatio}% + \raiseLength=#2% + \raisebox{\fontSizeRatio\raiseLength}{% + \includegraphics[scale=\scale]{#1}% + }% + }% + % Add kerns if the next character is punctuation. + \ltx@ifnextchar@nospace.{\,}{% + \ltx@ifnextchar@nospace,{\,}{% + }% + }% +} +\makeatother + +\newcommand{\inlinepic}[2][\defaultGraphicsScale]{% + \inlinepicraised{#2}{-4pt}{#1}% +} + +\newcommand{\inlinereporterpic}[2][\defaultGraphicsScale]{% + \inlinepicraised{#2}{-1.5pt}{#1}% +} + +\newcommand{\bigpic}[1]{ + \begin{figure}[H] + \centering + \includegraphics[scale=\defaultGraphicsScale]{#1} + \end{figure} +} + +\newcommand{\manualtitlepage}[2][]{ { + \def\title{#2} + \def\translator{#1} + + \begin{titlepage} + + \pgfdeclarelayer{background} + \pgfsetlayers{background,main} + \begin{tikzpicture}[remember picture, overlay, text depth=0] + + \definecolor{title blue}{HTML}{206C8F} + \definecolor{title orange}{HTML}{F59201} + \pgfdeclareimage[width=0.2\textwidth]{logo}{../common/logo} + \pgfdeclareimage{cover picture}{../common/cover-picture} + \coordinate (NW) at ($(current page.north west) + (0.5, -0.5)$); + \coordinate (NE) at ($(current page.north east) + (-0.5, -0.5)$); + \coordinate (SW) at ($(current page.south west) + (0.5, 0.5)$); + \coordinate (SE) at ($(current page.south east) + (-0.5, 0.5)$); + + % Logo at the top left corner. + \node[ + below right, + align=left, + font=\Large, + execute at begin node=\setlength\baselineskip{3ex}] + at ($(NW) + (1.5, -1.5)$) { + \pgfuseimage{logo}\\ + Build Your Own Blocks + }; + + % Version number. + \node[ + below right, + color=white, + font=\fontsize{40pt}{48pt}\selectfont] + at ($(NE) - (7, 5)$) { + 4.0 + }; + + % Title. Note: the % at the end is necessary for correct spacing between the text and orange background. + \node[ + left, + text=white, + align=right, + inner sep=15pt, + font=\fontsize{40pt}{42pt}\selectfont] + (title text) + at ($(NE) - (1.5, 9)$) { + \title% + }; + + % The cover picture. + \node[below left, inner sep=0] (cover picture) + at ($(NE) - (0, 12)$) { + \pgfuseimage{cover picture} + }; + \draw[white, ultra thick] + (cover picture.north west) -- (cover picture.north east); + \draw[white, ultra thick] + (cover picture.south west) -- (cover picture.south east); + + % Authors. + \node[above right, color=white, align=left, font=\Large] + at ($(SE) + (-7, 3)$) { + Brian Harvey\\ + Jens M\"onig + }; + + % Translator. + \ifdefempty{\translator}{}{ + \node [below right, color=white, align=left, font=\large] + at ($(SE) + (-7, 2)$) { + \translator + }; + } + + \begin{pgfonlayer}{background} + % Blue bar on the right. + \fill[title blue] ($(NE) - (7.5, 0)$) rectangle (SE); + + % Title background stretches to the left until it reaches the left edge, which means (NW.x, title text.west.y). + \coordinate (title background left point) + at (NW |- title text.west); + % Orange title background. + \node[ + fit=(title text) (title background left point), + shape=rectangle, + fill=title orange, + text=white, + draw=white, + ultra thick, + align=right, + inner sep=0, + font=\fontsize{40pt}{42pt}\selectfont] {}; + \end{pgfonlayer} + + \end{tikzpicture} + \end{titlepage} +} } + +\newcommand{\TODO}[1]{% + \colorbox{yellow}{\textcolor{red}{\textbf{% + TODO% + \ifstrempty{#1}{}{% + : #1 + }% + }}}% +} \ No newline at end of file diff --git a/help/manual-LaTeX/common/dog2-c.png b/help/manual-LaTeX/common/dog2-c.png new file mode 100644 index 00000000..6b314d25 Binary files /dev/null and b/help/manual-LaTeX/common/dog2-c.png differ diff --git a/help/manual-LaTeX/common/logo.png b/help/manual-LaTeX/common/logo.png new file mode 100644 index 00000000..38ac4abe Binary files /dev/null and b/help/manual-LaTeX/common/logo.png differ diff --git a/help/manual-LaTeX/common/square.png b/help/manual-LaTeX/common/square.png new file mode 100644 index 00000000..e1512ecd Binary files /dev/null and b/help/manual-LaTeX/common/square.png differ diff --git a/help/manual-LaTeX/common/tree.png b/help/manual-LaTeX/common/tree.png new file mode 100644 index 00000000..ff76cf76 Binary files /dev/null and b/help/manual-LaTeX/common/tree.png differ diff --git a/help/manual-LaTeX/common/turtle-says-its-position.png b/help/manual-LaTeX/common/turtle-says-its-position.png new file mode 100644 index 00000000..983d55de Binary files /dev/null and b/help/manual-LaTeX/common/turtle-says-its-position.png differ diff --git a/help/manual-LaTeX/common/turtle-says-its-rounded-position.png b/help/manual-LaTeX/common/turtle-says-its-rounded-position.png new file mode 100644 index 00000000..62a6af51 Binary files /dev/null and b/help/manual-LaTeX/common/turtle-says-its-rounded-position.png differ diff --git a/help/manual-LaTeX/common/wiggling-line.png b/help/manual-LaTeX/common/wiggling-line.png new file mode 100644 index 00000000..52a075a8 Binary files /dev/null and b/help/manual-LaTeX/common/wiggling-line.png differ diff --git a/help/manual-LaTeX/en/ask-and-set.png b/help/manual-LaTeX/en/ask-and-set.png new file mode 100644 index 00000000..18bf46cf Binary files /dev/null and b/help/manual-LaTeX/en/ask-and-set.png differ diff --git a/help/manual-LaTeX/en/block-editor-square.png b/help/manual-LaTeX/en/block-editor-square.png new file mode 100644 index 00000000..adf37092 Binary files /dev/null and b/help/manual-LaTeX/en/block-editor-square.png differ diff --git a/help/manual-LaTeX/en/block-prototype-with-plus-highlighted.pdf b/help/manual-LaTeX/en/block-prototype-with-plus-highlighted.pdf new file mode 100644 index 00000000..d84c50bc Binary files /dev/null and b/help/manual-LaTeX/en/block-prototype-with-plus-highlighted.pdf differ diff --git a/help/manual-LaTeX/en/block-prototype-with-plus-highlighted.svg b/help/manual-LaTeX/en/block-prototype-with-plus-highlighted.svg new file mode 100644 index 00000000..f4c6035a --- /dev/null +++ b/help/manual-LaTeX/en/block-prototype-with-plus-highlighted.svg @@ -0,0 +1,751 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/help/manual-LaTeX/en/broadcast-bark-and-wait.png b/help/manual-LaTeX/en/broadcast-bark-and-wait.png new file mode 100644 index 00000000..149018e0 Binary files /dev/null and b/help/manual-LaTeX/en/broadcast-bark-and-wait.png differ diff --git a/help/manual-LaTeX/en/concise-factorial-block-script.png b/help/manual-LaTeX/en/concise-factorial-block-script.png new file mode 100644 index 00000000..2203fd46 Binary files /dev/null and b/help/manual-LaTeX/en/concise-factorial-block-script.png differ diff --git a/help/manual-LaTeX/en/create-input-name.png b/help/manual-LaTeX/en/create-input-name.png new file mode 100644 index 00000000..79ab7ce3 Binary files /dev/null and b/help/manual-LaTeX/en/create-input-name.png differ diff --git a/help/manual-LaTeX/en/dragging-block-input.png b/help/manual-LaTeX/en/dragging-block-input.png new file mode 100644 index 00000000..00bd8a17 Binary files /dev/null and b/help/manual-LaTeX/en/dragging-block-input.png differ diff --git a/help/manual-LaTeX/en/dragging-block-into-block-editor.png b/help/manual-LaTeX/en/dragging-block-into-block-editor.png new file mode 100644 index 00000000..4b1b5ff2 Binary files /dev/null and b/help/manual-LaTeX/en/dragging-block-into-block-editor.png differ diff --git a/help/manual-LaTeX/en/example-script-that-uses-a-reporter.png b/help/manual-LaTeX/en/example-script-that-uses-a-reporter.png new file mode 100644 index 00000000..b06db791 Binary files /dev/null and b/help/manual-LaTeX/en/example-script-that-uses-a-reporter.png differ diff --git a/help/manual-LaTeX/en/factorial-5-with-result.png b/help/manual-LaTeX/en/factorial-5-with-result.png new file mode 100644 index 00000000..ca42ea1f Binary files /dev/null and b/help/manual-LaTeX/en/factorial-5-with-result.png differ diff --git a/help/manual-LaTeX/en/factorial-block-script.png b/help/manual-LaTeX/en/factorial-block-script.png new file mode 100644 index 00000000..07368c59 Binary files /dev/null and b/help/manual-LaTeX/en/factorial-block-script.png differ diff --git a/help/manual-LaTeX/en/hand-waving-command.png b/help/manual-LaTeX/en/hand-waving-command.png new file mode 100644 index 00000000..3aa2f1e7 Binary files /dev/null and b/help/manual-LaTeX/en/hand-waving-command.png differ diff --git a/help/manual-LaTeX/en/if.png b/help/manual-LaTeX/en/if.png new file mode 100644 index 00000000..190b288b Binary files /dev/null and b/help/manual-LaTeX/en/if.png differ diff --git a/help/manual-LaTeX/en/inter-sprite-communication-1.png b/help/manual-LaTeX/en/inter-sprite-communication-1.png new file mode 100644 index 00000000..5956ff98 Binary files /dev/null and b/help/manual-LaTeX/en/inter-sprite-communication-1.png differ diff --git a/help/manual-LaTeX/en/inter-sprite-communication-2.png b/help/manual-LaTeX/en/inter-sprite-communication-2.png new file mode 100644 index 00000000..cec24727 Binary files /dev/null and b/help/manual-LaTeX/en/inter-sprite-communication-2.png differ diff --git a/help/manual-LaTeX/en/join-hello-world.png b/help/manual-LaTeX/en/join-hello-world.png new file mode 100644 index 00000000..ab3163b2 Binary files /dev/null and b/help/manual-LaTeX/en/join-hello-world.png differ diff --git a/help/manual-LaTeX/en/make-a-block.png b/help/manual-LaTeX/en/make-a-block.png new file mode 100644 index 00000000..f3c99c3c Binary files /dev/null and b/help/manual-LaTeX/en/make-a-block.png differ diff --git a/help/manual-LaTeX/en/make-a-variable.png b/help/manual-LaTeX/en/make-a-variable.png new file mode 100644 index 00000000..a4062186 Binary files /dev/null and b/help/manual-LaTeX/en/make-a-variable.png differ diff --git a/help/manual-LaTeX/en/mouse-down.png b/help/manual-LaTeX/en/mouse-down.png new file mode 100644 index 00000000..cab1b671 Binary files /dev/null and b/help/manual-LaTeX/en/mouse-down.png differ diff --git a/help/manual/en/move-10-steps.png b/help/manual-LaTeX/en/move-10-steps.png similarity index 100% rename from help/manual/en/move-10-steps.png rename to help/manual-LaTeX/en/move-10-steps.png diff --git a/help/manual-LaTeX/en/move-mouse-down-steps.png b/help/manual-LaTeX/en/move-mouse-down-steps.png new file mode 100644 index 00000000..6c52b1a9 Binary files /dev/null and b/help/manual-LaTeX/en/move-mouse-down-steps.png differ diff --git a/help/manual-LaTeX/en/nested-sprites-in-corral.png b/help/manual-LaTeX/en/nested-sprites-in-corral.png new file mode 100644 index 00000000..339b206a Binary files /dev/null and b/help/manual-LaTeX/en/nested-sprites-in-corral.png differ diff --git a/help/manual-LaTeX/en/next-costume.png b/help/manual-LaTeX/en/next-costume.png new file mode 100644 index 00000000..27f7ca99 Binary files /dev/null and b/help/manual-LaTeX/en/next-costume.png differ diff --git a/help/manual-LaTeX/en/play-sound-help-until-done.png b/help/manual-LaTeX/en/play-sound-help-until-done.png new file mode 100644 index 00000000..e7b65abb Binary files /dev/null and b/help/manual-LaTeX/en/play-sound-help-until-done.png differ diff --git a/help/manual-LaTeX/en/play-sound-help.png b/help/manual-LaTeX/en/play-sound-help.png new file mode 100644 index 00000000..6e6c27c6 Binary files /dev/null and b/help/manual-LaTeX/en/play-sound-help.png differ diff --git a/help/manual-LaTeX/en/plus.png b/help/manual-LaTeX/en/plus.png new file mode 100644 index 00000000..9b2aa99c Binary files /dev/null and b/help/manual-LaTeX/en/plus.png differ diff --git a/help/manual-LaTeX/en/predicates-and-conditional-evaluation-1.png b/help/manual-LaTeX/en/predicates-and-conditional-evaluation-1.png new file mode 100644 index 00000000..f20ac007 Binary files /dev/null and b/help/manual-LaTeX/en/predicates-and-conditional-evaluation-1.png differ diff --git a/help/manual-LaTeX/en/predicates-and-conditional-evaluation-2.png b/help/manual-LaTeX/en/predicates-and-conditional-evaluation-2.png new file mode 100644 index 00000000..b77f9f16 Binary files /dev/null and b/help/manual-LaTeX/en/predicates-and-conditional-evaluation-2.png differ diff --git a/help/manual-LaTeX/en/predicates-and-conditional-evaluation-3.png b/help/manual-LaTeX/en/predicates-and-conditional-evaluation-3.png new file mode 100644 index 00000000..e11c1f7f Binary files /dev/null and b/help/manual-LaTeX/en/predicates-and-conditional-evaluation-3.png differ diff --git a/help/manual-LaTeX/en/predicates-and-conditional-evaluation-4.png b/help/manual-LaTeX/en/predicates-and-conditional-evaluation-4.png new file mode 100644 index 00000000..629d037a Binary files /dev/null and b/help/manual-LaTeX/en/predicates-and-conditional-evaluation-4.png differ diff --git a/help/manual-LaTeX/en/round-x-position-plus-100.png b/help/manual-LaTeX/en/round-x-position-plus-100.png new file mode 100644 index 00000000..411fc744 Binary files /dev/null and b/help/manual-LaTeX/en/round-x-position-plus-100.png differ diff --git a/help/manual-LaTeX/en/script-variable-name-dialog.png b/help/manual-LaTeX/en/script-variable-name-dialog.png new file mode 100644 index 00000000..900a5d14 Binary files /dev/null and b/help/manual-LaTeX/en/script-variable-name-dialog.png differ diff --git a/help/manual-LaTeX/en/script-variables-a-b-c.png b/help/manual-LaTeX/en/script-variables-a-b-c.png new file mode 100644 index 00000000..42e399d8 Binary files /dev/null and b/help/manual-LaTeX/en/script-variables-a-b-c.png differ diff --git a/help/manual-LaTeX/en/scripting-area-with-additional-costume.png b/help/manual-LaTeX/en/scripting-area-with-additional-costume.png new file mode 100644 index 00000000..27d82938 Binary files /dev/null and b/help/manual-LaTeX/en/scripting-area-with-additional-costume.png differ diff --git a/help/manual-LaTeX/en/sign-in.png b/help/manual-LaTeX/en/sign-in.png new file mode 100644 index 00000000..2452506a Binary files /dev/null and b/help/manual-LaTeX/en/sign-in.png differ diff --git a/help/manual-LaTeX/en/sign-up.png b/help/manual-LaTeX/en/sign-up.png new file mode 100644 index 00000000..b6989342 Binary files /dev/null and b/help/manual-LaTeX/en/sign-up.png differ diff --git a/help/manual-LaTeX/en/snap-manual.tex b/help/manual-LaTeX/en/snap-manual.tex new file mode 100644 index 00000000..5dbf60c0 --- /dev/null +++ b/help/manual-LaTeX/en/snap-manual.tex @@ -0,0 +1,525 @@ +\documentclass{report} + +\input{../common/defs.tex} + +\begin{document} + +\title{Snap! Reference Manual} +\author{Brian Harvey\texorpdfstring{ \and}{,} Jens M\"onig} +\date{} + +\manualtitlepage{Snap! Reference Manual} + +\tableofcontents + +\chapter*{} +\section*{Acknowledgements} + +We have been extremely lucky in our mentors. Jens cut his teeth in the company of the Smalltalk pioneers: Alan Kay, Dan Ingalls, and the rest of the gang who invented personal computing and object oriented programming in the great days of Xerox PARC. He worked with John Maloney, of the MIT Scratch Team, who developed the Morphic graphics framework that's still at the heart of \Snap{}. The brilliant design of Scratch, from the Lifelong Kindergarten Group at the MIT Media Lab, is crucial to \Snap{}. + +\textbf{\emph{Our earlier version, BYOB, was a direct modification of the Scratch source code. \Snap{} is a complete rewrite, but its code structure and its user interface remain deeply indebted to Scratch. And the Scratch Team, who could have seen us as rivals, have been entirely supportive and welcoming to us.}} + +Brian grew up at the MIT and Stanford Artificial Intelligence Labs, learning from Lisp inventor John McCarthy, Scheme inventors Gerald~J. Sussman and Guy Steele, and the authors of the world's best computer science book, \textit{Structure and Interpretation of Computer Programs}, Hal Abelson and Gerald~J. Sussman with Julie Sussman, among many other heroes of computer science. + +\textbf{\emph{In the glory days of the MIT Logo Lab, we used to say, ``Logo is Lisp disguised as BASIC.'' Now, with its first class procedures, lexical scope, and first class continuations, \Snap{} is Scheme disguised as Scratch.}} + +We have been fortunate to get to know an amazing group of brilliant middle school (!\@) and high school students through the Scratch Advanced Topics forum, several of whom have contributed code to \Snap{}: Kartik Chandra, Nathan Dinsmore, Connor Hudson, and Ian Reynolds. Many more have contributed ideas and alpha-testing bug reports. UC Berkeley students who've contributed code include Michael Ball, Achal Dave, Kyle Hotchkiss, Ivan Motyashov, and Yuan Yuan. Contributors of translations are too numerous to list here, but they're in the ``About\ldots'' box in \Snap{} itself. + +This work was supported in part by the National Science Foundation grant 1143566, and in part by MioSoft. + +\clearpage + +\begin{center} +\bf \Huge \Snap{} Reference Manual \\ +\huge Version 4.0 +\vspace{40pt} +\end{center} + +\Snap{} (formerly BYOB) is an extended reimplementation of Scratch (\url{http://scratch.mit.edu}) that allows you to Build Your Own Blocks. It also features first class lists, first class procedures, and continuations. These added capabilities make it suitable for a serious introduction to computer science for high school or college students. + +To run \Snap{}, open a browser window and connect to either \url{http://snap.berkeley.edu/run} to start with a minimal set of blocks or \url{http://snap.berkeley.edu/init} to load a small set of additional blocks (a little slower startup, but recommended for convenience and assumed in this manual). + +\clearpage + +\chapter{Blocks, Scripts, and Sprites} + +This chapter describes the \Snap{} features inherited from Scratch; experienced Scratch users can skip to section~\ref{sec:nesting-sprites}. + +\Snap{} is a programming language---a notation in which you can tell a computer what you want it to do. Unlike most programming languages, though, \Snap{} is a visual language; instead of writing a program using the keyboard, the \Snap{} programmer uses the same drag-and-drop interface familiar to computer users. + +Start \Snap{}. You should see the following arrangement of regions in the window:\nopagebreak + +\begin{center} +\includegraphics[width=\textwidth]{window-regions} +\end{center} + +(The proportions of these areas may be different, depending on the size and shape of your browser window.) + +A \Snap{} program consists of one or more \emph{scripts}, each of which is made of \emph{blocks}. Here's a typical script:\nopagebreak + +\label{fig:typical-script} +\bigpic{typical-script} + +The five blocks that make up this script have three different colors, corresponding to three of the eight \emph{palettes} in which blocks can be found. The palette area at the left edge of the window shows one palette at a time, chosen with the eight buttons just above the palette area. In this script, the gold blocks are from the Control palette; the green block is from the Pen palette; and the blue blocks are from the Motion palette. A script is assembled by dragging blocks from a palette into the \emph{scripting area} in the middle part of the window. Blocks snap together (hence the name \Snap{} for the language) when you drag a block so that its indentation is near the tab of the one above it:\nopagebreak + +\bigpic{snapping-blocks} + +The white horizontal line is a signal that if you let go of the green block it will snap into the tab of the gold one. + +\subsection{Hat Blocks and Command Blocks} + +At the top of the script is a \emph{hat} block, which indicates when the script should be carried out. Hat block names typically start with the word ``\code{when}''; in this example, the script should be run when the green flag near the right end of the \Snap{} tool bar is clicked. (The \Snap{} tool bar is part of the \Snap{} window, not the same as the browser's or operating system's menu bar.) A script isn't required to have a hat block, but if not, then the script will be run only if the user clicks on the script itself. A script can't have more than one hat block, and the hat block can be used only at the top of the script; its distinctive shape is meant to remind you of that. + +The other blocks in this script are \emph{command} blocks. Each command block corresponds to an action that \Snap{} already knows how to carry out. For example, the block \inlinepic{move-10-steps} tells the sprite (the arrowhead shape on the \emph{stage} at the right end of the window) to move ten steps (a step is a very small unit of distance) in the direction in which the arrowhead is pointing. We'll see shortly that there can be more than one sprite, and that each sprite has its own scripts. Also, a sprite doesn't have to look like an arrowhead, but can have any picture as a costume. The shape of the \code{move} block is meant to remind you of a Lego\texttrademark{} brick; a script is a stack of blocks. (The word ``block'' denotes both the graphical shape on the screen and the procedure, the action, that the block carries out.) + +The number~$10$ in the \code{move} block above is called an \emph{input} to the block. By clicking on the white oval, you can type any number in place of the $10$. The sample script on page~\pageref{fig:typical-script} uses $100$ as the input value. We'll see later that inputs can have non-oval shapes that accept values other than numbers. We'll also see that you can compute input values, instead of typing a particular value into the oval. A block can have more than one input slot. For example, the \code{glide} block located about halfway down the Motion palette has three inputs. + +Most command blocks have that brick shape, but some, like the \code{repeat} block in the sample script, are \emph{C-shaped}. Most C-shaped blocks are found in the Control palette. The slot inside the C shape is a special kind of input slot that accepts a \emph{script} as the input. In the sample script, the \code{repeat} block has two inputs: the number $4$ and the script\nopagebreak + +\bigpic{typical-script-inner} + +\section{Sprites and Parallelism} + +Just below the stage is the ``new sprite'' button~\inlinepic{../common/btn-new-sprite}. Click the button to add a new sprite to the stage. The new sprite will appear in a random position on the stage, facing in a random direction, with a random color. + +Each sprite has its own scripts. To see the scripts for a particular sprite in the scripting area, click on the picture of that sprite in the \emph{sprite corral} in the bottom right corner of the window. Try putting one of the following scripts in each sprite's scripting area:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{sprites-and-parallelism-1} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{sprites-and-parallelism-2} +\end{minipage} +\end{figure} + +When you click the green flag~\inlinepic{../common/btn-start}, you should see one sprite rotate while the other moves back and forth. This experiment illustrates the way different scripts can run in parallel. The turning and the moving happen together. Parallelism can be seen with multiple scripts of a single sprite also. Try this example:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{sprites-and-parallelism-3} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{sprites-and-parallelism-4} +\end{minipage} +\end{figure} + +When you press the space key, the sprite should turn forever in a circle, because the \code{move} and \code{turn} blocks are run in parallel. (To stop the program, click the red stop sign~\inlinepic{../common/btn-stop} at the right end of the tool bar.) + +\subsection{Costumes and Sounds} + +To change the appearance of a sprite, import a new \emph{costume} for it. There are three ways to do this. First, select the desired sprite in the sprite corral. Then, one way is to click on the file icon~\inlinepic{../common/btn-file} in the tool bar, then choose the ``\code{Costumes\ldots}'' menu item. You will see a list of costumes from the public media library, and can choose one. The second way, for a costume stored on your own computer, is too click on the file icon and choose the ``\code{Import\ldots}'' menu item. You can then select a file in any picture format (PNG, JPEG, etc.) supported by your browser. The third way is quicker if the file you want is visible on the desktop: Just drag the file onto the \Snap{} window. In any of these cases, the scripting area will be replaced by something like this:\nopagebreak + +\bigpic{scripting-area-with-additional-costume} + +Just above this part of the window is a set of three tabs: Scripts, Costumes, and Sounds. You'll see that the Costumes tab is now selected. In this view, the sprite's \emph{wardrobe}, you can choose whether the sprite should wear its Turtle costume or its Alonzo costume. (Alonzo, the \Snap{} mascot, is named after Alonzo Church, a mathematician who invented the idea of procedures as data, the most important way in which \Snap{} is different from Scratch.) You can give a sprite as many costumes as you like, and then choose which it will wear either by clicking in its wardrobe or by using the \inlinepic{switch-to-costume-turtle} or \inlinepic{next-costume} block in a script. (Every costume has a number as well as a name. The \code{next costume} block selects the next costume by number; after the highest-numbered costume it switches to costume~1. The Turtle, costume~0, is never chosen by \code{next costume}.) The Turtle costume is the only one that changes color to match a change in the sprite's pen color. + +In addition to its costumes, a sprite can have \emph{sounds}; the equivalent for sounds of the sprite's wardrobe is called its \emph{jukebox}. Sound files can be imported in any format (WAV, OGG, MP3, etc.) supported by your browser. Two blocks accomplish the task of playing sounds. If you would like a script to continue running while the sound is playing, use the block \inlinepic{play-sound-help}. In contrast, you can use the \inlinepic{play-sound-help-until-done} block to wait for the sound's completion before continuing the rest of the script. + +\subsection{Inter-Sprite Communication with Broadcast} + +Earlier we saw an example of two sprites moving at the same time. In a more interesting program, though, the sprites on stage will interact to tell a story, play a game, etc. Often one sprite will have to tell another sprite to run a script. Here's a simple example:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=0.4]{../common/boy1-walking} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\reflectbox{\includegraphics[scale=0.3]{../common/dog2-c}} +\end{minipage} + +\vspace{3ex} +\begin{minipage}[t]{0.5\textwidth} +\centering +\vspace{0pt} % REALLY align to top +\includegraphics[scale=\defaultGraphicsScale]{inter-sprite-communication-1} +\end{minipage}% +\begin{minipage}[t]{0.5\textwidth} +\centering +\vspace{0pt} % REALLY align to top +\includegraphics[scale=\defaultGraphicsScale]{inter-sprite-communication-2} +\end{minipage} +\end{figure} + +In the \inlinepic{broadcast-bark-and-wait} block, the word ``bark'' is just an arbitrary name I made up. When you click on the downward arrowhead in that input slot, one of the choices (the only choice, the first time) is ``\code{new},'' which then prompts you to enter a name for the new broadcast. When this block is run, the chosen message is sent to \emph{every} sprite, which is why the block is called ``broadcast.'' In this program, though, only one sprite has a script to run when that broadcast is sent, namely the dog. Because the boy's script uses \code{broadcast and wait} rather than just \code{broadcast}, the boy doesn't go on to his next \code{say} block until the dog's script finishes. That's why the two sprites take turns talking, instead of both talking at once. + +Notice, by the way, that the \code{say} block's first input slot is rectangular rather than oval. This means the input can be any text string, not only a number. In the text input slots, a space character is shown as a brown dot, so that you can count the number of spaces between words, and in particular you can tell the difference between an empty slot and one containing spaces. The brown dots are \emph{not} shown on the stage when the block is run. + +The stage has its own scripting area. It can be selected by clicking on the Stage icon at the left of the sprite corral. Unlike a sprite, though, the stage can't move. Instead of costumes, it has \emph{backgrounds}: pictures that fill the entire stage area. The sprites appear in front of the current background. In a complicated project, it's often convenient to use a script in the stage's scripting area as the overall director of the action. + +\section{Nesting Sprites: Anchors and Parts} +\label{sec:nesting-sprites} + +Sometimes it's desirable to make a sort of ``super-sprite'' composed of pieces that can move together but can also be separately articulated. The classic example is a person's body made up of a torso, limbs, and a head. \Snap{} allows one sprite to be designated as the \emph{anchor} of the combined shape, with other sprites as its \emph{parts}. To set up sprite nesting, drag the sprite corral icon of a \emph{part} sprite onto the stage display (not the sprite corral icon!) of the desired \emph{anchor} sprite. + +Sprite nesting is shown in the sprite corral icons of both anchors and parts:\nopagebreak + +\bigpic{nested-sprites-in-corral} + +In this illustration, it is desired to animate Alonzo's arm. (The arm has been colored green in this picture to make the relationship of the two sprites clearer, but in a real project they'd be the same color, probably.) Sprite1, representing Alonzo's body, is the anchor; Sprite2 is the arm. The icon for the anchor shows small images of up to three attached parts at the bottom. The icon for each part shows a small image of the anchor in its top left corner, and a \emph{synchronous rotation flag} in the top right corner. In its initial setting, as shown above, it means that the when the anchor sprite rotates, the part sprite also rotates as well as revolving around the anchor. When clicked, it changes from a circular arrow to a straight arrow, and indicates that when the anchor sprite rotates, the part sprite revolves around it, but does not rotate, keeping its original orientation. (The part can also be rotated separately, using its \code{turn} blocks.) Any change in the position or size of the anchor is always extended to its parts. + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]{hand-waving-command}% +\hspace{2em}% +\includegraphics[scale=0.4]{../common/alonzo-waving} +\end{figure} + +\section{Reporter Blocks and Expressions} + +So far, we've used two kinds of blocks: hat blocks and command blocks. Another kind is the \emph{reporter} block, which has an oval shape: \inlinereporterpic{x-position}. It's called a ``reporter'' because when it's run, instead of carrying out an action, it reports a value that can be used as an input to another block. If you drag a reporter into the scripting area by itself and click on it, the value it reports will appear in a speech balloon next to the block:\nopagebreak + +\bigpic{x-position-returns-a-number} + +When you drag a reporter block over another block's input slot, a white ``halo'' appears around that input slot, analogous to the white line that appears when snapping command blocks together. Here's a simple script that uses a reporter block:\nopagebreak + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]{example-script-that-uses-a-reporter}% +\hspace{2em}% +\includegraphics{../common/turtle-says-its-position} +\end{figure} + +Here the \code{x position} reporter provides the first input to the \code{say} block. (The sprite's X position is its horizontal position, how far left (negative values) or right (positive values) it is compared to the center of the stage. Similarly, the Y position is measured vertically, in steps above (positive) or below (negative) the center.) + +You can do arithmetic using reporters in the Operators palette:\nopagebreak + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]{using-reporters-for-arithmetic}% +\hspace{2em}% +\includegraphics{../common/turtle-says-its-rounded-position} +\end{figure} + +The \code{round} block rounds $35.3905\ldots$ to $35$, and the \code{+}~block adds $100$ to that. (By the way, the \code{round} block is in the Operators palette, just like~\code{+}, but in this script it's a lighter color with black lettering because \Snap{} alternates light and dark versions of the palette colors when a block is nested inside another block from the same palette:\nopagebreak + +\bigpic{zebra-coloring} + +This aid to readability is called \emph{zebra coloring}.) A reporter block with its inputs, maybe including other reporter blocks, such as \inlinepic{round-x-position-plus-100}, is called an \emph{expression}. + +\section{Predicates and Conditional Evaluation} + +Most reporters report either a number, like \inlinereporterpic{plus}, or a text string, like \inlinereporterpic{join-hello-world}. A \emph{predicate} is a special kind of reporter that always reports \code{true} or \code{false}. Predicates have a hexagonal shape:\nopagebreak + +\bigpic{mouse-down} + +The special shape is a reminder that predicates don't generally make sense in an input slot of blocks that are expecting a number or text. You wouldn't say \inlinepic{move-mouse-down-steps}, although (as you can see from the picture) \Snap{} lets you do it if you really want. Instead, you normally use predicates in special hexagonal input slots like this one:\nopagebreak + +\bigpic{if} + +The C-shaped \code{if} block runs its input script if (and only if) the expression in its hexagonal input reports \code{true}.\nopagebreak + +\bigpic{predicates-and-conditional-evaluation-1} + +A really useful block in animations runs its input script \emph{repeatedly} until a predicate is satisfied:\nopagebreak + +\bigpic{predicates-and-conditional-evaluation-2} + +If, while working on a project, you want to omit temporarily some commands in a script, but you don't want to forget where they belong, you can say\nopagebreak + +\bigpic{predicates-and-conditional-evaluation-3} + +Sometimes you want to take the same action whether some condition is true or false, but with a different input value. For this purpose you can use the \emph{reporter} \code{if} block:\footnote{\onehalfspacing If you don't see it in the Control palette, click on the File button~\inlinepic{../common/btn-file} in the Tool Bar and choose ``Import tools.''}\nopagebreak + +\bigpic{predicates-and-conditional-evaluation-4} + +The technical term for a \code{true} or \code{false} value is a ``Boolean'' value; it has a capital~B because it's named after a person, George Boole, who developed the mathematical theory of Boolean values. Don't get confused; a hexagonal block is a \emph{predicate}, but the value it reports is a \emph{Boolean}. + +Another quibble about vocabulary: Many programming languages reserve the name ``procedure'' for Commands (that carry out an action) and use the name ``function'' for Reporters and Predicates. In this manual, a \emph{procedure} is any computational capability, including those that report values and those that don't. Commands, Reporters, and Predicates are all procedures. The words ``a Procedure type'' are shorthand for ``Command type, Reporter type, or Predicate type.'' + +\section{Variables} + +Try this script:\footnote{The \code{for} block is also in the tools library; choose ``\code{Import tools}'' from the file menu if you don't have it in the Control palette.}\nopagebreak + +\bigpic{squiral-script} + +The input to the \code{move} block is an orange oval. To get it there, drag the orange oval that's part of the \code{for} block:\nopagebreak + +\bigpic{variable-dragging} + +The orange oval is a \emph{variable}: a symbol that represents a value. (I took this screenshot before changing the second number input to the \code{for} block from the default $10$ to $200$, and before dragging in a \code{turn} block.) \code{For} runs its script input repeatedly, just like \code{repeat}, but before each repetition it sets the variable \code{i} to a number starting with its first numeric input, adding $1$ for each repetition, until it reaches the second numeric input. In this case, there will be $200$ repetitions, first with $\code{i}=1$, then with $\code{i}=2$, then $3$, and so on until $\code{i}=200$ for the final repetition. The result is that each \code{move} draws a longer and longer line segment, and that's why the picture you see is a kind of spiral. (If you try again with a turn of $90$ degrees instead of $92$, you'll see why this picture is called a ``squiral.'') + +The variable~\code{i} is created by the \code{for} block, and it can only be used in the script inside the block's C-slot. (By the way, if you don't like the name~\code{i}, you can change it by clicking on the orange oval without dragging it, which will pop up a dialog window in which you can enter a different name:\nopagebreak + +\bigpic{script-variable-name-dialog} + +``\code{I}'' isn't a very descriptive name; you might prefer ``\code{length}'' to indicate its purpose in the script. ``\code{I}'' is traditional because mathematicians tend to use letters between~\code{i} and~\code{n} to represent integer values, but in programming languages we don't have to restrict ourselves to single-letter variable names.) + +\subsection{Global Variables} + +You can create variables ``by hand'' that aren't limited to being used within a single block. At the top of the Variables palette, click the ``\code{Make a variable}'' button:\nopagebreak + +\bigpic{make-a-variable} + +This will bring up a dialog window in which you can give your variable a name:\nopagebreak + +\bigpic{variable-name-dialog} + +The dialog also gives you a choice to make the variable available to all sprites (which is almost always what you want) or to make it visible only in the current sprite. You'd do that if you're going to give several sprites individual variables \emph{with the same name}, so that you can share a script between sprites (by dragging it from the current sprite's scripting area to the picture of another sprite in the sprite corral), and the different sprites will do slightly different things when running that script because each has a different value for that variable name. + +If you give your variable the name ``\code{name}'' then the Variables palette will look like this:\nopagebreak + +\bigpic{variable-on-a-palette} + +There's now a ``\code{Delete a variable}'' button, and there's an orange oval with the variable name in it, just like the orange oval in the \code{for} block. You can drag the variable into any script in the scripting area. Next to the oval is a checkbox, initially checked. When it's checked, you'll also see a \emph{variable watcher} on the stage:\nopagebreak + +\bigpic{variable-watcher} + +When you give the variable a value, the orange box in its watcher will display the value. + +\emph{How} do you give it a value? You use the \code{set} block:\nopagebreak + +\bigpic{ask-and-set} + +Note that you \emph{don't} drag the variable's oval into the \code{set} block! You click on the down arrow in the first input slot, and you get a menu of all the available variable names. + +\subsection{Script Variables} + +In that example, our project is going to carry on an interaction with the user, and we want to remember her name throughout the project. That's a good example of a situation in which a \emph{global} variable (the kind you make with the ``\code{Make a variable}'' button) is appropriate. Another common example is a variable called ``\code{score}'' in a game project. But sometimes you only need a variable temporarily, during the running of a particular script. In that case you can use the \code{script variables} block to make the variable:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{wiggling-line-script}% +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics{../common/wiggling-line} +\end{minipage}% +\end{figure} + +As in the \code{for} block, you can click on an orange oval in the \code{script variables} block without dragging to change its name. You can also make more than one temporary variable by clicking on the right arrow at the end of the block to add another variable oval:\nopagebreak + +\bigpic{script-variables-a-b-c} + +\section{Etcetera} + +This manual doesn't explain every block in detail. There are many more motion blocks, sound blocks, costume and graphics effects blocks, and so on. You can learn what they all do by experimentation, and also by reading the ``help screens'' that you can get by right-clicking or control-clicking a block and selecting ``\code{help\ldots}'' from the menu that appears. If you forget what palette (color) a block is, but you remember at least part of its name, type control-F and enter the name in the text block that appears in the palette area. + +\chapter{Saving and Loading Projects and Media} + +After you've created a project, you'll want to save it, so that you can have access to it the next time you use \Snap{}. There are several ways to do that. You can save a project on your own computer, or you can save it at the \Snap{} web site. The advantage of saving on the net is that you have access to your project even if you are using a different computer, or a mobile device such as a tablet or smartphone. The advantage of saving on your computer is that you have access to the saved project while on an airplane or otherwise not on the net. This is why we have multiple ways to save. + +\section{Local Storage} + +There are two different ways to save a project (or a media file such as a costume) on your computer. The reason for this complexity is that JavaScript, in which \Snap{} is implemented, deliberately restricts the ability of programs running in a browser to affect the computer. This is a good thing, because it means that you can confidently run someone else's \Snap{} project without worrying that it will delete all your files or infect your computer with a virus. But it does make things a little complicated. + +\subsection{Localstore} + +{\Huge \TODO{}} + +\subsection{XML Export} + +The second way to save a project on your computer requires two steps, but it doesn't have the limitations of localstore (\TODO{rename}). Projects saved in this second way are normal files on your computer and can be shared with friends, can be opened in any browser, and have no size limitation. + +From the file menu~\inlinepic{../common/btn-file}, choose ``Export project\ldots'' The entire \Snap{} window will disappear, replaced by a screenful of what looks like gibberish. Don't panic! This is what's supposed to happen. You are looking at your project, in a notation called XML. The main reason it looks like gibberish is that it includes an encoding of the pictures and other media in the project. If you look through the XML, the actual scripts of the project are pretty readable, although they don't look like \Snap{} blocks. \Snap{} has opened a new browser tab for this XML text; the actual \Snap{} window is still there, hiding in its original tab. + +But the point of this XML text isn't for you to read. Once you're looking at that tab, use your browser's Save command (in its File menu, or usually with a shortcut of command-S (Mac) or control-S (everything else). You can choose a filename for it, and it'll be saved in your usual Downloads folder. You can then close that tab and return to the \Snap{} tab. + +\section{Cloud Storage} + +The other possibility is to save your project ``in the cloud,'' at the \Snap{} web site. In order to do this, you need an account with us. Click on the Cloud button (\,\inlinepic{../common/btn-cloud}\,) in the Tool Bar. Choose the ``Signup\ldots'' option. This will show you a window that looks like this:\nopagebreak + +\bigpic{sign-up} + +You must choose a user name that will identify you on the web site, such as \code{Jens} or \code{bh}. If you're a Scratch user, you can use your Scratch name for \Snap{} too. If you're a kid, don't pick a user name that includes your family name, but first names or initials are okay. Don't pick something you'd be embarrassed to have other users (or your parents) see! If the name you want is already taken, you'll have to choose another one. + +We ask for your month and year of birth; we use this information only to decide whether to ask for your own email address or your parent's email address. (If you're a kid, you shouldn't sign up for anything on the net, not even \Snap{}, without your parent's knowledge.) We do not store your birthdate information on our server; it is used on your own computer only during this initial signup. We do not ask for your \emph{exact} birthdate, even for this one-time purpose, because that's an important piece of personally identifiable information. + +When you click OK, an email will be sent to the email address you gave, with an initial password for your account. We keep your email address on file so that, if you forget your password, we can send you a password-reset link. We will also email you if your account is suspended for violation of the Terms of Service. We do not use your address for any other purpose. You will never receive marketing emails of any kind through this site, neither from us nor from third parties. If, nevertheless, you are worried about providing this information, do a web search for ``temporary email.'' + +Finally, you must read and agree to the Terms of Service. A quick summary: Don't interfere with anyone else's use of the web site, and don't put copyrighted media or personally identifiable information in projects that you share with other users. And we're not responsible if something goes wrong. (Not that we \emph{expect} anything to go wrong; since \Snap{} runs in JavaScript in your browser, it is strongly isolated from the rest of your computer. But the lawyers make us say this.) + +Once you've created your account, you can log into it using the ``Login\ldots'' option from the Cloud menu:\nopagebreak + +\bigpic{sign-in} + +Use the user name and password that you set up earlier. If you check the ``Stay signed in'' box, then you will be logged in automatically the next time you run \Snap{} from the same browser on the same computer. Check the box if you're using your own computer and you don't share it with siblings. Don't check the box if you're using a public computer at the library, at school, etc. + +Once logged in, you can choose the ``Cloud'' option in the ``Save as\ldots'' dialog shown on page~\TODO{Link to the picture}. You enter a project name, and optionally project notes, just as for Localstore \TODO{name} saving, but your project will be saved online and can be loaded from anywhere with net access. + +\section{Loading Saved Projects} + +Once you've saved a project, you want to be able to load it back into \Snap{}. There are two ways to do this: + +\begin{enumerate} +\item If you saved the project in Localstore or in your online \Snap{} account, choose the ``Open\ldots'' option from the File menu. Choose the ``Browser'' or ``Cloud'' button, then select your project from the list in the big text box and click OK. (A third button, ``Examples,'' lets you choose from example projects that we provide. You can see what each of these projects is about by clicking on it and reading its project notes.) + +\item If you saved the project as an XML file on your computer, choose ``Import\ldots'' from the File menu. This will give you an ordinary browser file-open window, in which you can navigate to the file as you would in other software. Alternatively, find the XML file on your desktop, and just drag it onto the \Snap{} window. +\end{enumerate} + +The second technique above also allows you to import media (costumes and sounds) into a project. Just choose ``Import\ldots'' and then select a picture or sound file instead of an XML file. + +\Snap{} can also import projects created in BYOB~3.0, or in Scratch~1.4 or (with some effort; see our web site) 2.0. Almost all such projects work correctly in \Snap{}, apart from a small number of incompatible blocks. BYOB~3.1 projects that don't use first class sprites work, too; all BYOB~3.1 projects will work in \Snap{}~4.1. + +\chapter{Building a Block} + +The first version of \Snap{} was called BYOB, for ``Build Your Own Blocks.'' This was the first and is still the most important capability we added to Scratch. (The name was changed because a few teachers have no sense of humor.~\frownie{} You pick your battles.) The new Scratch~2.0 also has a partial custom block capability. + +\section{Simple Blocks} + +In the Variables palette, at or near the bottom, is a button labeled ``Make a block.''\nopagebreak + +\begin{figure}[H] +\centering +\includegraphics[scale=0.5]{variables-palette} +\end{figure} + +Clicking this button will display a dialog window in which you choose the block's name, shape, and palette/color. You also decide whether the block will be available to all sprites, or only to the current sprite and its children. Note: You can also enter the ``Make a block'' dialog by right-click/control-click on the script area background and then choose ``\code{Make a block}'' from the menu that appears. + +\bigpic{make-a-block} + +In this dialog box, you can choose the block's palette, shape and name. With one exception, there is one color per palette, e.g., all Motion blocks are blue. But the Variables palette includes the orange variable-related blocks and the red list-related blocks. Both colors are available, along with an ``Other'' option that makes grey blocks in the Variables palette for blocks that don't fit any category. + +There are three block shapes, following a convention that should be familiar to Scratch users: The jigsaw-puzzle-piece shaped blocks are Commands, and don't report a value. The oval blocks are Reporters, and the hexagonal blocks are Predicates, which is the technical term for reporters that report Boolean (true or false) values. + +Suppose you want to make a block named ``square'' that draws a square. You would choose Motion, Command, and type ``\code{square}'' into the name field. When you click OK, you enter the Block Editor. This works just like making a script in the sprite's scripting area, except that the ``hat'' block at the top, instead of saying something like ``\code{when I am clicked},'' has a picture of the block you're building. This hat block is called the \emph{prototype} of your custom block.\footnote{This use of the word ``prototype'' is unrelated to the \emph{prototyping object oriented programming} discussed later.} You drag blocks under the hat to program your custom block, then click OK:\nopagebreak + +\bigpic{dragging-block-into-block-editor} +\bigpic{block-editor-square} + +Your block appears at the bottom of the Motion palette. Here's the block and the result of using it:\nopagebreak + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]% + {square-block-on-palette} +\includegraphics[scale=\defaultGraphicsScale]{../common/square} +\end{figure} + +\subsection{Custom Blocks with Inputs} + +But suppose you want to be able to draw squares of different sizes. Control-click or right-click on the block, choose ``\code{edit},'' and the Block Editor will open. Notice the plus signs before and after the word \code{square} in the prototype block. If you hover the mouse over one, it lights up:\nopagebreak + +\bigpic{block-prototype-with-plus-highlighted} + +Click on the plus on the right. You will then see the ``input name'' dialog:\nopagebreak + +\bigpic{create-input-name} + +Type in the name ``\code{size}'' and click OK. There are other options in this dialog; you can choose ``\code{title text}'' if you want to add words to the block name, so it can have text after an input slot, like the ``\code{move~(\,)~steps}'' block. Or you can select a more extensive dialog with a lot of options about your input name. But we'll leave that for later. When you click OK, the new input appears in the block prototype:\nopagebreak + +\bigpic{square-block-script-with-size-input} + +You can now drag the orange variable down into the script, then click okay:\nopagebreak + +\bigpic{dragging-block-input} + +Your block now appears in the Motion palette with an input box:\nopagebreak + +\bigpic{square-block} + +You can draw any size square by entering the length of its side in the box and running the block as usual, by clicking it or by putting it in a script. + +\section{Recursion} + +Since the new custom block appears in its palette as soon as you \emph{start} editing it, you can write recursive blocks (blocks that call themselves) by dragging the block into its own definition:\nopagebreak + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]{tree-block-script}\\ +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]% + {tree-block-inside-script} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{../common/tree} +\end{minipage}% +\end{figure} + +If recursion is new to you, here are a few brief hints: It's crucial that the recursion have a \emph{base case}, that is, some small(est) case that the block can handle without using recursion. In this example, it's the case $\code{depth}=0$, for which the block does nothing at all, because of the enclosing \code{if}. Without a base case, the recursion would run forever, calling itself over and over. + +Don't try to trace the exact sequence of steps that the computer follows in a recursive program. Instead, imagine that inside the computer there are many small people, and if Theresa is drawing a tree of size~100, depth~6, she hires Tom to make a tree of size~70, depth~5, and later hires Theo to make another tree of size~70, depth~5. Tom in turn hires Tammy and Tallulah, and so on. Each little person has his or her own local variables \code{size} and \code{depth}, each with different values. + +You can also write recursive reporters, like this block to compute the factorial function:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{factorial-block-script} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]% + {factorial-5-with-result} +\end{minipage}% +\end{figure} + +Note the use of the \code{report} block. When a reporter block uses this block, the reporter finishes its work and reports the value given; any further blocks in the script are not evaluated. Thus, the \code{if else} block in the script above could have been just an \code{if}, with the second \code{report} block below it instead of inside it, and the result would be the same, because when the first \code{report} is seen in the base case, that finishes the block invocation, and the second \code{report} is ignored. There is also a \code{stop this block} block that has a similar purpose, ending the block invocation early, for command blocks. (By contrast, the \code{stop this script} block stops not only the current block invocation, but also the entire top-level script that called it.) + +Here's a slightly more compact way to write the factorial function:\nopagebreak + +\bigpic{concise-factorial-block-script} + +(If you don't see the reporter-\code{if} block in the Control palette, click the file button~\inlinepic{../common/btn-file} in the tool bar and choose ``\code{Import tools}.'') + +For more on recursion, see \textit{Thinking Recursively} by Eric Roberts. (The original edition is ISBN~978--0471816522; a more recent \textit{Thinking Recursively in Java} is ISBN~978--0471701460.) + +\section{Block Libraries} +\chapter{First Class Lists} +\section{The list Block} +\section{Lists of Lists} +\section{Functional and Imperative List Programming} +\section{Higher Order List Operations and Rings} +\chapter{Typed Inputs} +\section{Scratch's Type Notation} +\section{The \Snap{} Input Type Dialog} +\subsection{Procedure Types} +\subsection{Pulldown inputs} +\subsection{Input variants} +\subsection{Prototype Hints} +\subsection{Title Text and Symbols} +\chapter{Procedures as Data} +\section{Call and Run} +\subsection{Call/Run with inputs} +\subsection{Variables in Ring Slots} +\section{Writing Higher Order Procedures} +\subsection{Recursive Calls to Multiple-Input Blocks} +\section{Formal Parameters} +\section{Procedures as Data} +\section{Special Forms} +\subsection{Special Forms in Scratch} +\chapter{Object Oriented Programming} +\section{Local State with Script Variables} +\section{Messages and Dispatch Procedures} +\section{Inheritance via Delegation} +\section{An Implementation of Prototyping OOP} +\chapter{The Outside World} +\section{The World Wide Web} +\section{Hardware Devices} +\section{Date and Time} +\chapter{Continuations} +\section{Continuation Passing Style} +\section{Call/Run w/Continuation} +\subsection{Nonlocal exit} +\chapter{User Interface Elements} +\section{Tool Bar Features} +\subsection{The \Snap{} Logo Menu} +\subsection{The File Menu} +\subsection{The Cloud Menu} +\subsection{The Settings Menu} +\subsection{Stage Resizing Buttons} +\subsection{Project Control Buttons} +\section{The Palette Area} +\subsection{Context Menus for Palette Blocks} +\subsection{Context Menu for the Palette Background} +\section{The Scripting Area} +\subsection{Sprite Appearance and Behavior Controls} +\subsection{Scripting Area Tabs} +\subsection{Scripts and Blocks Within Scripts} +\subsection{Scripting Area Background Context Menu} +\subsection{Controls in the Costumes Tab} +\subsection{The Paint Editor} +\subsection{Controls in the Sounds Tab} +\section{Controls on the Stage} +\section{The Sprite Corral and Sprite Creation Buttons} + +\end{document} diff --git a/help/manual/en/snapping-blocks.png b/help/manual-LaTeX/en/snapping-blocks.png similarity index 100% rename from help/manual/en/snapping-blocks.png rename to help/manual-LaTeX/en/snapping-blocks.png diff --git a/help/manual-LaTeX/en/sprites-and-parallelism-1.png b/help/manual-LaTeX/en/sprites-and-parallelism-1.png new file mode 100644 index 00000000..5035b404 Binary files /dev/null and b/help/manual-LaTeX/en/sprites-and-parallelism-1.png differ diff --git a/help/manual-LaTeX/en/sprites-and-parallelism-2.png b/help/manual-LaTeX/en/sprites-and-parallelism-2.png new file mode 100644 index 00000000..191003ee Binary files /dev/null and b/help/manual-LaTeX/en/sprites-and-parallelism-2.png differ diff --git a/help/manual-LaTeX/en/sprites-and-parallelism-3.png b/help/manual-LaTeX/en/sprites-and-parallelism-3.png new file mode 100644 index 00000000..f7707e5b Binary files /dev/null and b/help/manual-LaTeX/en/sprites-and-parallelism-3.png differ diff --git a/help/manual-LaTeX/en/sprites-and-parallelism-4.png b/help/manual-LaTeX/en/sprites-and-parallelism-4.png new file mode 100644 index 00000000..548ef50f Binary files /dev/null and b/help/manual-LaTeX/en/sprites-and-parallelism-4.png differ diff --git a/help/manual-LaTeX/en/square-block-on-palette.pdf b/help/manual-LaTeX/en/square-block-on-palette.pdf new file mode 100644 index 00000000..935ef662 Binary files /dev/null and b/help/manual-LaTeX/en/square-block-on-palette.pdf differ diff --git a/help/manual-LaTeX/en/square-block-on-palette.svg b/help/manual-LaTeX/en/square-block-on-palette.svg new file mode 100644 index 00000000..5c63a5ef --- /dev/null +++ b/help/manual-LaTeX/en/square-block-on-palette.svg @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/help/manual-LaTeX/en/square-block-script-with-size-input.png b/help/manual-LaTeX/en/square-block-script-with-size-input.png new file mode 100644 index 00000000..3c5f5c54 Binary files /dev/null and b/help/manual-LaTeX/en/square-block-script-with-size-input.png differ diff --git a/help/manual-LaTeX/en/square-block.png b/help/manual-LaTeX/en/square-block.png new file mode 100644 index 00000000..b95142cd Binary files /dev/null and b/help/manual-LaTeX/en/square-block.png differ diff --git a/help/manual-LaTeX/en/squiral-script.png b/help/manual-LaTeX/en/squiral-script.png new file mode 100644 index 00000000..d3b69d6b Binary files /dev/null and b/help/manual-LaTeX/en/squiral-script.png differ diff --git a/help/manual-LaTeX/en/switch-to-costume-turtle.png b/help/manual-LaTeX/en/switch-to-costume-turtle.png new file mode 100644 index 00000000..ac8002a8 Binary files /dev/null and b/help/manual-LaTeX/en/switch-to-costume-turtle.png differ diff --git a/help/manual-LaTeX/en/tree-block-inside-script.png b/help/manual-LaTeX/en/tree-block-inside-script.png new file mode 100644 index 00000000..37ddefe7 Binary files /dev/null and b/help/manual-LaTeX/en/tree-block-inside-script.png differ diff --git a/help/manual-LaTeX/en/tree-block-script.png b/help/manual-LaTeX/en/tree-block-script.png new file mode 100644 index 00000000..b04d0e3d Binary files /dev/null and b/help/manual-LaTeX/en/tree-block-script.png differ diff --git a/help/manual/en/typical-script-inner.png b/help/manual-LaTeX/en/typical-script-inner.png similarity index 100% rename from help/manual/en/typical-script-inner.png rename to help/manual-LaTeX/en/typical-script-inner.png diff --git a/help/manual/en/typical-script.png b/help/manual-LaTeX/en/typical-script.png similarity index 100% rename from help/manual/en/typical-script.png rename to help/manual-LaTeX/en/typical-script.png diff --git a/help/manual-LaTeX/en/using-reporters-for-arithmetic.png b/help/manual-LaTeX/en/using-reporters-for-arithmetic.png new file mode 100644 index 00000000..83d68cd3 Binary files /dev/null and b/help/manual-LaTeX/en/using-reporters-for-arithmetic.png differ diff --git a/help/manual-LaTeX/en/variable-dragging.png b/help/manual-LaTeX/en/variable-dragging.png new file mode 100644 index 00000000..c2708ed1 Binary files /dev/null and b/help/manual-LaTeX/en/variable-dragging.png differ diff --git a/help/manual-LaTeX/en/variable-name-dialog.png b/help/manual-LaTeX/en/variable-name-dialog.png new file mode 100644 index 00000000..4c3e4886 Binary files /dev/null and b/help/manual-LaTeX/en/variable-name-dialog.png differ diff --git a/help/manual-LaTeX/en/variable-on-a-palette.png b/help/manual-LaTeX/en/variable-on-a-palette.png new file mode 100644 index 00000000..2889afa0 Binary files /dev/null and b/help/manual-LaTeX/en/variable-on-a-palette.png differ diff --git a/help/manual-LaTeX/en/variable-watcher.png b/help/manual-LaTeX/en/variable-watcher.png new file mode 100644 index 00000000..f1bd4019 Binary files /dev/null and b/help/manual-LaTeX/en/variable-watcher.png differ diff --git a/help/manual-LaTeX/en/variables-palette.png b/help/manual-LaTeX/en/variables-palette.png new file mode 100644 index 00000000..aefb97bc Binary files /dev/null and b/help/manual-LaTeX/en/variables-palette.png differ diff --git a/help/manual-LaTeX/en/wiggling-line-script.png b/help/manual-LaTeX/en/wiggling-line-script.png new file mode 100644 index 00000000..bba0a51e Binary files /dev/null and b/help/manual-LaTeX/en/wiggling-line-script.png differ diff --git a/help/manual/en/window-regions.png b/help/manual-LaTeX/en/window-regions.png similarity index 100% rename from help/manual/en/window-regions.png rename to help/manual-LaTeX/en/window-regions.png diff --git a/help/manual-LaTeX/en/x-position-returns-a-number.png b/help/manual-LaTeX/en/x-position-returns-a-number.png new file mode 100644 index 00000000..5996d89d Binary files /dev/null and b/help/manual-LaTeX/en/x-position-returns-a-number.png differ diff --git a/help/manual-LaTeX/en/x-position.png b/help/manual-LaTeX/en/x-position.png new file mode 100644 index 00000000..4939b805 Binary files /dev/null and b/help/manual-LaTeX/en/x-position.png differ diff --git a/help/manual-LaTeX/en/zebra-coloring.png b/help/manual-LaTeX/en/zebra-coloring.png new file mode 100644 index 00000000..fb8c5c8e Binary files /dev/null and b/help/manual-LaTeX/en/zebra-coloring.png differ diff --git a/help/manual-LaTeX/pl/blok-drzewo-w-skrypcie.png b/help/manual-LaTeX/pl/blok-drzewo-w-skrypcie.png new file mode 100644 index 00000000..bf40f5b3 Binary files /dev/null and b/help/manual-LaTeX/pl/blok-drzewo-w-skrypcie.png differ diff --git a/help/manual-LaTeX/pl/blok-kwadrat-na-palecie.pdf b/help/manual-LaTeX/pl/blok-kwadrat-na-palecie.pdf new file mode 100644 index 00000000..910fef08 Binary files /dev/null and b/help/manual-LaTeX/pl/blok-kwadrat-na-palecie.pdf differ diff --git a/help/manual-LaTeX/pl/blok-kwadrat-na-palecie.svg b/help/manual-LaTeX/pl/blok-kwadrat-na-palecie.svg new file mode 100644 index 00000000..6b8e1f70 --- /dev/null +++ b/help/manual-LaTeX/pl/blok-kwadrat-na-palecie.svg @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/help/manual-LaTeX/pl/blok-kwadrat.png b/help/manual-LaTeX/pl/blok-kwadrat.png new file mode 100644 index 00000000..aadfef3b Binary files /dev/null and b/help/manual-LaTeX/pl/blok-kwadrat.png differ diff --git a/help/manual-LaTeX/pl/dialog-nazwy-zmiennej-skryptu.png b/help/manual-LaTeX/pl/dialog-nazwy-zmiennej-skryptu.png new file mode 100644 index 00000000..c239239b Binary files /dev/null and b/help/manual-LaTeX/pl/dialog-nazwy-zmiennej-skryptu.png differ diff --git a/help/manual-LaTeX/pl/dialog-nazwy-zmiennej.png b/help/manual-LaTeX/pl/dialog-nazwy-zmiennej.png new file mode 100644 index 00000000..216e4666 Binary files /dev/null and b/help/manual-LaTeX/pl/dialog-nazwy-zmiennej.png differ diff --git a/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-1.png b/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-1.png new file mode 100644 index 00000000..4f2a4d0d Binary files /dev/null and b/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-1.png differ diff --git a/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-2.png b/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-2.png new file mode 100644 index 00000000..4acf611e Binary files /dev/null and b/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-2.png differ diff --git a/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-3.png b/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-3.png new file mode 100644 index 00000000..3ee07139 Binary files /dev/null and b/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-3.png differ diff --git a/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-4.png b/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-4.png new file mode 100644 index 00000000..17199505 Binary files /dev/null and b/help/manual-LaTeX/pl/duszki-i-wspolbieznosc-4.png differ diff --git a/help/manual-LaTeX/pl/edytor-blokow-kwadrat.png b/help/manual-LaTeX/pl/edytor-blokow-kwadrat.png new file mode 100644 index 00000000..2ffe5754 Binary files /dev/null and b/help/manual-LaTeX/pl/edytor-blokow-kwadrat.png differ diff --git a/help/manual-LaTeX/pl/jezeli-to.png b/help/manual-LaTeX/pl/jezeli-to.png new file mode 100644 index 00000000..d4815483 Binary files /dev/null and b/help/manual-LaTeX/pl/jezeli-to.png differ diff --git a/help/manual-LaTeX/pl/kolorowanie-w-zebre.png b/help/manual-LaTeX/pl/kolorowanie-w-zebre.png new file mode 100644 index 00000000..45454a6f Binary files /dev/null and b/help/manual-LaTeX/pl/kolorowanie-w-zebre.png differ diff --git a/help/manual-LaTeX/pl/komenda-machania-reka.png b/help/manual-LaTeX/pl/komenda-machania-reka.png new file mode 100644 index 00000000..341f7471 Binary files /dev/null and b/help/manual-LaTeX/pl/komenda-machania-reka.png differ diff --git a/help/manual/pl/laczenie-blokow.png b/help/manual-LaTeX/pl/laczenie-blokow.png similarity index 100% rename from help/manual/pl/laczenie-blokow.png rename to help/manual-LaTeX/pl/laczenie-blokow.png diff --git a/help/manual-LaTeX/pl/nadaj-szczekaj-do-wszystkich-i-czekaj.png b/help/manual-LaTeX/pl/nadaj-szczekaj-do-wszystkich-i-czekaj.png new file mode 100644 index 00000000..ae776cb3 Binary files /dev/null and b/help/manual-LaTeX/pl/nadaj-szczekaj-do-wszystkich-i-czekaj.png differ diff --git a/help/manual-LaTeX/pl/nadawanie-i-odbieranie-komunikatow-1.png b/help/manual-LaTeX/pl/nadawanie-i-odbieranie-komunikatow-1.png new file mode 100644 index 00000000..bf762623 Binary files /dev/null and b/help/manual-LaTeX/pl/nadawanie-i-odbieranie-komunikatow-1.png differ diff --git a/help/manual-LaTeX/pl/nadawanie-i-odbieranie-komunikatow-2.png b/help/manual-LaTeX/pl/nadawanie-i-odbieranie-komunikatow-2.png new file mode 100644 index 00000000..26272eb1 Binary files /dev/null and b/help/manual-LaTeX/pl/nadawanie-i-odbieranie-komunikatow-2.png differ diff --git a/help/manual-LaTeX/pl/nastepny-kostium.png b/help/manual-LaTeX/pl/nastepny-kostium.png new file mode 100644 index 00000000..2a9379fa Binary files /dev/null and b/help/manual-LaTeX/pl/nastepny-kostium.png differ diff --git a/help/manual-LaTeX/pl/nowy-blok.png b/help/manual-LaTeX/pl/nowy-blok.png new file mode 100644 index 00000000..f80534cb Binary files /dev/null and b/help/manual-LaTeX/pl/nowy-blok.png differ diff --git a/help/manual-LaTeX/pl/obszar-skryptow-z-dodatkowym-kostiumem.png b/help/manual-LaTeX/pl/obszar-skryptow-z-dodatkowym-kostiumem.png new file mode 100644 index 00000000..833ff031 Binary files /dev/null and b/help/manual-LaTeX/pl/obszar-skryptow-z-dodatkowym-kostiumem.png differ diff --git a/help/manual/pl/obszary-okna.pdf b/help/manual-LaTeX/pl/obszary-okna.pdf similarity index 100% rename from help/manual/pl/obszary-okna.pdf rename to help/manual-LaTeX/pl/obszary-okna.pdf diff --git a/help/manual/pl/obszary-okna.pdf_tex b/help/manual-LaTeX/pl/obszary-okna.pdf_tex similarity index 100% rename from help/manual/pl/obszary-okna.pdf_tex rename to help/manual-LaTeX/pl/obszary-okna.pdf_tex diff --git a/help/manual/pl/obszary-okna.svg b/help/manual-LaTeX/pl/obszary-okna.svg similarity index 100% rename from help/manual/pl/obszary-okna.svg rename to help/manual-LaTeX/pl/obszary-okna.svg diff --git a/help/manual-LaTeX/pl/paleta-dane.pdf b/help/manual-LaTeX/pl/paleta-dane.pdf new file mode 100644 index 00000000..da936de2 Binary files /dev/null and b/help/manual-LaTeX/pl/paleta-dane.pdf differ diff --git a/help/manual-LaTeX/pl/paleta-dane.pdf_tex b/help/manual-LaTeX/pl/paleta-dane.pdf_tex new file mode 100644 index 00000000..f5324b76 --- /dev/null +++ b/help/manual-LaTeX/pl/paleta-dane.pdf_tex @@ -0,0 +1,55 @@ +%% Creator: Inkscape inkscape 0.48.4, www.inkscape.org +%% PDF/EPS/PS + LaTeX output extension by Johan Engelen, 2010 +%% Accompanies image file 'paleta-dane.pdf' (pdf, eps, ps) +%% +%% To include the image in your LaTeX document, write +%% \input{.pdf_tex} +%% instead of +%% \includegraphics{.pdf} +%% To scale the image, write +%% \def\svgwidth{} +%% \input{.pdf_tex} +%% instead of +%% \includegraphics[width=]{.pdf} +%% +%% Images with a different path to the parent latex file can +%% be accessed with the `import' package (which may need to be +%% installed) using +%% \usepackage{import} +%% in the preamble, and then including the image with +%% \import{}{.pdf_tex} +%% Alternatively, one can specify +%% \graphicspath{{/}} +%% +%% For more information, please see info/svg-inkscape on CTAN: +%% http://tug.ctan.org/tex-archive/info/svg-inkscape +%% +\begingroup% + \makeatletter% + \providecommand\color[2][]{% + \errmessage{(Inkscape) Color is used for the text in Inkscape, but the package 'color.sty' is not loaded}% + \renewcommand\color[2][]{}% + }% + \providecommand\transparent[1]{% + \errmessage{(Inkscape) Transparency is used (non-zero) for the text in Inkscape, but the package 'transparent.sty' is not loaded}% + \renewcommand\transparent[1]{}% + }% + \providecommand\rotatebox[2]{#2}% + \ifx\svgwidth\undefined% + \setlength{\unitlength}{533.54912109bp}% + \ifx\svgscale\undefined% + \relax% + \else% + \setlength{\unitlength}{\unitlength * \real{\svgscale}}% + \fi% + \else% + \setlength{\unitlength}{\svgwidth}% + \fi% + \global\let\svgwidth\undefined% + \global\let\svgscale\undefined% + \makeatother% + \begin{picture}(1,0.45208073)% + \put(0,0){\includegraphics[width=\unitlength]{paleta-dane.pdf}}% + \put(0.33676736,0.03854298){\color[rgb]{0.38039216,0.6,0.18431373}\makebox(0,0)[lb]{\smash{\textit{Tylko jeśli zaimportowano narzędzia}}}}% + \end{picture}% +\endgroup% diff --git a/help/manual-LaTeX/pl/paleta-dane.svg b/help/manual-LaTeX/pl/paleta-dane.svg new file mode 100644 index 00000000..ec386063 --- /dev/null +++ b/help/manual-LaTeX/pl/paleta-dane.svg @@ -0,0 +1,594 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + \textit{Tylko jeśli zaimportowano narzędzia} + + + + + + + + + + diff --git a/help/manual-LaTeX/pl/plus.png b/help/manual-LaTeX/pl/plus.png new file mode 100644 index 00000000..a1e6cc73 Binary files /dev/null and b/help/manual-LaTeX/pl/plus.png differ diff --git a/help/manual-LaTeX/pl/podglad-zmiennej.png b/help/manual-LaTeX/pl/podglad-zmiennej.png new file mode 100644 index 00000000..9f5ec503 Binary files /dev/null and b/help/manual-LaTeX/pl/podglad-zmiennej.png differ diff --git a/help/manual-LaTeX/pl/polacz-witaj-swiecie.png b/help/manual-LaTeX/pl/polacz-witaj-swiecie.png new file mode 100644 index 00000000..09d34a59 Binary files /dev/null and b/help/manual-LaTeX/pl/polacz-witaj-swiecie.png differ diff --git a/help/manual-LaTeX/pl/pozycja-x-zwraca-liczbe.png b/help/manual-LaTeX/pl/pozycja-x-zwraca-liczbe.png new file mode 100644 index 00000000..f582aa98 Binary files /dev/null and b/help/manual-LaTeX/pl/pozycja-x-zwraca-liczbe.png differ diff --git a/help/manual-LaTeX/pl/pozycja-x.png b/help/manual-LaTeX/pl/pozycja-x.png new file mode 100644 index 00000000..bf69345d Binary files /dev/null and b/help/manual-LaTeX/pl/pozycja-x.png differ diff --git a/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-1.png b/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-1.png new file mode 100644 index 00000000..eb799211 Binary files /dev/null and b/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-1.png differ diff --git a/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-2.png b/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-2.png new file mode 100644 index 00000000..d3413f68 Binary files /dev/null and b/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-2.png differ diff --git a/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-3.png b/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-3.png new file mode 100644 index 00000000..793a1063 Binary files /dev/null and b/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-3.png differ diff --git a/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-4.png b/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-4.png new file mode 100644 index 00000000..08bb9ff7 Binary files /dev/null and b/help/manual-LaTeX/pl/predykaty-i-obliczenia-warunkowe-4.png differ diff --git a/help/manual-LaTeX/pl/prototyp-bloku-z-podswietlonym-plusem.pdf b/help/manual-LaTeX/pl/prototyp-bloku-z-podswietlonym-plusem.pdf new file mode 100644 index 00000000..785f01a0 Binary files /dev/null and b/help/manual-LaTeX/pl/prototyp-bloku-z-podswietlonym-plusem.pdf differ diff --git a/help/manual-LaTeX/pl/prototyp-bloku-z-podswietlonym-plusem.svg b/help/manual-LaTeX/pl/prototyp-bloku-z-podswietlonym-plusem.svg new file mode 100644 index 00000000..f04cc7a5 --- /dev/null +++ b/help/manual-LaTeX/pl/prototyp-bloku-z-podswietlonym-plusem.svg @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/help/manual-LaTeX/pl/przeciaganie-bloku-do-edytora-blokow.pdf b/help/manual-LaTeX/pl/przeciaganie-bloku-do-edytora-blokow.pdf new file mode 100644 index 00000000..cc22559b Binary files /dev/null and b/help/manual-LaTeX/pl/przeciaganie-bloku-do-edytora-blokow.pdf differ diff --git a/help/manual-LaTeX/pl/przeciaganie-bloku-do-edytora-blokow.svg b/help/manual-LaTeX/pl/przeciaganie-bloku-do-edytora-blokow.svg new file mode 100644 index 00000000..50ddd007 --- /dev/null +++ b/help/manual-LaTeX/pl/przeciaganie-bloku-do-edytora-blokow.svg @@ -0,0 +1,551 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/help/manual-LaTeX/pl/przeciaganie-parametru-bloku.pdf b/help/manual-LaTeX/pl/przeciaganie-parametru-bloku.pdf new file mode 100644 index 00000000..7ce44c30 Binary files /dev/null and b/help/manual-LaTeX/pl/przeciaganie-parametru-bloku.pdf differ diff --git a/help/manual-LaTeX/pl/przeciaganie-parametru-bloku.svg b/help/manual-LaTeX/pl/przeciaganie-parametru-bloku.svg new file mode 100644 index 00000000..8b0a74c8 --- /dev/null +++ b/help/manual-LaTeX/pl/przeciaganie-parametru-bloku.svg @@ -0,0 +1,339 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/help/manual-LaTeX/pl/przeciaganie-zmiennej.pdf b/help/manual-LaTeX/pl/przeciaganie-zmiennej.pdf new file mode 100644 index 00000000..2dd3f9ef Binary files /dev/null and b/help/manual-LaTeX/pl/przeciaganie-zmiennej.pdf differ diff --git a/help/manual-LaTeX/pl/przeciaganie-zmiennej.svg b/help/manual-LaTeX/pl/przeciaganie-zmiennej.svg new file mode 100644 index 00000000..de1ce759 --- /dev/null +++ b/help/manual-LaTeX/pl/przeciaganie-zmiennej.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/help/manual/pl/przesun-o-10-krokow.png b/help/manual-LaTeX/pl/przesun-o-10-krokow.png similarity index 100% rename from help/manual/pl/przesun-o-10-krokow.png rename to help/manual-LaTeX/pl/przesun-o-10-krokow.png diff --git a/help/manual-LaTeX/pl/przesun-o-przycisk-myszy-nacisniety-krokow.png b/help/manual-LaTeX/pl/przesun-o-przycisk-myszy-nacisniety-krokow.png new file mode 100644 index 00000000..4e2696f0 Binary files /dev/null and b/help/manual-LaTeX/pl/przesun-o-przycisk-myszy-nacisniety-krokow.png differ diff --git a/help/manual-LaTeX/pl/przycisk-myszy-nacisniety.png b/help/manual-LaTeX/pl/przycisk-myszy-nacisniety.png new file mode 100644 index 00000000..7005a34d Binary files /dev/null and b/help/manual-LaTeX/pl/przycisk-myszy-nacisniety.png differ diff --git a/help/manual-LaTeX/pl/przykladowy-skrypt-wykorzystujacy-funkcje.png b/help/manual-LaTeX/pl/przykladowy-skrypt-wykorzystujacy-funkcje.png new file mode 100644 index 00000000..0bb6dda0 Binary files /dev/null and b/help/manual-LaTeX/pl/przykladowy-skrypt-wykorzystujacy-funkcje.png differ diff --git a/help/manual-LaTeX/pl/rejestracja.png b/help/manual-LaTeX/pl/rejestracja.png new file mode 100644 index 00000000..49a64c86 Binary files /dev/null and b/help/manual-LaTeX/pl/rejestracja.png differ diff --git a/help/manual-LaTeX/pl/silnia-5-z-rezultatem.png b/help/manual-LaTeX/pl/silnia-5-z-rezultatem.png new file mode 100644 index 00000000..bc4dd076 Binary files /dev/null and b/help/manual-LaTeX/pl/silnia-5-z-rezultatem.png differ diff --git a/help/manual-LaTeX/pl/skrypt-bloku-drzewo.png b/help/manual-LaTeX/pl/skrypt-bloku-drzewo.png new file mode 100644 index 00000000..d9720df0 Binary files /dev/null and b/help/manual-LaTeX/pl/skrypt-bloku-drzewo.png differ diff --git a/help/manual-LaTeX/pl/skrypt-bloku-kwadrat-z-parametrem-rozmiar.png b/help/manual-LaTeX/pl/skrypt-bloku-kwadrat-z-parametrem-rozmiar.png new file mode 100644 index 00000000..e90e3793 Binary files /dev/null and b/help/manual-LaTeX/pl/skrypt-bloku-kwadrat-z-parametrem-rozmiar.png differ diff --git a/help/manual-LaTeX/pl/skrypt-bloku-silnia.png b/help/manual-LaTeX/pl/skrypt-bloku-silnia.png new file mode 100644 index 00000000..3167b72b Binary files /dev/null and b/help/manual-LaTeX/pl/skrypt-bloku-silnia.png differ diff --git a/help/manual-LaTeX/pl/skrypt-bloku-zwiezla-silnia.png b/help/manual-LaTeX/pl/skrypt-bloku-zwiezla-silnia.png new file mode 100644 index 00000000..543368a9 Binary files /dev/null and b/help/manual-LaTeX/pl/skrypt-bloku-zwiezla-silnia.png differ diff --git a/help/manual-LaTeX/pl/skrypt-kwadratowej-spirali.png b/help/manual-LaTeX/pl/skrypt-kwadratowej-spirali.png new file mode 100644 index 00000000..3249f9fd Binary files /dev/null and b/help/manual-LaTeX/pl/skrypt-kwadratowej-spirali.png differ diff --git a/help/manual-LaTeX/pl/skrypt-skrecajacej-linii.png b/help/manual-LaTeX/pl/skrypt-skrecajacej-linii.png new file mode 100644 index 00000000..708a8b68 Binary files /dev/null and b/help/manual-LaTeX/pl/skrypt-skrecajacej-linii.png differ diff --git a/help/manual-LaTeX/pl/snap-podrecznik.tex b/help/manual-LaTeX/pl/snap-podrecznik.tex new file mode 100644 index 00000000..5db44f5c --- /dev/null +++ b/help/manual-LaTeX/pl/snap-podrecznik.tex @@ -0,0 +1,532 @@ +% !TeX spellcheck = pl + +\documentclass[a4paper]{report} + +\input{../common/defs.tex} + +\usepackage[polish]{babel} +\usepackage{polski} +\frenchspacing +\usepackage{indentfirst} + +\begin{document} + +\title{Snap! --- Podręcznik użytkownika} +\author{Brian Harvey\texorpdfstring{ \and}{,} Jens M\"onig} +\date{} + +\manualtitlepage[Tłumaczenie na język polski:\\Bartosz Leper]{Snap!\\Podręcznik użytkownika} + +\tableofcontents + +\chapter*{} +\section*{Podziękowania} + +Mieliśmy ogromne szczęście do mentorów. Jens zdobył dużo doświadczenia pracując wśród pionierów Smalltalka: Alana Kaya, Dana Ingallsa i~reszty ekipy, która wynalazła komputery osobiste i~programowanie obiektowe w~najlepszych dniach firmy Xerox PARC. Pracował z~Johnem Maloneyem z~zespołu Scratcha w~MIT\footnote{Massachusetts Institute of Technology, amerykańska uczelnia techniczna --- przyp. tłum.}, autorem platformy graficznej Morphic, wciąż stanowiącej fundament \Snap{a}. Znakomity projekt języka Scratch, autorstwa Lifelong Kindergarten Group z~MIT Media Lab, odgrywa w~\Snap{ie} kluczową rolę. + +\textbf{\emph{Nasza poprzednia wersja, BYOB, była bezpośrednią modyfikacją kodu źródłowego Scratcha. \Snap{} został napisany od zera, lecz struktura jego kodu oraz interfejs użytkownika pozostają mocno zakorzenione w~Scratchu. Z~kolei zespół Scratcha, który mógłby widzieć w~nas rywali, przyjął nas ciepło i~okazał nam całkowite wsparcie.}} + +Brian zdobywał szlify w~MIT oraz Stanford Artificial Intelligence Labs\footnote{Laboratorium sztucznej inteligencji na Uniwersytecie Stanforda --- przyp. tłum.}, gdzie uczył się pod okiem Johna McCarthy'ego, twórcy Lispa, oraz Geralda~J. Suss\-mana i~Guya Steele'a, twórców języka Scheme. Zdobywał również wiedzę od wielu innych wybitnych informatyków, w~tym autorów najlepszej książki z zakresu informatyki --- \emph{Struktury i~interpretacji programów komputerowych}: Hala Abelsona, Geralda~J. Suss\-mana i~Julie Suss\-man. + +\textbf{\emph{Za starych dobrych czasów mawialiśmy w~MIT Logo Lab: ,,Język Logo to Lisp braniu BASIC-a''. Dziś, ze swoimi pierwszoklasowymi procedurami, zasięgami leksykalnymi~i pierwszoklasowymi kontynuacjami, \Snap{} jest jak Scheme w~przebraniu Scratcha.}} + +Szczęśliwym zrządzeniem losu, poprzez forum Scratch Advanced Topics, poznaliśmy wspaniałą grupę błyskotliwych uczniów gimnazjów~(!\@) i liceów. Kilku z nich wniosło swój wkład w~kod \Snap{a}: Kartik Chandra, Nathan Dinsmore, Connor Hudson i~Ian Reynolds. Ponadto wielu zgłosiło pomysły i~raporty błędów podczas testowania wersji alfa. Wśród studentów Uniwersytetu Kalifornijskiego w~Berkeley, którzy przyczynili się do rozwoju kodu, znajdują się Michael Ball, Achal Dave, Kyle Hotchkiss, Ivan Motyashov i~Yuan Yuan. Wymienianie wszystkich tłumaczy zajęłoby zbyt wiele miejsca, ale można ich odnaleźć w~okienku ,,O \Snap{}\ldots'' dostępnym w~programie. + +Niniejsze dzieło powstało częściowo w~ramach grantu nr~1143566 udzielonego przez National Science Foundation, a częściowo przy wsparciu firmy MioSoft. + +\clearpage + +\begin{center} +\bf \Huge \Snap{} \\ +Podręcznik użytkownika \\ +\huge Wersja 4.0 \vspace{40pt} +\end{center} + +\Snap{} to rozszerzona reimplementacja języka Scratch (\url{http://scratch.mit.edu}), która pozwala na tworzenie własnych bloków (ang.\ \textit{Build Your Own Blocks}; stąd dawna nazwa \Snap{a} --- BYOB). Opisywany tu język obsługuje pierwszoklasowe listy, procedury i~kontynuacje. Te dodatkowe możliwości sprawiają, że nadaje się on do przeprowadzenia poważnego wstępu do informatyki dla uczniów liceów i szkół wyższych. + +Aby uruchomić środowisko \Snap{}, wystarczy otworzyć przeglądarkę internetową i~wpisać adres \url{http://snap.berkeley.edu/run}, aby zacząć pracę z~minimalnym zestawem bloków. Można też użyć adresu \url{http://snap.berkeley.edu/init}, aby załadować niewielki zestaw dodatkowych bloków. Wiąże się to z~nieco wolniejszym ładowaniem, ale jest zalecane dla wygody użytkowników (w~dalszej części podręcznika będziemy zakładali korzystanie z~tej właśnie metody). + +\clearpage + +\chapter{Bloki, skrypty i~duszki} + +W~tym rozdziale poznamy kilka cech języka \Snap{} odziedziczonych po Scratchu; doświadczeni użytkownicy Scratcha mogą przejść od razu do sekcji~\ref{sec:zagnieżdżanie-duszków}. + +\Snap{} jest językiem programowania --- notacją, przy pomocy której możemy powiedzieć komputerowi, co ma zrobić. Jednak w~odróżnieniu od większości innych, \Snap{} jest językiem wizualnym; programując w~nim, zamiast posługiwać się klawiaturą, używamy metody ,,przeciągnij i~upuść'', dobrze znanej użytkownikom komputerów. + +Uruchom teraz środowisko \Snap{}. Powinieneś zobaczyć ekran podzielony na kilka obszarów:\footnote{\onehalfspacing Pierwsze uruchomienie \Snap{a} prawdopodobnie spowoduje wyświetlenie angielskiej wersji programu; aby przełączyć się na język polski, należy kliknąć menu ,,Ustawienia''~\inlinepic{../common/btn-settings} na pasku narzędzi, a~następnie użyć polecenia ,,Language\ldots'' (,,Język\ldots'') --- przyp. tłum.}\nopagebreak + +\begin{center} +\def\svgwidth{\textwidth} +\input{obszary-okna.pdf_tex} +\end{center} + +(Proporcje tych stref mogą się różnić, w~zależności od rozmiaru i~kształtu okna przeglądarki). + +Program w~języku \Snap{} składa się z~jednego lub więcej \emph{skryptów}, te zaś z~kolei --- z~\emph{bloków}. Oto przykładowy skrypt:\nopagebreak + +\label{fig:typowy-skrypt} +\bigpic{typowy-skrypt} + +Na powyższy skrypt składa się pięć bloków w~trzech różnych kolorach, odpowiadających trzem z~ośmiu \emph{palet} z~blokami. Obszar palet, znajdujący się po lewej stronie okna, pokazuje jedną paletę na raz. Do zmiany widocznej palety służy osiem przycisków znajdujących się tuż nad tym obszarem. Bloki ciemnożółte, widoczne w~naszym skrypcie, pochodzą z~palety ,,Kontrola''; zielone z~palety ,,Pisak'', a~niebieskie --- z~palety ,,Ruch''. Aby złożyć taki skrypt, należy poprzeciągać odpowiednie bloki z~palet do \emph{obszaru skryptów}, umiejscowionego na środku okna. Kiedy układamy jeden blok pod drugim w~taki sposób, aby wcięcie dolnego bloku znalazło się w~pobliżu wypustki tego powyżej, bloki łączą się ze sobą (ang. \textit{snap together}; stąd nazwa języka \Snap{}):\nopagebreak + +\bigpic{laczenie-blokow} + +Pozioma biała linia sygnalizuje, że jeśli puścimy zielony blok, połączy się on z~wypustką ciemnożółtego. + +\subsection{Bloki-czapki i~bloki komend} + +Na górze skryptu znajduje się \emph{blok-czapka}, który określa, kiedy skrypt ma zostać wykonany. Nazwy bloków-czapek zazwyczaj zaczynają się słowem ,,\code{kiedy}''; nasz przykładowy skrypt powinien zostać uruchomiony w~momencie kliknięcia zielonej flagi, znajdującej się w pobliżu prawej strony paska narzędzi \Snap{a}. (Pasek ten jest częścią okna programu \Snap{}; nie chodzi tutaj o pasek menu przeglądarki lub systemu operacyjnego). Skrypt nie musi posiadać czapki, jednak w~takim przypadku zostanie wykonany tylko wtedy, gdy użytkownik sam go kliknie. Skrypt nie może mieć więcej niż jednej czapki; jej charakterystyczny kształt służy łatwiejszemu zapamiętaniu tej szczególnej własności. + +Pozostałe bloki w naszym skrypcie to \emph{bloki komend}. Każdy z~nich oznacza jakąś akcję, którą \Snap{} potrafi wykonać. Na przykład blok \inlinepic{przesun-o-10-krokow} nakazuje duszkowi\footnote{W grafice komputerowej słowem ,,duszek'' (ang. \textit{sprite}) nazywa się ruchomy obiekt na ekranie --- przyp. tłum.}, czyli strzałce na \emph{scenie} po prawej stronie okna, aby przesunął się o~dziesięć kroków do przodu w~kierunku, w~którym jest zwrócony. Każdy krok to niewielka odległość na ekranie. Wkrótce przekonamy się, że na scenie może być więcej duszków, a~każdy z nich może mieć własne skrypty. Ponadto duszki nie muszą wyglądać jak strzałki; ich kostiumy mogą być dowolnymi obrazkami. Kształt bloku \code{przesuń} ma za zadanie przypominać klocek, skrypt zaś jest jak wieża z klocków. Słowa ,,blok'' będziemy używać dla oznaczenia zarówno graficznego symbolu na ekranie, jak i~procedury (akcji) jaką ten blok wykonuje. + +Liczbę $10$ w powyższym bloku \code{przesuń} nazywamy jego \emph{parametrem}. Kliknąwszy na białym, owalnym polu, możemy wpisać w~jej miejsce dowolną inną. W przykładowym skrypcie ze strony \pageref{fig:typowy-skrypt} parametrem jest liczba $100$. Jak się później okaże, pola parametrów mogą mieć kształty inne od owalnych; oznacza to wtedy, że akceptują one wartości inne niż liczby. Zobaczymy również, że zamiast wpisywać konkretne wartości w~pola, możemy nakazać komputerowi je obliczać. Ponadto blok może mieć więcej niż jeden parametr. Na przykład blok \code{leć}, znajdujący się mniej więcej w~połowie palety ,,Ruch'', przyjmuje trzy parametry. + +Większość bloków komend ma kształt klocków, lecz niektóre, jak \code{powtórz} z~tego samego przykładu, wyglądają jak \emph{klamry}. Większość bloków klamrowych można znaleźć na palecie ,,Kontrola'. Wnętrze klamry jest szczególnym rodzajem pola parametru, który przyjmuje \emph{skrypt} jako parametr. W~przykładowym skrypcie blok \code{powtórz} ma dwa parametry: liczbę $4$ oraz skrypt\nopagebreak + +\bigpic{typowy-skrypt-wnetrze} + +\section{Duszki i~współbieżność} + +Tuż pod sceną znajduje się przycisk ,,nowy duszek''~\inlinepic{../common/btn-new-sprite}. Kliknięcie go spowoduje dodanie nowego duszka do sceny. Pojawi się on w~losowym miejscu na scenie, skierowany w~losową stronę i~zabarwiony na losowy kolor. + +Każdy duszek ma swoje własne skrypty. Aby wyświetlić w~obszarze skryptów te należące do konkretnego duszka, należy kliknąć na jego obrazku w~\emph{zagrodzie duszków}, znajdującej się w~prawym dolnym rogu okna. Spróbuj umieścić następujące skrypty w~obszarze skryptów --- po jednym dla każdego duszka:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{duszki-i-wspolbieznosc-1} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{duszki-i-wspolbieznosc-2} +\end{minipage} +\end{figure} + +Kiedy klikniemy zieloną flagę~\inlinepic{../common/btn-start}, powinniśmy zobaczyć jak jeden duszek się obraca, podczas gdy drugi porusza się w~tę i~z~powrotem. Ten eksperyment pokazuje, jak różne skrypty mogą być wykonywane jednocześnie (\emph{współbieżnie}). Obracanie się dookoła i~ruch po linii prostej zachodzą jednocześnie. Współbieżność zachodzi również w~przypadku wielu skryptów należących do tego samego duszka. Spróbujmy tego przykładu:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{duszki-i-wspolbieznosc-3} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{duszki-i-wspolbieznosc-4} +\end{minipage} +\end{figure} + +Po naciśnięciu spacji duszek powinien zacząć bez końca chodzić w kółko, ponieważ bloki \code{przesuń} i \code{obróć} są wykonywane współbieżnie. (Aby przerwać program, kliknij czerwony czerwony znak ,,stop''~\inlinepic{../common/btn-stop} na prawym brzegu paska narzędzi). + +\subsection{Kostiumy i~dźwięki} + +Aby zmienić wygląd duszka, należy zaimportować dla niego nowy \emph{kostium}. Są na to trzy sposoby. Najpierw trzeba wybrać duszka z~zagrody. Następnie, w~pierwszej metodzie, klikamy na ikonie pliku~\inlinepic{../common/btn-file} na pasku narzędzi, a~następnie wybieramy polecenie ,,\code{Kostiumy\ldots}''. Ukaże się lista kostiumów z~publicznej biblioteki multimediów, spośród których możemy dokonać wyboru. Drugą metodą jest wybór pliku ze swojego własnego komputera. Należy w~tym celu kliknąć ikonę pliku, a~następnie polecenie ,,\code{Importuj\ldots}''. Można wtedy wybrać plik obrazu w~dowolnym formacie (PNG, JPEG itd.) obsługiwanym przez przeglądarkę. Trzeci sposób jest szybszy jeśli plik, którego chcemy użyć, jest widoczny na pulpicie: po prostu przeciągnij go do okna \Snap{a}. W~każdym z~tych przypadków obszar skryptów zacznie wyglądać mniej więcej tak:\nopagebreak + +\bigpic{obszar-skryptow-z-dodatkowym-kostiumem} + +Tuż nad tą częścią okna znajdują się trzy zakładki: ,,Skrypty'', ,,Kostiumy'' i~,,Dźwię\-ki''. W~tym momencie aktywna jest karta ,,Kostiumy''. Widzimy na niej \emph{garderobę} duszka i~możemy z~jej poziomu wybrać dla niego kostium --- domyślny kostium żółwia\footnote{Z powodów historycznych, słowem ,,żółw'' nazywamy ruchomy obiekt, który porusza się wykonując program i~rysuje, zostawiając za sobą ślad} lub wybrany wcześniej kostium Alonza. (Alonzo, maskotka \Snap{a}, został nazwany na cześć Alonza Churcha, matematyka, który jako pierwszy wpadł na pomysł, aby procedury traktować na równi z~danymi, co jest najistotniejszą różnicą między \Snap{em} a~Scratchem). Możemy przypisać duszkowi tyle kostiumów ile chcemy, a~potem wybierać, który z~nich założy, albo poprzez kliknięcie w~obrębie garderoby, albo używając w~skrypcie bloku \inlinepic{zmien-kostium-na-zolwia} lub \inlinepic{nastepny-kostium}. (Każdy kostium ma zarówno numer jak i~nazwę. Blok \code{następny kostium} wybiera następny w~kolejności kostium; po ostatnim wybiera z~powrotem kostium numer~1. Żółw, czyli kostium numer~0, jest przez blok \code{następny kostium} ignorowany). Kostium ,,Żółw'' jest jedynym, który zmienia kolor zgodnie z~kolorem pisaka. + +Oprócz kostiumów, duszki mogą posiadać \emph{dźwięki}; dźwiękowy odpowiednik garderoby duszka nazywamy jego \emph{szafą grającą}. Można importować pliki dźwiękowe w~dowolnym formacie obsługiwanym przez przeglądarkę. Do odtwarzania dźwięków służą dwa rodzaje bloków. Jeśli skrypt ma się dalej wykonywać podczas odtwarzania, używamy bloku \inlinepic{zagraj-dzwiek-ratunku}. Za to aby poczekać, aż dźwięk się zakończy, zanim skrypt będzie kontynuowany, należy wykorzystać blok \inlinepic{zagraj-dzwiek-ratunku-i-czekaj}. + +\subsection{Nadawanie i~odbieranie komunikatów} + +Widzieliśmy wcześniej przykład dwóch duszków poruszających się jednocześnie. Jednak w~bardziej interesującym programie duszki na scenie będą wchodzić w~interakcje, abyśmy mogli opowiedzieć przy ich pomocy jakąś historię, zagrać w~grę itd. Czasami jeden duszek będzie musiał nakazać innemu wykonanie jakiegoś skryptu. Oto prosty przykład:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=0.4]{../common/boy1-walking} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\reflectbox{\includegraphics[scale=0.3]{../common/dog2-c}} +\end{minipage} +\vskip 3ex +\begin{minipage}[t]{0.5\textwidth} +\centering +\vspace{0pt} % REALLY align to top +\includegraphics[scale=\defaultGraphicsScale]{nadawanie-i-odbieranie-komunikatow-1} +\end{minipage}% +\begin{minipage}[t]{0.5\textwidth} +\centering +\vspace{0pt} % REALLY align to top +\includegraphics[scale=\defaultGraphicsScale]{nadawanie-i-odbieranie-komunikatow-2} +\end{minipage} +\end{figure} + +Słowo ,,szczekaj'' występujące w~bloku \inlinepic{nadaj-szczekaj-do-wszystkich-i-czekaj} to pierwszy lepszy wyraz, który przyszedł mi do głowy. Jedną z~opcji, które ukazują się po kliknięciu strzałki w~dół obok tego pola parametru (i~jedyną początkowo dostępną), jest ,,\code{nowy}''. Po jej wybraniu \Snap{} pyta o~nazwę komunikatu. Kiedy wspomniany blok zostanie wykonany, wybrany komunikat zostaje wysłany do \emph{każdego} duszka --- stąd też określenie ,,nadaj do wszystkich''. Jednak w~naszym przykładzie tylko jeden duszek ma skrypt, który jest wywoływany w~momencie nadania tego komunikatu --- jest nim pies. Ponieważ skrypt chłopca wykorzystuje blok \code{nadaj do wszystkich i~czekaj} zamiast \code{nadaj do wszystkich}, chłopiec nie przechodzi do następującego po nim bloku \code{powiedz}, dopóki skrypt psa się nie skończy. Z~tej przyczyny dwa duszki mówią na zmianę, a~nie jednocześnie. + +Warto przy okazji zwrócić uwagę na to, że pierwsze pole parametru na bloku \code{powiedz} nie jest owalne, lecz prostokątne. Oznacza to, że parametr ten może być dowolnym łańcuchem znaków (tekstem), nie tylko liczbą. W~polach parametrów typu tekstowego spacje ukazują się jako brązowe kropki, abyśmy mogli policzyć liczbę odstępów między wyrazami. Co ważniejsze, możemy dzięki temu odróżnić pusty łańcuch od złożonego z~samych spacji. Brązowe kropki \emph{nie będą} widoczne na scenie, kiedy blok zostanie wykonany. + +Scena ma swój własny obszar skryptów. Możemy wyświetlić jej szczegóły klikając ikonę ,,Scena'' po lewej stronie zagrody duszków. W~przeciwieństwie do duszków scena się nie porusza. Zamiast kostiumów ma \emph{tła} --- obrazki wypełniające cały obszar sceny. Duszki rysowane są na aktualnym tle. W~skomplikowanych projektach często wygodnie jest użyć skryptu sceny do koordynacji działań poszczególnych części programu. + +\section{Zagnieżdżanie duszków: kotwice i~części} +\label{sec:zagnieżdżanie-duszków} + +Czasem dobrze jest stworzyć swego rodzaju ,,nadduszka'', złożonego z~kawałków, które poruszają się razem, ale mogą być osobno względem siebie ustawiane. Klasycznym przykładem może być ciało człowieka złożone z~tułowia, kończyn i~głowy. \Snap{} pozwala nam uczynić jednego z~duszków \emph{kotwicą} złożonego obiektu, a~resztę --- jego \emph{częściami}. Aby zagnieździć w~ten sposób duszki, należy przeciągnąć z~zagrody ikonę duszka, który ma zostać \emph{częścią} złożonego obiektu na znajdującego się na scenie (nie w~zagrodzie!) duszka, który zostanie \emph{kotwicą}. + +Zagnieżdżone duszki --- zarówno kotwice jak i części --- mają w zagrodzie specjalne oznaczenia:\nopagebreak + +\bigpic{zagniezdzone-duszki-w-zagrodzie} + +W~tym przypadku chcielibyśmy animować rękę Alonza. (Ręka została pokolorowana na zielono, aby uwypuklić zależność między dwoma duszkami, choć w~prawdziwym projekcie miałyby one raczej ten sam kolor). ,,Duszek1'', reprezentujący ciało Alonza, jest kotwicą; ,,Duszek2'' to ręka. Ikona duszka-kotwicy zawiera w~dolnej części do trzech miniatur doczepionych do niego duszków-części. Z~kolei na ikonie każdej z~części widać pomniejszony obrazek duszka-kotwicy w~lewym górnym rogu, w~prawym górnym zaś --- \emph{przełącznik synchronizacji obrotów}. Początkowo, jak widać na rysunku powyżej, jest on tak ustawiony, aby obrót kotwicy powodował zarówno orbitowanie części wokół niej, jak i~obrót części dookoła swojej własnej osi. Po kliknięciu przełącznik zmienia kształt z~okrągłej strzałki na prostą, co oznacza, że od tej pory obrót duszka-kotwicy będzie powodował jedynie zmianę pozycji przymocowanych do niego części, ale nie będą się one obracać wokół własnej osi. (Części mogą również obracać się niezależnie, przy pomocy bloków \code{obróć}). Każda zmiana pozycji lub rozmiaru kotwicy jest propagowana na wszystkie części. + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]{komenda-machania-reka}% +\hspace{2em}% +\includegraphics[scale=0.4]{../common/alonzo-waving} +\end{figure} + +\section{Bloki funkcji i~wyrażenia} + +Jak dotąd używaliśmy dwóch rodzajów bloków: ,,czapek'' i~komend. Kolejnym rodzajem jest blok \emph{funkcji}, który ma owalny kształt: \inlinereporterpic{pozycja-x}. Nazywamy go ,,blokiem funkcji'', ponieważ --- podobnie jak funkcja w~matematyce --- kiedy zostaje wykonany, zamiast przeprowadzać jakąś czynność, zwraca wartość, która może zostać użyta jako parametr w~innym bloku. Jeśli przeciągniemy sam blok funkcji do obszaru skryptów i~klikniemy go, obok pokaże się dymek z~wartością zwróconą przez tę funkcję:\nopagebreak + +\bigpic{pozycja-x-zwraca-liczbe} + +Kiedy przeciągamy blok funkcji nad polem parametru należącym do innego bloku, wokół tego pola pojawia się biała otoczka, analogicznie do sytuacji, w~której łączymy bloki komend i~pojawia się biała linia. Oto przykładowy skrypt wykorzystujący funkcję:\nopagebreak + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]{przykladowy-skrypt-wykorzystujacy-funkcje}% +\hspace{2em}% +\includegraphics{../common/turtle-says-its-position} +\end{figure} + +Funkcja \code{pozycja X} nadaje tu wartość pierwszemu parametrowi bloku \code{powiedz}. Pozycja X~duszka to inaczej jego współrzędna pozioma. Określa ona, jak daleko w~lewo (jeśli jest liczbą ujemną) lub w~prawo (jeśli dodatnią) znajduje się duszek w~stosunku do środka sceny. Analogicznie, pozycja Y~to współrzędna pionowa, mierzona ilością kroków w~górę (wartości dodatnie) lub w~dół od środka (wartości ujemne). + +Przy pomocy funkcji z palety ,,Wyrażenia'' możemy wykonywać obliczenia:\nopagebreak + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]{uzycie-funkcji-do-obliczen}% +\hspace{2em}% +\includegraphics{../common/turtle-says-its-rounded-position} +\end{figure} + +Blok \code{zaokrąglij} zaokrągla $35.3905\ldots$ do $35$, a~blok~\code{+} dodaje do tej liczby $100$. Nawiasem mówiąc, choć blok \code{zaokrąglij} znajduje się na palecie ,,Wyrażenia'', podobnie jak~\code{+}, to w~tym skrypcie ma on jaśniejszy kolor i~czarne litery. To dlatego, że \Snap{} używa na przemian ciemnych i~jasnych odcieni kolorów, kiedy zagnieżdżamy w~sobie bloki z~tej samej palety:\nopagebreak + +\bigpic{kolorowanie-w-zebre} + +Takie \emph{kolorowanie w~zebrę} poprawia czytelność programu. Blok funkcji wraz z~parametrami, a~być może również innymi blokami funkcji, na przykład \inlinepic{zaokraglij-pozycja-x-plus-100}, nazywamy \emph{wyrażeniem}. + +\section{Predykaty i~obliczenia warunkowe} + +Większość funkcji zwraca albo liczbę, jak \inlinereporterpic{plus}, lub łańcuch tekstowy, jak \inlinereporterpic{polacz-witaj-swiecie}. \emph{Predykat} to specjalny rodzaj funkcji, która zawsze zwraca jedną z dwojga wartości: \code{prawdę} lub \code{fałsz}. Predykaty mają kształt sześciokątów:\nopagebreak + +\bigpic{przycisk-myszy-nacisniety} + +\begin{sloppypar} +Specjalny kształt jest oznaką, że predykaty nie mają z~reguły sensu w~tych polach parametrów, które oczekują liczby lub tekstu. Raczej nie napisalibyśmy \inlinepic{przesun-o-przycisk-myszy-nacisniety-krokow}, choć gdybyśmy się uparli, \Snap{} by nam na to pozwolił, co widać na załączonym obrazku. W~typowych sytuacjach predykaty umieszczamy w~specjalnych sześciokątnych polach parametrów takich jak to:\nopagebreak +\end{sloppypar} + +\bigpic{jezeli-to} + +Klamra \code{jeżeli --- to} wykonuje obejmowany przez nią fragment skryptu wtedy i~tylko wtedy, gdy wyrażenie w~jej sześciokątnym polu parametru jest prawdziwe, czyli zwraca wartość \code{prawda}.\nopagebreak + +\bigpic{predykaty-i-obliczenia-warunkowe-1} + +Poniższy blok jest bardzo użyteczny w~animacjach. Wykonuje on skrypt będący jego parametrem \emph{wielokrotnie}, dopóki predykat nie zostanie spełniony:\nopagebreak + +\bigpic{predykaty-i-obliczenia-warunkowe-2} + +Jeśli pracując nad projektem, będziemy chcieli tymczasowo pominąć niektóre komendy w~skrypcie, lecz nie będziemy chcieli zapomnieć, gdzie było ich miejsce, możemy użyć następującej sztuczki:\nopagebreak + +\bigpic{predykaty-i-obliczenia-warunkowe-3} + +Czasami potrzeba wykonać tę samą czynność bez względu na to, czy jakiś warunek zachodzi czy nie, za to z~różnymi parametrami dla obu tych przypadków. Można do tego użyć bloku \emph{funkcji} \code{if}:\footnote{\onehalfspacing Jeśli nie widzisz go w~palecie ,,Kontrola'', kliknij przycisk ,,Plik''~\inlinepic{../common/btn-file} na pasku narzędzi i~wybierz polecenie ,,Importuj narzędzia''.}\footnote{Niestety, podobnie jak pozostałe dodatkowe narzędzia i~biblioteki bloków, funkcja \code{if --- then --- else} posiada wyłącznie angielską nazwę, bez względu na nasze ustawienia języka. Oznacza ona ,,jeżeli --- to --- w~przeciwnym razie'' --- przyp. tłum.}\nopagebreak + +\bigpic{predykaty-i-obliczenia-warunkowe-4} + +Wartości \code{prawda} i~\code{fałsz} określa się technicznymi terminami ,,wartość logiczna'' lub ,,wartość boolowska'' (ang. \textit{Boolean}). Ta ostatnia nazwa pochodzi on nazwiska George'a Boole'a, który stworzył opisującą je teorię matematyczną\footnote{Jest to \emph{algebra Boole'a} --- przyp. tłum.}. Uważaj na nazewnictwo --- sześciokątny blok to \emph{predykat}, ale wartość przezeń zwracana to \emph{wartość logiczna}. + +Jest jeszcze jedna warta wytłumaczenia niejasność terminologiczna: W wielu językach programowania nazwa ,,procedura'' jest zarezerwowana dla \emph{komend}, które wykonują jakąś czynność, zaś nazwa ,,funkcja'' --- dla części programów zwracających wartość (\emph{funkcji} i \emph{predykatów}). W tym podręczniku \emph{procedury} to dowolne składniki programu, zarówno te zwracające, jak i nie zwracające wartości. Podobnie jak komendy, również funkcje i predykaty będziemy nazywać procedurami. Słowa ,,typ proceduralny'' będą skrótem myślowym dla słów ,,typ komendowy, funkcyjny lub predykatowy''. + +\section{Zmienne} + +Wypróbujmy następujący skrypt:\footnote{Blok \code{for}\footnotemark{} również znajduje się w~bibliotece narzędzi; użyj polecenia ,,\code{Importuj narzędzia}'' z~menu ,,Plik'', jeśli nie widzisz ich na palecie ,,Kontrola''.}\footnotetext{Słowa \code{for i = 1 to 10} oznaczają ,,dla $i=1$ do $10$'' --- przyp. tłum.}\nopagebreak + +\bigpic{skrypt-kwadratowej-spirali} + +Parametr bloku \code{przesuń} ma postać pomarańczowego owalu. Aby go tam umieścić, należy przeciągnąć taki sam owal będący częścią bloku \code{for}:\nopagebreak + +\bigpic{przeciaganie-zmiennej} + +Ten owal to \emph{zmienna} --- symboliczna nazwa reprezentująca jakąś wartość. Powyższy rysunek przedstawia sytuację sprzed zmiany drugiego parametru liczbowego bloku \code{for} z~domyślnego $10$ na $200$ oraz przeciągnięcia do jego środka bloku \code{obróć}. Blok \code{for} wykonuje swój parametr skryptowy wielokrotnie, podobnie jak \code{powtarzaj}, lecz przed każdym razem zapisuje liczbę do zmiennej~\code{i}, zaczynając od swojego pierwszego parametru liczbowego, dodając~$1$ przy każdym powtórzeniu, aż dojdzie do liczby z~drugiego parametru liczbowego. W~tym przypadku będziemy mieć $200$ powtórzeń, najpierw dla $\code{i}=1$, potem dla $\code{i}=2$, następnie $3$ i~tak dalej, aż do $\code{i}=200$ w~ostatnim powtórzeniu. W~rezultacie każdy blok \code{przesuń} rysuje coraz to dłuższy segment łamanej, co nadaje jej wygląd zbliżony do spirali. (Możesz spróbować ze skrętem $90$~stopni zamiast $92$; zobaczysz wtedy, dlaczego nazywamy tego rodzaju obraz ,,kwadratową spiralą''). + +Zmienna~\code{i}~została utworzona przez blok \code{for} i~może zostać użyta wyłącznie wewnątrz jego klamry. Nawiasem mówiąc, jeśli nie spodoba nam się nazwa~\code{i}, możemy ją zmienić klikając pomarańczowy owal bez przeciągania go. Pokaże się wtedy okno dialogowe, do którego można wpisać inną nazwę:\nopagebreak + +\bigpic{dialog-nazwy-zmiennej-skryptu} + +Nazwa~,,\code{i}'' nie mówi nam zbyt wiele; można by tu użyć słowa ,,długość'', aby podkreślić znaczenie zmiennej. Nazwa~,,\code{i}'' jest popularna, gdyż w~matematyce istnieje tradycja używania liter od~\code{i} do~\code{n} dla liczb całkowitych. W~językach programowania nie musimy się jednak ograniczać do jednoliterowych nazw zmiennych. + +\subsection{Zmienne globalne} + +Możemy ,,ręcznie'' tworzyć zmienne, których widoczność nie jest ograniczona do jednego bloku. Aby to zrobić, należy kliknąć przycisk ,,\code{Stwórz zmienną}'' w~górnej części palety ,,Dane'':\nopagebreak + +\bigpic{stworz-zmienna} + +Otworzy się okno dialogowe, które pozwala nadać nazwę nowej zmiennej:\nopagebreak + +\bigpic{dialog-nazwy-zmiennej} + +Okno to pozwala również wybrać, czy zmienna ma być dostępna dla wszystkich duszków (co jest pożądane w~większości przypadków), czy ma być ona widoczna tylko dla aktualnego duszka. Postępujemy tak, jeśli mamy zamiar dać wielu duszkom własne zmienne o~\emph{tej samej nazwie}. Możemy potem kopiować skrypty między duszkami, przeciągając je z~obszaru skryptów aktualnego duszka na obrazek innego duszka w~zagrodzie duszków. Dzięki temu różne duszki będą wykonywać nieco inne rzeczy w~momencie wykonywania tego skryptu, ponieważ każdy z~nich będzie miał w~tej zmiennej zapisaną inną wartość. + +Jeśli nadamy zmiennej nazwę ,,imię'', paleta ,,Dane'' będzie wyglądać następująco:\nopagebreak + +\bigpic{zmienna-na-palecie} + +Widzimy teraz przycisk ,,\code{usuń zmienną}'', a~także pomarańczowy owal z~nazwą zmiennej, taki sam jak owal na bloku \code{for}. Zmienną możemy przeciągnąć do dowolnego skryptu w~obszarze skryptów. Obok owalu widzimy pole wyboru; jest ono początkowo zaznaczone, dzięki czemu na scenie widoczny jest \emph{podgląd zmiennej}:\nopagebreak + +\bigpic{podglad-zmiennej} + +Kiedy nadamy zmiennej jakąś wartość, pojawia ona się w~polu podglądu. + +Ale \emph{jak} nadać zmiennej wartość? Należy użyć do tego bloku \code{ustaw}:\nopagebreak + +\bigpic{zapytaj-i-ustaw} + +Zauważ, że \emph{nie przeciągamy} owalu zmiennej do bloku \code{ustaw}! Aby wybrać z~listy dostępnych zmiennych, należy kliknąć strzałkę w~dół przy pierwszym polu parametru tegoż bloku. + +\subsection{Zmienne skryptu} + +W~poprzednim przykładzie przeprowadzaliśmy interakcję z~użytkownikiem i~chcieliśmy zapamiętać jego imię na potrzeby całego projektu. To dobry przykład sytuacji, w~której właściwe jest użycie zmiennej \emph{globalnej} (z~rodzaju tych, które tworzymy przyciskiem ,,\code{Stwórz zmienną}''). Innym typowym przykładem jest zmienna ,,\code{wynik}'' w~projekcie gry. Czasem jednak potrzebujemy zmiennej tylko tymczasowo podczas wykonywania któregoś ze skryptów. W~takim przypadku możemy użyć bloku \code{zmienne skryptu} aby ją utworzyć:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{skrypt-skrecajacej-linii}% +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics{../common/wiggling-line} +\end{minipage}% +\end{figure} + +Podobnie jak w~przypadku bloku \code{for}, aby zmienić nazwę zmiennej, wystarczy kliknąć pomarańczowy owal w~bloku \code{zmienne skryptu} bez przeciągania. Możemy również stworzyć więcej tymczasowych zmiennych klikając strzałkę w~prawo na końcu bloku. Spowoduje to dodanie kolejnego owalu zmiennej:\nopagebreak + +\bigpic{zmienne-skryptu-a-b-c} + +\section{I tak dalej} + +Niniejszy podręcznik nie opisuje szczegółowo każdego bloku. Jest wiele bloków związanych z~ruchem, dźwiękiem, kostiumami, efektami graficznymi i~tak dalej. Ich przeznaczenie można poznać eksperymentalnie, ale także poprzez lekturę ,,ekranów pomocy''\footnote{Niestety, są one dostępne wyłącznie w~języku angielskim --- przyp. tłum.}, które można obejrzeć klikając interesujący nas blok albo prawym przyciskiem myszy, albo lewym z~wciśniętym jednocześnie klawiszem Ctrl, a~następnie wybierając z~menu polecenie ,,\code{pomoc\ldots}''. Jeśli zapomnisz, w~której palecie znajduje się potrzebny Ci blok, ale pamiętasz choćby część nazwy, wciśnij Ctrl-F i~wpisz ją w~polu tekstowym, które pojawi się w~obszarze palet. + +\chapter{Zapisywanie i otwieranie projektów i multimediów} + +Kiedy już stworzymy jakiś projekt, dobrze by było móc go zapisać, aby mieć go pod ręką, kiedy ponownie uruchomimy \Snap{a}. Jest na to kilka sposobów. Możemy zapisać projekt na swoim komputerze albo na stronie internetowej \Snap{a}. Zaletą zapisywania w~sieci jest dostęp do projektów nawet podczas używania innego komputera lub urządzenia mobilnego, takiego jak tablet czy smartfon. Z~kolei zapisywanie na własnym komputerze pozwala na dostęp do zapisanych projektów w~przypadku braku dostępu do sieci, na przykład w~czasie podróży pociągiem lub samolotem. Dlatego właśnie mamy wiele sposobów na zapisanie projektu. + +\section{Zapisywanie na komputerze} + +Istnieją dwa różne sposoby na zapisanie projektu lub pliku multimedialnego (na przykład kostiumu) na własnym komputerze. Ta złożoność wynika z~tego, że JavaScript, w~którym \Snap{} jest zaimplementowany, celowo ogranicza wpływ programów wykonywanych przez przeglądarkę na komputer użytkownika. Jest to pożyteczne, ponieważ dzięki temu możemy z~pełnym zaufaniem uruchamiać w~\Snap{ie} cudze projekty --- bez obawy, że usuną nam wszystkie pliki lub zainfekują komputer wirusem. Jednak mechanizm ten nieco utrudnia pracę. + +\subsection{Localstore} + +{\Huge \TODO{}} + +\subsection{XML Export} + +Drugi sposób na zapisanie projektu na komputerze ma dwa etapy, ale nie ma ograniczeń związanych z~użyciem localstore (\TODO{zmień nazwę}). Projekty zapisane w~ten sposób są zwykłymi plikami na dysku komputera i~można je wysłać do znajomych oraz otworzyć w~dowolnej przeglądarce. Ponadto ich rozmiar nie jest ograniczony. + +Z~menu ,,Plik''~\inlinepic{../common/btn-file} wybieramy ,,Eksportuj projekt\ldots''. Okno \Snap{a} zniknie i~zostanie zastąpione przez ekran wypełniony ,,krzakami''. Bez paniki! Tak ma być. To, co widzimy, to zapis projektu w~notacji zwanej XML. Głównym powodem, dla którego wygląda on jak stek bzdur, jest to, że zawiera on zakodowane obrazki i~inne multimedia zawarte w~projekcie. Jeśli się dobrze wpatrzymy, same skrypty są jako tako czytelne, choć nie wyglądają jak bloki \Snap{a}. Przeglądarka otworzyła dla tekstu XML nową kartę; okno \Snap{a} jest wciąż otwarte, ukryte pod spodem. + +Jednak tekst XML nie jest po to, abyśmy go czytali. Kiedy już mamy otwartą tę kartę, możemy użyć polecenia ,,Zapisz'' w~przeglądarce. Można je uruchomić z~poziomu menu przeglądarki lub przy pomocy skrótu klawiszowego --- zazwyczaj Command-S (Mac) lub Ctrl-S (wszędzie indziej). Wybieramy nazwę dla pliku, a~przeglądarka zapisze go w~folderze ,,Pobrane''\footnote{lub w~innym miejscu, które wskażemy w~oknie zapisu --- przyp. tłum.}. Na koniec zamykamy kartę z~plikiem XML i~wracamy do środowiska \Snap{}. + +\section{Cloud Storage} + +Inną możliwością jest zapisanie projektu ,,w~chmurze'', na stronie internetowej \Snap{a}. Aby to zrobić, musimy najpierw założyć tam konto. Klikamy na przycisku ,,Chmura'' (\,\inlinepic{../common/btn-cloud}\,) na pasku narzędzi, a~następnie wybieramy polecenie ,,Rejestracja\ldots''. Pokaże się następujące okno:\nopagebreak + +\bigpic{rejestracja} + +Należy teraz wybrać nazwę użytkownika, którą będziemy się legitymować w~obrębie strony, taką jak na przykład \code{Jens} lub \code{bh}. Jeśli jesteś użytkownikiem Scratcha, możesz również użyć na stronie \Snap{a} swojej nazwy użytkownika z~serwisu Scratch. Jeśli jesteś dzieckiem, nie wybieraj nazwy użytkownika, która zawierałaby twoje nazwisko; imiona i~inicjały są akceptowalne. Nie wybieraj też nazwy, której wstydziłbyś się pokazać innym użytkownikom lub rodzicom! Jeśli wybrana przez Ciebie nazwa jest już zajęta, będziesz musiał wybrać inną. + +Będziesz musiał też podać swój miesiąc i~rok urodzenia. \Snap{} używa tych informacji tylko i~wyłącznie aby zdecydować, czy zapytać Cię o~Twój własny adres e-mail, czy też o~adres rodzica. Jeśli jesteś dzieckiem, nie powinieneś zakładać konta w~żadnym serwisie internetowym --- wliczając w~to \Snap{a} --- bez zgody rodzica. \Snap{} nie będzie przechowywać Twojej daty urodzenia na serwerze; zostanie ona użyta tylko na Twoim własnym komputerze podczas procedury rejestracji. Program nie wymaga podania \emph{dokładnej} daty, nawet ten jeden raz, ponieważ jest to ważna dana osobowa. + +Po kliknięciu przycisku OK, na podany adres e-mail zostanie wysłana wiadomość z~początkowym hasłem do nowego konta. \Snap{} będzie przechowywać Twój adres, aby w~przypadku zapomnienia hasła móc wysłać Ci link pozwalający je zresetować. Ponadto \Snap{} wyśle Ci wiadomość, jeśli Twoje konto zostanie zawieszone za naruszenie regulaminu. Twój adres nie będzie wykorzystywany do innych celów. Nie będziesz dostawać żadnego rodzaju wiadomości marketingowych poprzez tę stronę --- ani od zespołu \Snap{a}, ani od osób trzecich. Jeśli mimo to wahasz się przed podaniem tej informacji, poszukaj w~internecie pod hasłem ,,tymczasowy e-mail''. + +Na koniec powinieneś przeczytać regulamin usługi i~wyrazić zgodę na jego postanowienia. Oto ich krótkie streszczenie: nie przeszkadzaj korzystać ze \Snap{a} innym użytkownikom, nie umieszczaj cudzych utworów chronionych prawem autorskim ani żadnych danych osobowych w~projektach udostępnionych innym. Zespół \Snap{a} nie bierze również odpowiedzialności za szkody, jeśli coś pójdzie nie tak. (To nie znaczy, że można by \emph{oczekiwać}, że coś złego się stanie --- ponieważ \Snap{} używa JavaScriptu wewnątrz przeglądarki, jest mocno izolowany od całej reszty komputera. Jednak prawo jest prawem i~musimy to wyraźnie zaznaczyć.) + +Po utworzeniu konta, możemy się na nie zalogować używając polecenia ,,Logowanie\ldots'' z~menu ,,Chmura'':\nopagebreak + +\bigpic{zaloguj-sie} + +Użyj ustalonej wcześniej nazwy użytkownika i~hasła. Jeśli zaznaczysz pole ,,Zapamiętaj mnie na tym komputerze'', zostaniesz zalogowany automatycznie, kiedy następnym razem uruchomisz \Snap{a} w~tej samej przeglądarce na tym samym komputerze. Zaznacz to pole jeśli używasz swojego własnego komputera i~nie udostępniasz go innym. Nie zaznaczaj go, jeśli używasz publicznie dostępnego komputera w~bibliotece, szkole itp. + +Kiedy już się zalogujemy, możemy wybrać opcję ,,Chmura'' w~oknie ,,Zapisz projekt'' ze strony \TODO{Link do obrazka}. Wpisujemy nazwę projektu, a~także opcjonalnie notatki, tak jak w~przypadku zapisywania w~Localstore \TODO{nazwa}. Jednak tym razem nasz projekt będzie zapisany online i~będzie mógł zostać wczytany z~dowolnego miejsca z~dostępem do sieci. + +\section{Wczytywanie zapisanych projektów} + +Kiedy już zapisaliśmy projekt, chcielibyśmy go wczytać z~powrotem do \Snap{a}. Są na to dwa sposoby: + +\begin{enumerate} +\item Jeśli zapisałeś projekt w~Localstore \TODO{nazwa} lub na swoim koncie w~serwisie \Snap{}, użyj polecenia ,,Otwórz\ldots'' z~menu ,,Plik''. Wybierz opcję ,,Przeglądarka'' lub ,,Chmura'', a~potem wybierz swój projekt z~listy i~kliknij OK. Trzecia opcja --- ,,Przykłady'' --- pozwala wybrać spośród dostarczonych przez zespół \Snap{a} przykładowych projektów. Możemy dowiedzieć się o~czym jest każdy z~nich klikając na nim i~czytając jego notatki. + +\item Jeśli zapisałeś projekt w~formacie XML na komputerze, kliknij ,,Importuj\ldots'' w~menu ,,Plik''. Przeglądarka pokaże zwyczajne okno otwierania pliku, przy pomocy którego możesz wskazać projekt, podobnie jak w~innych programach. Możesz również znaleźć swój plik XML z~poziomu pulpitu, a~potem po prostu przeciągnąć go do okna \Snap{a}. +\end{enumerate} + +Druga z powyższych technik pozwala również importować do projektu multimedia (kostiumy i dźwięki). Wystarczy wybrać ,,Importuj...'', a potem wskazać obrazek lub dźwięk zamiast pliku XML. + +\Snap{} potrafi również importować projekty stworzone przy pomocy BYOB~3.0, Scratcha~1.4 lub Scratcha~2.0 (to ostatnie z~pewnymi utrudnieniami; zob. naszą stronę internetową). Niemal wszystkie takie projekty pracują poprawnie pod \Snap{em}, z~wyłączeniem niewielkiej liczby niekompatybilnych bloków. Projekty z~BYOB~3.1 również działają, pod warunkiem, że nie korzystają z~pierwszoklasowych duszków; w~\Snap{ie}~4.1 będą już działać wszystkie projekty z~BYOB~3.1. + +\chapter{Tworzenie własnych bloków} + +\Snap{} pierwotnie nazywał się BYOB, co oznacza ,,Zbuduj Swoje Własne Bloki'' (ang. \textit{Build Your Own Blocks}). Była to pierwsza i~do dziś najważniejsza cecha, którą dodaliśmy do Scratcha. (Nazwa została zmieniona, gdyż niektórzy nauczyciele nie podzielali naszego poczucia humoru.\footnote{Skrót BYOB oznacza tak naprawdę \textit{bring your own beer}, czyli ,,przynieś własne piwo'' i~oznacza, że zostajemy zaproszeni na przyjęcie, ale oczekuje się od nas, że przyniesiemy ze sobą alkohol --- przyp. tłum.} Cóż, czasem trzeba wiedzieć kiedy się poddać.) Nowy Scratch~2.0 również częściowo obsługuje tworzenie własnych bloków. + +\section{Proste bloki} + +Na palecie ,,Dane'', w~pobliżu dolnej krawędzi, znajdziemy przycisk ,,nowy blok''.\nopagebreak + +\begin{figure}[H] +\centering +\input{paleta-dane.pdf_tex} +\end{figure} + +Po kliknięciu tego przycisku pokaże się okno dialogowe, przy pomocy którego możemy wybrać nazwę i~kształt bloku, a~także jego paletę (a~zarazem kolor). Możemy także zdecydować, czy blok ten będzie dostępny dla wszystkich duszków, czy tylko dla aktualnego duszka i~jego klonów. Uwaga: Możemy również wywołać okno ,,nowy blok'' poprzez kliknięcie na tle obszaru skryptu prawym przyciskiem (lub lewym z~wciśniętym klawiszem Ctrl), a~następnie wybranie z~menu polecenia ,,buduj nowy blok\ldots''. + +\bigpic{nowy-blok} + +W~tym oknie dialogowym możemy wybrać paletę, kształt i~nazwę bloku. Poza jednym wyjątkiem, każda paleta ma przyporządkowany jeden kolor, np. wszystkie bloki z~palety ,,Ruch'' są niebieskie. Jednak paleta ,,Dane'' zawiera zarówno pomarańczowe bloki związane ze zmiennymi, jak i~czerwone, związane z~listami. Oba kolory są dostępne; jest również opcja ,,Inne'', przy pomocy której w~palecie ,,Dane'' tworzymy szare bloki, które nie pasują do żadnej z~powyższych kategorii. + +Istnieją trzy kształty bloków, zgodnie z~konwencją która powinna być znana użytkownikom Scratcha: bloki w~kształcie puzzli to komendy --- nie zwracają one wyników. Bloki owalne to funkcje, które zwracają wyniki, a~sześciokątne to predykaty, czyli, innymi słowy, funkcje zwracające wyniki typu logicznego (prawdę lub fałsz). + +Załóżmy, że chcemy utworzyć blok o~nazwie ,,kwadrat'', który rysuje kwadrat. W~oknie ,,nowy blok'' wybieramy opcje ,,Ruch'' i~,,Komenda'', a~następnie wpisujemy ,,\code{kwadrat}'' w~pole nazwy. Po kliknięciu przycisku OK przechodzimy do edytora bloków. Korzystamy z~niego w~taki sam sposób, jak z~obszaru skryptów w~głównym oknie. Jedyna różnica polega na tym, że blok-kapelusz na górze skryptu, zamiast nosić nazwę w~rodzaju ,,\code{kiedy zostanę kliknięty}'', zawiera obraz bloku, który budujemy. Ten kapelusz nazywamy \emph{prototypem}\footnote{W~tym znaczeniu słowo ,,prototyp'' nie jest związane z~omawianym później \emph{programowaniem obiektowym opartym na prototypach}.} nowo tworzonego bloku. Aby zaprogramować działanie własnego bloku, układamy inne bloki pod kapeluszem, a~następnie klikamy przycisk OK:\nopagebreak + +\bigpic{przeciaganie-bloku-do-edytora-blokow} +\bigpic{edytor-blokow-kwadrat} + +Nasz blok pojawi się na dole palety ,,Ruch''. Oto on wraz z~rezultatem jego użycia:\nopagebreak + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]% + {blok-kwadrat-na-palecie} +\includegraphics[scale=\defaultGraphicsScale]{../common/square} +\end{figure} + +\subsection{Własne bloki z parametrami} + +Załóżmy jednak, że chcielibyśmy móc rysować kwadraty o~różnych rozmiarach. Klikamy na bloku prawym przyciskiem lub lewym z~wciśniętym klawiszem Ctrl. Kiedy wybierzemy polecenie ,,\code{edytuj\ldots}'', otworzy się edytor bloków. Zwróć uwagę na symbole plusów przed i~po słowie \code{kwadrat} w~bloku prototypu. Jeśli zatrzymasz wskaźnik myszy nad jednym z~nich, zostanie on podświetlony:\nopagebreak + +\bigpic{prototyp-bloku-z-podswietlonym-plusem} + +Klikamy prawy plus. Pojawi się okno dialogowe ,,Utwórz nazwę parametru'':\nopagebreak + +\bigpic{utworz-nazwe-parametru} + +Wpisujemy nazwę ,,\code{wielkość}'' i~klikamy przycisk OK. Dialog ten ma więcej opcji; możemy wybrać ,,\code{Tekst tytułowy}'', aby dodać słowa do nazwy bloku, tak aby po polu parametru następował tekst, podobnie jak w~bloku ,,\code{przesuń o~(\,)~kroków}''. Możemy też użyć bardziej kompleksowego okna z~wieloma opcjami dotyczącymi naszego pola parametru; zostawmy to jednak na później. Po kliknięciu OK ujrzymy w~prototypie bloku nowy parametr:\nopagebreak + +\bigpic{skrypt-bloku-kwadrat-z-parametrem-rozmiar} + +Teraz już możemy przeciągnąć pomarańczowy owal zmiennej do skryptu, a~następnie kliknąć przycisk OK w~oknie edytora bloków:\nopagebreak + +\bigpic{przeciaganie-parametru-bloku} + +Nasz blok wraz z~polem parametru jest teraz widoczny na palecie ,,Ruch'':\nopagebreak + +\bigpic{blok-kwadrat} + +Możemy narysować kwadrat dowolnego rozmiaru wpisując długość boku w~polu parametru i~uruchamiając blok jak zazwyczaj, poprzez kliknięcie na nim lub umieszczenie go w~skrypcie. + +\section{Rekurencja} + +Ponieważ nowy blok pojawił się na palecie, kiedy tylko \emph{zaczęliśmy} go tworzyć, możemy programować bloki rekurencyjne --- czyli takie, które wywołują same siebie. W tym celu należy przeciągnąć blok do jego własnej definicji:\nopagebreak + +\begin{figure}[H] +\centering +\includegraphics[scale=\defaultGraphicsScale]{skrypt-bloku-drzewo}\\ +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{blok-drzewo-w-skrypcie} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{../common/tree} +\end{minipage}% +\end{figure} + +Jeśli rekurencja jest dla Ciebie czymś nowym, oto kilka wskazówek: Kluczowym składnikiem rekurencji jest \emph{przypadek bazowy}, a~więc jakiś minimalny problem, który nasz blok może rozwiązać od razu, bez wywoływania samego siebie. W~naszym przykładzie jest to przypadek $\code{głębokość}=0$, w~którym blok nie robi zupełnie nic, co gwarantuje instrukcja \code{if}, obejmująca w~całości jego treść. Bez przypadku bazowego blok rekurencyjny wykonywałaby się w~nieskończoność\footnote{Mówiąc ściśle: dopóki nie zabraknie pamięci, bowiem każde zagnieżdżone wywołanie bloku rekurencyjnego zużywa jej odrobinę, zwalniając ją dopiero po zakończeniu wykonywania, co w~tym przypadku nigdy by nie nastąpiło. W~przeciwieństwie do wielu innych języków programowania, \Snap{} jest jednak na tyle powolny, że prawdopodobnie trudno by było doczekać się wyczerpania pamięci bez zauważenia wcześniej, że coś jest nie tak --- przyp. tłum.}, wywołując w~kółko sam siebie. + +Staraj się nie myśleć o~tym, jaką dokładnie sekwencję kroków wykonuje komputer przy uruchamianiu programu rekurencyjnego. Zamiast tego wyobraź sobie, że wewnątrz komputera żyje mnóstwo małych ludzików. Jeśli jedna z~nich --- nazwijmy ją Dorota --- rysuje drzewo o~rozmiarze 100 i~głębokości 6, musi zatrudnić Dominika, aby zrobił drzewo o~rozmiarze 70 i~głębokości 5, a~następnie Darka do zrobienia kolejnego drzewa o~rozmiarze 70 i~głębokości 5. Dominik z~kolei zatrudnia Dagmarę oraz Darię i~tak dalej. Każdy z~ludzików ma swoje własne zmienne lokalne --- \code{rozmiar} oraz \code{głębokość} --- i~każda z~nich ma swoją własną wartość, potencjalnie inną od zmiennych innych ludzików. + +Możesz również tworzyć funkcje rekurencyjne, takie jak na przykład ten blok obliczający silnię:\nopagebreak + +\begin{figure}[H] +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{skrypt-bloku-silnia} +\end{minipage}% +\begin{minipage}{0.5\textwidth} +\centering +\includegraphics[scale=\defaultGraphicsScale]{silnia-5-z-rezultatem} +\end{minipage}% +\end{figure} + +Skupmy się chwilowo na sposobie użycia bloku \code{zwróć}. Kiedy funkcja używa tego bloku, kończy ona swą pracę i~zwraca zadaną wartość; żadne inne bloki w~jej skrypcie nie będą już wykonane. Dlatego też blok \code{jeżeli --- to --- w~przeciwnym razie} z~powyższego skryptu mógłby być po prostu blokiem \code{jeżeli}; wówczas drugi z~bloków \code{zwróć} znajdowałby się pod nim, a~nie wewnątrz niego. Działanie funkcji pozostałoby takie samo, ponieważ w~momencie gdy pierwszy blok \code{zwróć} zostałby wykonany w~przypadku bazowym, działanie funkcji zostałoby zakończone, a~drugi blok \code{zwróć} zostałby zignorowany. Istnieje również komenda \code{zatrzymaj ten blok}, która także kończy wykonywanie aktualnego bloku, a~której używamy do przerywania działania bloków niczego nie zwracających, czyli komend. W~przeciwieństwie do niej blok \code{zatrzymaj ten skrypt} przerywa nie tylko obecne wywołanie bloku, ale cały wywołujący go skrypt, aż do najwyższego poziomu. + +A oto nieco bardziej zwięzły sposób na zapis funkcji silni:\nopagebreak + +\bigpic{skrypt-bloku-zwiezla-silnia} + +(Jeśli nie widzisz bloku funkcji \code{if} na palecie ,,Kontrola'', kliknij przycisk ,,Plik''~\inlinepic{../common/btn-file} na pasku narzędzi i wybierz ,,\code{Importuj narzędzia}''.) + +Pragnących dowiedzieć się więcej na temat rekursji odsyłamy do książek Erica Robertsa \textit{Thinking Recursively} (ISBN~978-0471816522) oraz nieco bardziej aktualnej \textit{Thinking Recursively in Java} (ISBN~978-0471701460). + +\section{Block Libraries} +\chapter{First Class Lists} +\section{The list Block} +\section{Lists of Lists} +\section{Functional and Imperative List Programming} +\section{Higher Order List Operations and Rings} +\chapter{Typed Inputs} +\section{Scratch's Type Notation} +\section{The \Snap{} Input Type Dialog} +\subsection{Procedure Types} +\subsection{Pulldown inputs} +\subsection{Input variants} +\subsection{Prototype Hints} +\subsection{Title Text and Symbols} +\chapter{Procedures as Data} +\section{Call and Run} +\subsection{Call/Run with inputs} +\subsection{Variables in Ring Slots} +\section{Writing Higher Order Procedures} +\subsection{Recursive Calls to Multiple-Input Blocks} +\section{Formal Parameters} +\section{Procedures as Data} +1\section{Special Forms} +\subsection{Special Forms in Scratch} +\chapter{Object Oriented Programming} +\section{Local State with Script Variables} +\section{Messages and Dispatch Procedures} +\section{Inheritance via Delegation} +\section{An Implementation of Prototyping OOP} +\chapter{The Outside World} +\section{The World Wide Web} +\section{Hardware Devices} +\section{Date and Time} +\chapter{Continuations} +\section{Continuation Passing Style} +\section{Call/Run w/Continuation} +\subsection{Nonlocal exit} +\chapter{User Interface Elements} +\section{Tool Bar Features} +\subsection{The \Snap{} Logo Menu} +\subsection{The File Menu} +\subsection{The Cloud Menu} +\subsection{The Settings Menu} +\subsection{Stage Resizing Buttons} +\subsection{Project Control Buttons} +\section{The Palette Area} +\subsection{Context Menus for Palette Blocks} +\subsection{Context Menu for the Palette Background} +\section{The Scripting Area} +\subsection{Sprite Appearance and Behavior Controls} +\subsection{Scripting Area Tabs} +\subsection{Scripts and Blocks Within Scripts} +\subsection{Scripting Area Background Context Menu} +\subsection{Controls in the Costumes Tab} +\subsection{The Paint Editor} +\subsection{Controls in the Sounds Tab} +\section{Controls on the Stage} +\section{The Sprite Corral and Sprite Creation Buttons} + +\end{document} diff --git a/help/manual-LaTeX/pl/stworz-zmienna.png b/help/manual-LaTeX/pl/stworz-zmienna.png new file mode 100644 index 00000000..5acc16d1 Binary files /dev/null and b/help/manual-LaTeX/pl/stworz-zmienna.png differ diff --git a/help/manual/pl/typowy-skrypt-wnetrze.png b/help/manual-LaTeX/pl/typowy-skrypt-wnetrze.png similarity index 100% rename from help/manual/pl/typowy-skrypt-wnetrze.png rename to help/manual-LaTeX/pl/typowy-skrypt-wnetrze.png diff --git a/help/manual/pl/typowy-skrypt.png b/help/manual-LaTeX/pl/typowy-skrypt.png similarity index 100% rename from help/manual/pl/typowy-skrypt.png rename to help/manual-LaTeX/pl/typowy-skrypt.png diff --git a/help/manual-LaTeX/pl/utworz-nazwe-parametru.png b/help/manual-LaTeX/pl/utworz-nazwe-parametru.png new file mode 100644 index 00000000..cfbc5033 Binary files /dev/null and b/help/manual-LaTeX/pl/utworz-nazwe-parametru.png differ diff --git a/help/manual-LaTeX/pl/uzycie-funkcji-do-obliczen.png b/help/manual-LaTeX/pl/uzycie-funkcji-do-obliczen.png new file mode 100644 index 00000000..44eb37d2 Binary files /dev/null and b/help/manual-LaTeX/pl/uzycie-funkcji-do-obliczen.png differ diff --git a/help/manual-LaTeX/pl/zagniezdzone-duszki-w-zagrodzie.png b/help/manual-LaTeX/pl/zagniezdzone-duszki-w-zagrodzie.png new file mode 100644 index 00000000..aca5b609 Binary files /dev/null and b/help/manual-LaTeX/pl/zagniezdzone-duszki-w-zagrodzie.png differ diff --git a/help/manual-LaTeX/pl/zagraj-dzwiek-ratunku-i-czekaj.png b/help/manual-LaTeX/pl/zagraj-dzwiek-ratunku-i-czekaj.png new file mode 100644 index 00000000..29786156 Binary files /dev/null and b/help/manual-LaTeX/pl/zagraj-dzwiek-ratunku-i-czekaj.png differ diff --git a/help/manual-LaTeX/pl/zagraj-dzwiek-ratunku.png b/help/manual-LaTeX/pl/zagraj-dzwiek-ratunku.png new file mode 100644 index 00000000..2d9977da Binary files /dev/null and b/help/manual-LaTeX/pl/zagraj-dzwiek-ratunku.png differ diff --git a/help/manual-LaTeX/pl/zaloguj-sie.png b/help/manual-LaTeX/pl/zaloguj-sie.png new file mode 100644 index 00000000..d780790e Binary files /dev/null and b/help/manual-LaTeX/pl/zaloguj-sie.png differ diff --git a/help/manual-LaTeX/pl/zaokraglij-pozycja-x-plus-100.png b/help/manual-LaTeX/pl/zaokraglij-pozycja-x-plus-100.png new file mode 100644 index 00000000..b4b4c341 Binary files /dev/null and b/help/manual-LaTeX/pl/zaokraglij-pozycja-x-plus-100.png differ diff --git a/help/manual-LaTeX/pl/zapytaj-i-ustaw.png b/help/manual-LaTeX/pl/zapytaj-i-ustaw.png new file mode 100644 index 00000000..2596f373 Binary files /dev/null and b/help/manual-LaTeX/pl/zapytaj-i-ustaw.png differ diff --git a/help/manual-LaTeX/pl/zmien-kostium-na-zolwia.png b/help/manual-LaTeX/pl/zmien-kostium-na-zolwia.png new file mode 100644 index 00000000..ff5f8f6c Binary files /dev/null and b/help/manual-LaTeX/pl/zmien-kostium-na-zolwia.png differ diff --git a/help/manual-LaTeX/pl/zmienna-na-palecie.png b/help/manual-LaTeX/pl/zmienna-na-palecie.png new file mode 100644 index 00000000..3ef5a021 Binary files /dev/null and b/help/manual-LaTeX/pl/zmienna-na-palecie.png differ diff --git a/help/manual-LaTeX/pl/zmienne-skryptu-a-b-c.png b/help/manual-LaTeX/pl/zmienne-skryptu-a-b-c.png new file mode 100644 index 00000000..4b5b247a Binary files /dev/null and b/help/manual-LaTeX/pl/zmienne-skryptu-a-b-c.png differ diff --git a/help/manual/common/defs.tex b/help/manual/common/defs.tex deleted file mode 100644 index bb4d7851..00000000 --- a/help/manual/common/defs.tex +++ /dev/null @@ -1,39 +0,0 @@ -\usepackage[T1]{fontenc} -\usepackage[utf8]{inputenc} -\usepackage{baskervald} % Default font -\usepackage{setspace} \onehalfspacing -\usepackage{graphicx} -\usepackage{color} -\usepackage{textcomp} -\usepackage{hyperref} - -% This macro produces a "Snap!" logo. -% -% Note that in Polish (and other languages), nouns are inflected. The form "Snap!" with suffix looks plain stupid, so the macro takes a suffix as argument. If the suffix is non-empty, no exclamation mark is generated, and the suffix is used instead. -\newcommand{\Snap}[1]{% - \textsf{% - Snap% - \ifx\\#1\\\textit{!\@}% - \else #1% - \fi% - }% -} - -\newcommand{\code}[1]{\textsf{#1}} -\newcommand{\defaultGraphicsScale}{0.6} - -\renewcommand{\thechapter}{\Roman{chapter}} -\renewcommand{\thesection}{\Alph{section}} -\renewcommand{\thesubsection}{} - -\newcommand{\inlinepic}[1]{% - \raisebox{-4pt}{% - \includegraphics[scale=\defaultGraphicsScale]{#1}% - }% -} - -\newcommand{\bigpic}[1]{ - \begin{center} - \includegraphics[scale=\defaultGraphicsScale]{#1} - \end{center} -} diff --git a/help/manual/en/snap-manual.tex b/help/manual/en/snap-manual.tex deleted file mode 100644 index 036ad1ea..00000000 --- a/help/manual/en/snap-manual.tex +++ /dev/null @@ -1,159 +0,0 @@ -\documentclass{report} - -\input{../common/defs.tex} - -\begin{document} - -\title{\Snap{} Reference Manual} -\author{Brian Harvey \and Jens M\"{o}nig} -\date{} - -\maketitle - -\tableofcontents - -\chapter*{} -\section*{Acknowledgements} - -We have been extremely lucky in our mentors. Jens cut his teeth in the company of the Smalltalk pioneers: Alan Kay, Dan Ingalls, and the rest of the gang who invented personal computing and object oriented programming in the great days of Xerox PARC. He worked with John Maloney, of the MIT Scratch Team, who developed the Morphic graphics framework that's still at the heart of \Snap{}. The brilliant design of Scratch, from the Lifelong Kindergarten Group at the MIT Media Lab, is crucial to \Snap{}. - -\textbf{\emph{Our earlier version, BYOB, was a direct modification of the Scratch source code. \Snap{} is a complete rewrite, but its code structure and its user interface remain deeply indebted to Scratch. And the Scratch Team, who could have seen us as rivals, have been entirely supportive and welcoming to us.}} - -Brian grew up at the MIT and Stanford Artificial Intelligence Labs, learning from Lisp inventor John McCarthy, Scheme inventors Gerald~J. Sussman and Guy Steele, and the authors of the world's best computer science book, \textit{Structure and Interpretation of Computer Programs}, Hal Abelson and Gerald~J. Sussman with Julie Sussman, among many other heroes of computer science. - -\textbf{\emph{In the glory days of the MIT Logo Lab, we used to say, ``Logo is Lisp disguised as BASIC.'' Now, with its first class procedures, lexical scope, and first class continuations, \Snap{} is Scheme disguised as Scratch.}} - -We have been fortunate to get to know an amazing group of brilliant middle school (!\@) and high school students through the Scratch Advanced Topics forum, several of whom have contributed code to \Snap{}: Kartik Chandra, Nathan Dinsmore, Connor Hudson, and Ian Reynolds. Many more have contributed ideas and alpha-testing bug reports. UC Berkeley students who've contributed code include Michael Ball, Achal Dave, Kyle Hotchkiss, Ivan Motyashov, and Yuan Yuan. Contributors of translations are too numerous to list here, but they're in the ``About...'' box in \Snap{} itself. This work was supported in part by the National Science Foundation grant 1143566, and in part by MioSoft. - -\clearpage - -\begin{center} -\bf \Huge \Snap{} Reference Manual \\ -\huge Version 4.0 -\vspace{40pt} -\end{center} - -\Snap{} (formerly BYOB) is an extended reimplementation of Scratch (\url{http://scratch.mit.edu}) that allows you to Build Your Own Blocks. It also features first class lists, first class procedures, and continuations. These added capabilities make it suitable for a serious introduction to computer science for high school or college students. To run \Snap{}, open a browser window and connect to either \url{http://snap.berkeley.edu/run} to start with a minimal set of blocks or \url{http://snap.berkeley.edu/init} to load a small set of additional blocks (a little slower startup, but recommended for convenience and assumed in this manual). - -\clearpage - -\chapter{Blocks, Scripts, and Sprites} - -This chapter describes the \Snap{} features inherited from Scratch; experienced Scratch users can skip to section~\ref{sec:nesting-sprites}. - -\Snap{} is a programming language---a notation in which you can tell a computer what you want it to do. Unlike most programming languages, though, \Snap{} is a visual language; instead of writing a program using the keyboard, the \Snap{} programmer uses the same drag-and-drop interface familiar to computer users. - -Start \Snap{}. You should see the following arrangement of regions in the window: - -\begin{center} -\includegraphics[width=\textwidth]{window-regions} -\end{center} - -(The proportions of these areas may be different, depending on the size and shape of your browser window.) - -A \Snap{} program consists of one or more \emph{scripts}, each of which is made of \emph{blocks}. Here's a typical script: - -\label{fig:typical-script} -\bigpic{typical-script} - -The five blocks that make up this script have three different colors, corresponding to three of the eight \emph{palettes} in which blocks can be found. The palette area at the left edge of the window shows one palette at a time, chosen with the eight buttons just above the palette area. In this script, the gold blocks are from the Control palette; the green block is from the Pen palette; and the blue blocks are from the Motion palette. A script is assembled by dragging blocks from a palette into the \emph{scripting area} in the middle part of the window. Blocks snap together (hence the name \Snap{} for the language) when you drag a block so that its indentation is near the tab of the one above it: - -\bigpic{snapping-blocks} - -The white horizontal line is a signal that if you let go of the green block it will snap into the tab of the gold one. - -\subsection{Hat Blocks and Command Blocks} - -At the top of the script is a \emph{hat} block, which indicates when the script should be carried out. Hat block names typically start with the word ``\code{when}''; in this example, the script should be run when the green flag near the right end of the \Snap{} tool bar is clicked. (The \Snap{} tool bar is part of the \Snap{} window, not the same as the browser's or operating system's menu bar.) A script isn't required to have a hat block, but if not, then the script will be run only if the user clicks on the script itself. A script can't have more than one hat block, and the hat block can be used only at the top of the script; its distinctive shape is meant to remind you of that. - -The other blocks in this script are \emph{command} blocks. Each command block corresponds to an action that \Snap{} already knows how to carry out. For example, the block \inlinepic{move-10-steps} tells the sprite (the arrowhead shape on the \emph{stage} at the right end of the window) to move ten steps (a step is a very small unit of distance) in the direction in which the arrowhead is pointing. We'll see shortly that there can be more than one sprite, and that each sprite has its own scripts. Also, a sprite doesn't have to look like an arrowhead, but can have any picture as a costume. The shape of the \code{move} block is meant to remind you of a Lego\texttrademark{} brick; a script is a stack of blocks. (The word ``block'' denotes both the graphical shape on the screen and the procedure, the action, that the block carries out.) - -The number~10 in the \code{move} block above is called an \emph{input} to the block. By clicking on the white oval, you can type any number in place of the 10. The sample script on page~\pageref{fig:typical-script} uses 100 as the input value. We'll see later that inputs can have non-oval shapes that accept values other than numbers. We'll also see that you can compute input values, instead of typing a particular value into the oval. A block can have more than one input slot. For example, the \code{glide} block located about halfway down the Motion palette has three inputs. - -Most command blocks have that brick shape, but some, like the \code{repeat} block in the sample script, are \emph{C-shaped}. Most C-shaped blocks are found in the Control palette. The slot inside the C shape is a special kind of input slot that accepts a \emph{script} as the input. In the sample script, the \code{repeat} block has two inputs: the number 4 and the script - -\bigpic{typical-script-inner} - - - -\section{Sprites and Parallelism} -\subsection{Costumes and Sounds} -\subsection{Inter-Sprite Communication with Broadcast} -\section{Nesting Sprites: Anchors and Parts} -\label{sec:nesting-sprites} -\section{Reporter Blocks and Expressions} -\section{Predicates and Conditional Evaluation} -\section{Variables} -\subsection{Global Variables} -\subsection{Script Variables} -\section{Etcetera} -\chapter{Saving and Loading Projects and Media} -\section{Local Storage} -\subsection{Localstore} -\subsection{XML Export} -\section{Cloud Storage} -\section{Loading Saved Projects} -\chapter{Building a Block} -\section{Simple Blocks} -\subsection{Custom Blocks with Inputs} -\section{Recursion} -\section{Block Libraries} -\chapter{First Class Lists} -\section{The list Block} -\section{Lists of Lists} -\section{Functional and Imperative List Programming} -\section{Higher Order List Operations and Rings} -\chapter{Typed Inputs} -\section{Scratch's Type Notation} -\section{The \Snap{} Input Type Dialog} -\subsection{Procedure Types} -\subsection{Pulldown inputs} -\subsection{Input variants} -\subsection{Prototype Hints} -\subsection{Title Text and Symbols} -\chapter{Procedures as Data} -\section{Call and Run} -\subsection{Call/Run with inputs} -\subsection{Variables in Ring Slots} -\section{Writing Higher Order Procedures} -\subsection{Recursive Calls to Multiple-Input Blocks} -\section{Formal Parameters} -\section{Procedures as Data} -\section{Special Forms} -\subsection{Special Forms in Scratch} -\chapter{Object Oriented Programming} -\section{Local State with Script Variables} -\section{Messages and Dispatch Procedures} -\section{Inheritance via Delegation} -\section{An Implementation of Prototyping OOP} -\chapter{The Outside World} -\section{The World Wide Web} -\section{Hardware Devices} -\section{Date and Time} -\chapter{Continuations} -\section{Continuation Passing Style} -\section{Call/Run w/Continuation} -\subsection{Nonlocal exit} -\chapter{User Interface Elements} -\section{Tool Bar Features} -\subsection{The \Snap{} Logo Menu} -\subsection{The File Menu} -\subsection{The Cloud Menu} -\subsection{The Settings Menu} -\subsection{Stage Resizing Buttons} -\subsection{Project Control Buttons} -\section{The Palette Area} -\subsection{Context Menus for Palette Blocks} -\subsection{Context Menu for the Palette Background} -\section{The Scripting Area} -\subsection{Sprite Appearance and Behavior Controls} -\subsection{Scripting Area Tabs} -\subsection{Scripts and Blocks Within Scripts} -\subsection{Scripting Area Background Context Menu} -\subsection{Controls in the Costumes Tab} -\subsection{The Paint Editor} -\subsection{Controls in the Sounds Tab} -\section{Controls on the Stage} -\section{The Sprite Corral and Sprite Creation Buttons} - -\end{document} diff --git a/help/manual/pl/snap-podrecznik.tex b/help/manual/pl/snap-podrecznik.tex deleted file mode 100644 index f041a171..00000000 --- a/help/manual/pl/snap-podrecznik.tex +++ /dev/null @@ -1,167 +0,0 @@ -% !TeX spellcheck = pl - -\documentclass{report} - -\input{../common/defs.tex} - -\usepackage[polish]{babel} -\usepackage{polski} -\frenchspacing -\usepackage{indentfirst} - -\begin{document} - -\title{\Snap{} \\ Podręcznik użytkownika} -\author{Brian Harvey \and Jens M\"{o}nig} -\date{} - -\maketitle - -\tableofcontents - -\chapter*{} -\section*{Podziękowania} - -Mieliśmy ogromne szczęście do mentorów. Jens zdobył dużo doświadczenia pracując wśród pionierów Smalltalka: Alana Kaya, Dana Ingallsa i~reszty ekipy, która wynalazła komputery osobiste i~programowanie obiektowe w~najlepszych dniach firmy Xerox PARC. Pracował z~Johnem Maloneyem z~zespołu Scratcha w~MIT\footnote{Massachusetts Institute of Technology, amerykańska uczelnia techniczna --- przyp. tłum.}, autorem platformy graficznej Morphic, wciąż stanowiącej fundament \Snap{a}. Znakomity projekt języka Scratch, autorstwa Lifelong Kindergarten Group z~MIT Media Lab, odgrywa w~\Snap{ie} kluczową rolę. - -\textbf{\emph{Nasza poprzednia wersja, BYOB, była bezpośrednią modyfikacją kodu źródłowego Scratcha. \Snap{} został napisany od zera, lecz struktura jego kodu oraz interfejs użytkownika pozostają mocno zakorzenione w~Scratchu. Z~kolei zespół Scratcha, który mógłby widzieć w~nas rywali, przyjął nas ciepło i~okazał nam całkowite wsparcie.}} - -Brian zdobywał szlify w~MIT oraz Stanford Artificial Intelligence Labs\footnote{Laboratorium sztucznej inteligencji na Uniwersytecie Stanforda --- przyp. tłum.}, gdzie uczył się pod okiem Johna McCarthy'ego, twórcy Lispa, oraz Geralda~J. Suss\-mana i~Guya Steele'a, twórców języka Scheme. Zdobywał również wiedzę od wielu innych wybitnych informatyków, w~tym autorów najlepszej książki z zakresu informatyki --- \emph{Struktury i~interpretacji programów komputerowych}: Hala Abelsona, Geralda~J. Suss\-mana i~Julie Suss\-man. - -\textbf{\emph{Za starych dobrych czasów mawialiśmy w~MIT Logo Lab: ,,Język Logo to Lisp w przebraniu BASIC-a''. Dziś, ze swoimi pierwszoklasowymi procedurami, zasięgami leksykalnymi~i pierwszoklasowymi kontynuacjami, \Snap{} jest jak Scheme w~przebraniu Scratcha.}} - -Szczęśliwym zrządzeniem losu, poprzez forum Scratch Advanced Topics, poznaliśmy wspaniałą grupę błyskotliwych uczniów gimnazjów~(!\@) i liceów. Kilku z nich wniosło swój wkład w~kod \Snap{a}: Kartik Chandra, Nathan Dinsmore, Connor Hudson i~Ian Reynolds. Ponadto wielu zgłosiło pomysły i~raporty błędów podczas testowania wersji alfa. Wśród studentów Uniwersytetu Kalifornijskiego w~Berkeley, którzy przyczynili się do rozwoju kodu, znajdują się Michael Ball, Achal Dave, Kyle Hotchkiss, Ivan Motyashov i~Yuan Yuan. Wymienianie wszystkich tłumaczy zajęłoby zbyt wiele miejsca, ale można ich odnaleźć w~okienku ,,O Snap!...'' dostępnym w~programie. Niniejsze dzieło powstało częściowo w~ramach grantu nr~1143566 udzielonego przez National Science Foundation, a częściowo przy wsparciu firmy MioSoft. - -\clearpage - -\begin{center} -\bf \Huge \Snap{} \\ -Podręcznik użytkownika \\ -\huge Wersja 4.0 \vspace{40pt} -\end{center} - -\Snap{} to rozszerzona reimplementacja języka Scratch (\url{http://scratch.mit.edu}), która pozwala na tworzenie własnych bloków (ang.\ \textit{Build Your Own Blocks}; stąd dawna nazwa \Snap{a} --- BYOB). Opisywany tu język obsługuje pierwszoklasowe listy, procedury i~kontynuacje. Te dodatkowe możliwości sprawiają, że nadaje się on do przeprowadzenia poważnego wstępu do informatyki dla uczniów liceów i szkół wyższych. Aby uruchomić środowisko \Snap{}, wystarczy otworzyć przeglądarkę internetową i~wpisać adres \url{http://snap.berkeley.edu/run}, aby zacząć pracę z~minimalnym zestawem bloków. Można też użyć adresu \url{http://snap.berkeley.edu/init}, aby załadować niewielki zestaw dodatkowych bloków. Wiąże się to z~nieco wolniejszym ładowaniem, ale jest zalecane dla wygody użytkowników (w~dalszej części podręcznika będziemy zakładali korzystanie z~tej właśnie metody). - -\clearpage - -\chapter{Bloki, skrypty i duszki} - -W~tym rozdziale poznamy kilka cech języka \Snap{} odziedziczonych po Scratchu; doświadczeni użytkownicy Scratcha mogą przejść od razu do sekcji~\ref{sec:zagnieżdżanie-duszków}. - -\Snap{} jest językiem programowania --- notacją, przy pomocy której możemy powiedzieć komputerowi, co ma zrobić. Jednak w~odróżnieniu od większości innych, \Snap{} jest językiem wizualnym; programując w~nim, zamiast posługiwać się klawiaturą, używamy metody ,,przeciągnij i~upuść'', dobrze znanej użytkownikom komputerów. - -Uruchom teraz środowisko \Snap{}. Powinieneś zobaczyć ekran podzielony na kilka obszarów: - -\begin{center} -\def\svgwidth{\textwidth} -\input{obszary-okna.pdf_tex} -\end{center} - -(Proporcje tych stref mogą się różnić, w~zależności od rozmiaru i~kształtu okna przeglądarki.) - -Program w~języku \Snap{} składa się z~jednego lub więcej \emph{skryptów}, te zaś z~kolei --- z~\emph{bloków}. Oto przykładowy skrypt: - -\label{fig:typowy-skrypt} -\bigpic{typowy-skrypt} - -Na powyższy skrypt składa się pięć bloków w~trzech różnych kolorach, odpowiadających trzem z~ośmiu \emph{palet} z~blokami. Obszar palet, znajdujący się po lewej stronie okna, pokazuje jedną paletę na raz. Do zmiany widocznej palety służy osiem przycisków znajdujących się tuż nad tym obszarem. Bloki ciemnożółte, widoczne w~naszym skrypcie, pochodzą z~palety ,,Kontrola''; zielone z~palety ,,Pisak'', a~niebieskie --- z~palety ,,Ruch''. Aby złożyć taki skrypt, należy poprzeciągać odpowiednie bloki z~palet do \emph{obszaru skryptów}, umiejscowionego na środku okna. Kiedy układamy jeden blok pod drugim w~taki sposób, aby wcięcie dolnego bloku znalazło się w~pobliżu wypustki tego powyżej, bloki łączą się ze sobą (ang. \textit{snap together}; stąd nazwa języka \Snap{}): - -\bigpic{laczenie-blokow} - -Pozioma biała linia sygnalizuje, że jeśli puścimy zielony blok, połączy się on z~wypustką ciemnożółtego. - -\subsection{Bloki-czapki i bloki poleceń} - -Na górze skryptu znajduje się \emph{blok-czapka}, który określa, kiedy skrypt ma zostać wykonany. Nazwy bloków-czapek zazwyczaj zaczynają się słowem ,,\code{kiedy}''; nasz przykładowy skrypt powinien zostać uruchomiony w~momencie kliknięcia zielonej flagi, znajdującej się w pobliżu prawej strony paska narzędzi \Snap{a}. (Pasek ten jest częścią okna programu \Snap{}; nie chodzi tutaj o pasek menu przeglądarki lub systemu operacyjnego.) Skrypt nie musi posiadać czapki, jednak w~takim przypadku zostanie wykonany tylko wtedy, gdy użytkownik sam go kliknie. Skrypt nie może mieć więcej niż jednej czapki; jej charakterystyczny kształt służy łatwiejszemu zapamiętaniu tej szczególnej własności. - -Pozostałe bloki w naszym skrypcie to \emph{bloki poleceń}. Każdy z~nich oznacza jakąś akcję, którą \Snap{} potrafi wykonać. Na przykład blok \inlinepic{przesun-o-10-krokow} nakazuje duszkowi\footnote{W grafice komputerowej słowem ,,duszek'' (ang. \textit{sprite}) nazywa się ruchomy obiekt na ekranie --- przyp. tłum.}, czyli strzałce na \emph{scenie} po prawej stronie okna, aby przesunął się o~dziesięć kroków do przodu w~kierunku, w~którym jest zwrócony. Każdy krok to niewielka odległość na ekranie. Wkrótce przekonamy się, że na scenie może być więcej duszków, a~każdy z nich może mieć własne skrypty. Ponadto duszki nie muszą wyglądać jak strzałki; ich kostiumy mogą być dowolnymi obrazkami. Kształt bloku \code{przesuń} ma za zadanie przypominać klocek, skrypt zaś jest jak wieża z klocków. Słowa ,,blok'' będziemy używać dla oznaczenia zarówno graficznego symbolu na ekranie, jak i~procedury (akcji) jaką ten blok wykonuje. - -Liczbę 10 w powyższym bloku \code{przesuń} nazywamy jego \emph{parametrem}. Kliknąwszy na białym, zaokrąglonym polu, możemy wpisać w~jej miejsce dowolną inną. W przykładowym skrypcie ze strony \pageref{fig:typowy-skrypt} parametrem jest liczba 100. Jak się później okaże, pola parametrów mogą mieć kształty inne od zaokrąglonych; oznacza to wtedy, że akceptują one wartości inne niż liczby. Zobaczymy również, że zamiast wpisywać konkretne wartości w~pola, możemy nakazać komputerowi je obliczać. Ponadto blok może mieć więcej niż jeden parametr. Na przykład blok \code{leć}, znajdujący się mniej więcej w~połowie palety ,,Ruch'', przyjmuje trzy parametry. - -Większość bloków poleceń ma kształt klocków, lecz niektóre, jak \code{powtórz} z~tego samego przykładu, wyglądają jak \emph{klamry}. Większość bloków klamrowych można znaleźć na palecie ,,Kontrola'. Wnętrze klamry jest szczególnym rodzajem pola parametru, który przyjmuje \emph{skrypt} jako parametr. W~przykładowym skrypcie blok \code{powtórz} ma dwa parametry: liczbę 4 oraz skrypt - -\bigpic{typowy-skrypt-wnetrze} - - - -\section{Sprites and Parallelism} -\subsection{Costumes and Sounds} -\subsection{Inter-Sprite Communication with Broadcast} -\section{Zagnieżdżanie duszków: kotwice i części} -\label{sec:zagnieżdżanie-duszków} -\section{Reporter Blocks and Expressions} -\section{Predicates and Conditional Evaluation} -\section{Variables} -\subsection{Global Variables} -\subsection{Script Variables} -\section{Etcetera} -\chapter{Saving and Loading Projects and Media} -\section{Local Storage} -\subsection{Localstore} -\subsection{XML Export} -\section{Cloud Storage} -\section{Loading Saved Projects} -\chapter{Building a Block} -\section{Simple Blocks} -\subsection{Custom Blocks with Inputs} -\section{Recursion} -\section{Block Libraries} -\chapter{First Class Lists} -\section{The list Block} -\section{Lists of Lists} -\section{Functional and Imperative List Programming} -\section{Higher Order List Operations and Rings} -\chapter{Typed Inputs} -\section{Scratch's Type Notation} -\section{The \Snap{} Input Type Dialog} -\subsection{Procedure Types} -\subsection{Pulldown inputs} -\subsection{Input variants} -\subsection{Prototype Hints} -\subsection{Title Text and Symbols} -\chapter{Procedures as Data} -\section{Call and Run} -\subsection{Call/Run with inputs} -\subsection{Variables in Ring Slots} -\section{Writing Higher Order Procedures} -\subsection{Recursive Calls to Multiple-Input Blocks} -\section{Formal Parameters} -\section{Procedures as Data} -\section{Special Forms} -\subsection{Special Forms in Scratch} -\chapter{Object Oriented Programming} -\section{Local State with Script Variables} -\section{Messages and Dispatch Procedures} -\section{Inheritance via Delegation} -\section{An Implementation of Prototyping OOP} -\chapter{The Outside World} -\section{The World Wide Web} -\section{Hardware Devices} -\section{Date and Time} -\chapter{Continuations} -\section{Continuation Passing Style} -\section{Call/Run w/Continuation} -\subsection{Nonlocal exit} -\chapter{User Interface Elements} -\section{Tool Bar Features} -\subsection{The \Snap{} Logo Menu} -\subsection{The File Menu} -\subsection{The Cloud Menu} -\subsection{The Settings Menu} -\subsection{Stage Resizing Buttons} -\subsection{Project Control Buttons} -\section{The Palette Area} -\subsection{Context Menus for Palette Blocks} -\subsection{Context Menu for the Palette Background} -\section{The Scripting Area} -\subsection{Sprite Appearance and Behavior Controls} -\subsection{Scripting Area Tabs} -\subsection{Scripts and Blocks Within Scripts} -\subsection{Scripting Area Background Context Menu} -\subsection{Controls in the Costumes Tab} -\subsection{The Paint Editor} -\subsection{Controls in the Sounds Tab} -\section{Controls on the Stage} -\section{The Sprite Corral and Sprite Creation Buttons} - -\end{document} diff --git a/help/reportDate.png b/help/reportDate.png index f13cf387..36478072 100644 Binary files a/help/reportDate.png and b/help/reportDate.png differ diff --git a/help/sentence->list.png b/help/sentence->list.png deleted file mode 100644 index 7a3bd5d0..00000000 Binary files a/help/sentence->list.png and /dev/null differ diff --git a/history.txt b/history.txt index f0491ee9..f5d32418 100755 --- a/history.txt +++ b/history.txt @@ -2568,3 +2568,547 @@ ______ 150814 ------ * Blocks: fixed #907 + +150915 +------ +* new Croatian translation. Yay!! Thanks, Zeljko Hrvoj! +* fixed #925 + +150923 +------ +* Morphic, Objects: Improve display precision (stop rounding display coordinates) +* Added “ceiling” function, thanks, Michael +* Updated various translations + +151002 +------ +* GUI, Blocks, BYOB: New “Export Project Summary” Feature, also: exporting script pics now includes attached comments +* Blocks, Objects, Threads: Key hat block and key sensor support for “any” key +* German translation update + +151007 +------ +* BYOB, Objects, GUI: New “Remove Unused Global Blocks” Feature +* GUI, Lists: “Export Project Summary” improvements: + - show variable values as watcher pics + - expand list watcher pics to show their complete contents (1. level) + - url for shared projects + - table of contents + - basic support for sprite nesting and inheritance + - make the summary “browsable” instead of editable + - outline around sprite / stage snapshots + - experimental hidden (shift-click) “drop-shadows” option +* GUI: Rearrange project menu, only show global blocks-related ops if there are any +* GUI: Remove URL location.hash information when loading a new project +* Store: Fix deserialization support for projects using inheritance +* German translation update + + +++++++++++++++++++++++++++ +new stuff - bulk of 151116 +++++++++++++++++++++++++++ + +151030 +------ +* Blocks: Tweak precision of rendering of transparent “holes” +* updated Czech translation +* Morphic: Streamlined nop-stepping +* Blocks: Let SyntaxElements step (again), for better input slot editing experience + +151101 +------ +* BYOB: Script pic: Always export comments attached to custom block definitions +* Blocks: fixed #982 (made %interaction slot static) +* Morphic: removed an obsolete line (“dragOrigin”) +* BYOB: make block editor big enough to show the whole definition, if possible +* BYOB: remember user-set position and size of block editor when pressing “OK”, per session (not serialized in project) +* Blocks: speed up stacking of commands (also when done programmatically) by suppressing redraws +* Morphic, Blocks, BYOB: Suppress redundant redraws + +151104 +------ +* Morphic: new grabTheshold preference to suppress accidental grabbing through micro-movements of the hand +* GUI: hidden (shift-click) option to adjust the grabThreshold for the current session +* Lists, Blocks: Expand list watchers inside result bubbles to show everything +* Objects: Expand list watchers inside speech/thought bubbles to show everything +* Morphic: fixed a bug that occasionally expanded the Hand’s bounds when dragging morphs + +151107 +------ +* Threads: invoke a block synchronously + +151009 +------ +* Morphic: cache fullImage and fullBounds when dragging +* Blocks: make reporters semi-transparent while dragging +* GUI: make SpriteIcons semi-transparent while dragging +* Blocks: make it harder to drop reporters onto filled custom C-slots and variadic slot arrows +* Blocks: make ScriptsMorphs notice transparent clicks (addresses #997) +* Blocks: fixed “undrop” for replacing C-slots with reporters +* BYOB: fixed ctrl-f for the BlockEditor in all situations + +151111 +------ +* Objects: fixed a between slideBackTo() and possible running scripts in sprites, thanks, Paul, for reporting it! + +151112 +------ +* Blocks, Objects, Threads: new internal slot type: %cl for auto-reifying C-slots that reject reporter drops. Changed (hidden) “for each” to reject reporters in C-slot + +151113 +------ +* Frames, snap.html: initial version of a new general purpose prototypal single inheritance object system +* snap_slo.html: alternative animation-frame based outer scheduler, experimental +* Threads: added optional timeout to the new synchronous invoke(block) function +* Blocks: fixed too brutally optimized redraw for “ringify” and “unringify” + +151114 +------ +* Frames, snap.html, snap_slo.html: remove initial version for now, needs more low-levelish rewrite (Map-based “shortcut” design doesn’t cut it). + +151116 +------ +* Blocks, GUI: Slightly less transparency for dragged reporters and sprite icons + +=== v4.0.3 (unreleased) === + +++++++++++++++++++++++++++ +end - bulk of 151116 +++++++++++++++++++++++++++ + +151116 community contributions +------ +* new Bulgarian and Romanian translations contributed! +* fix for IE backspace and tab errors contributed! +* better resource loading mechanism contributed! + +151117 +------ +* Blocks: fixed a zebra-coloring glitch for BooleanSlotMorph + +151120 +------ +* Lists: fixed linked lists identity loss when showing watchers + +151204 +------ +* 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 +++++++++++++++++++++++++++ + +151215 - more changes +------ +* Cloud: 10 MB cloud upload limit for media per project + +151215 - contributions +------ +* Objects, Paint: Automatic Sprite Center Detection, Thanks, Craxic!! +* Morphic: Handling of diacritics, [Alt] + key in input fields (Windows), Thanks, DaDoro!! +* NL translation update +* Use Blob API to Save Files (to Disk), Thanks, Michael!! + +151217 +------ +* Threads: fixed #1071 “length of list” type error possibility by no longer guaranteeing that the red “length of” reporter also works on text input + +151218 +------ +* new Arabic translation, yay!! Thanks Tarek Galal!!! + +151219 +------ +* Objects: Optimization: Don’t redraw unrotateable sprites on TURN + +151221 +------ +* Morphic: Native Copy & Paste support, thanks, @cyderize, for this contribution!! +* GUI: Code tweaks +* Portuguese translation update, thanks, Manuel! + +151222 +------ +* Blocks, Objects, Threads, Locale: revert to ’any key’ in the key-pressed menu +* GUI: Improve sorting and tab switching in the Project Dialog, Thanks, Michael! + +151223 +------ +* Morphic: fixed #1083 + +160108 +------ +* BYOB: fixed #1098 +* Threads: remove a redundant yield from the fork primitive +* GUI: fixed #1099, thanks, Michael! +* Portuguese translation update, thanks, Manuel! + +160111 +------ +* BYOB: fixed #1107 + +160116 +------ +* Blocks: fixed a multi-line input slot layout glitch + +160117 +------ +* BYOB: preserve custom block instances’ block var values when editing their definition + +160118 +------ +* Paint: avoid pixel collision detection in PaintCanvas (optimization) +* BYOB: fixed a zebra coloring glitch in the block editor + +160119 +------ +* Threads, Store: Throw an error for “obsolete” blocks instead of (forever) doing nothing (and thus often freezing and crashing). Thanks, Paul, for helping identify this! + +160122 +------ +* Blocks: Fixed a slight rendering glitch when deleting reporters via the context menu + +160224 +------ +* == v4.0.5 ==== - table views + +160306 +------ +* Objects: Reenable custom hat blocks when dropping a sprite + +160316 +------ +* Store, Objects, GUI: fixed #99 (saving linked lists) +* Objects: fixed #1163 +* 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 + +* new Indonesian translation. Yay!! Thank you, Alexander Liu!! +* Translation updates: Slovenian, Portuguese, Chinese +* minor bug fixes + +== v4.0.7 ==== - first class sprites + +160504 +------ +* Morphic, Objects, Blocks, Threads, GUI: Partially shallow-copy clones for speed +* new Estonian translation! Yay!! Thanks, Hasso Tepper! + +== v4.0.7.1 ==== - cloning speed-up + +160509 +------ +* Threads: fixed #1212 - Null continuation doesn't escape from calling context. +* Updated Simplified Chinese translation, thanks to @ubertao! +* Media import dialog with thumbnail, thanks to @ubertao! + +== v4.0.7.2 ==== + +160714 +------ +* New Features + - Retina Display Support, thanks, Bartosz Leper!! + - Additional Graphic Effects, thanks, Dylan Servilla!! + - Interactive Toggle Switches for Boolean Slots and Literals + - Resizable Palette (double-click on resizers to slide back to normal) + +* New Default Settings (now enabled by default) + - Keyboard Editing + - Tables Support + +* Bugfixes (most notable only): + - reject dropping hat-blocks into block editor scripting areas + - accept multi-line inputs (e.g. in JS-functions or code-mapping blocks) with shift-enter + - prevent “make a block” dialog from being closed by pressing “OK” if no label text has been specified + - show error message balloons happening in other sprites next to their icon in the sprite-corral + - (again) enable recursive cloning + - prevent expandable blocks from expanding / collapsing inside the palette + +* Translation updates + - Italian + - Swedish + - Chinese + - Russian + - Catalan + - German + +== v4.0.8 ==== - retina screen support - + +160715 +------ +* Blocks: activate generic hat blocks inserted via keyboard editing + +== v4.0.8.1 ==== + +160717 +------ +* Morphic: fixed collision detection for non-integer devicePixelRatios + +== v4.0.8.2 ==== + +160719 +------ +* Morphic: avoid blitting artifacts for non-integer devicePixelRatios in Firefox +* Widgets: fixed 3D corners for buttons in Firefox for Windows +* Objects: fixed color collision detection for retina mode +* Threads: enable broadcasts to be sent to specific sprites (experimental) + +== v4.0.8.3 ==== + +160720 +------ +* GUI: fixed #1333 - paint a new costume not working in retina mode in FF and Edge + +== v4.0.8.4 ==== + +160731 +------ +* GUI: fixed #1348 - opening projects from url not working in non-English + +== v4.0.8.5 ==== + +160803 +------ +* Store: restore implicit formal parameters for serialized lambdas + +== v4.0.8.6 ==== + +160812 +------ +* Threads: for hidden sprites display ASK questions in the input box +* Morphic: replace deprecated KeyboardEvent.keyIdentifier with .key + +== v4.0.8.7 ==== + +*** in development *** + +160915 +------ +* new single stepping feature (like Scratch 1.4) with flashing blocks +* slider for single-stepping speed +* pausing now flashes the currently active blocks + +160916 +------ +* enable single stepping for clone-scripts w. multiple blocks flashing per script +* enable single stepping for custom block definitions +* Objects: fixed #1410 (duplicating a sprite does not duplicate its sounds) + +160918 +------ +* Treat single-stepping as thread safe (reverted on 160923) +* Allow user to trigger one step at a time, both in normal and single-stepping mode + +160919 +------ +* new “stepForward” symbol +* dragging the single-step speed slider all the way to the left turns the “resume” side of the “pause” button into “stepForward” + +160920 +------ +* atomic synching of single-stepping + +160921 +------ +* remove shift-click-to-forward-one-frame option for the “resume” button + +160922 +------ +* renamed “single stepping” to “visible stepping”, thanks, Brian! +* updated German translation + +160923 +------ +* custom block execution: only yield if directly recursive and unwrapped (“speed up”) +* new feature: “wait 0” or “wait ” now yields once, unless warped +* revert treating visual stepping as thread safe, because of music scheduling + +160924 +------ +* don’t update the recursion cache when updating a custom block definition + +160929 +------ +* Objects: fixed #1437 + +161007 +------ +* Blocks: [Keyboard-Entry] if an inserted block has inputs, go to the first one + +161010 +------ +* Morphic: configure autoscrolling +* GUI: suppress autoscrolling for the palette and the project dialog + +161011 +------ +* Blocks: make sure to fix multi-args when deleting a custom reporter definition + +161011 +------ +* Objects: fixed #1456 (collect message names from all scripts, including custom block definitions) + +161020 +------ +* Blocks: Tweak Keyboard-Entry + +161021 +------ +* Threads: Fixed #1422 + +161024 +------ +* Text Editing Tweaks, thanks, Bernat!! +* Store: fixed #1472 +* Threads: Tweak continuations + +161027 +------ +== v4.0.9 ==== + +== v4.0.9.1 ==== + +161110 +------ +* new Galician translation, yay!! Thanks, tecnoloxia.org! +* Italian translation update +* German translation update + +== v4.0.9.2 ==== + +*** in development *** + +161107 +------ +* New C-Slot auto-wrapping / snapping feature (similar to Scratch) + +161109 +------ +* Blocks, GUI: preference setting to enable auto-wrapping inside nested block stacks +* Blocks: Treat JS-function reporters the same as variable getters wrt rings +* German translation update diff --git a/index.html b/index.html index eecd318a..1f992b08 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ TurtleStitch - + @@ -13,16 +13,22 @@ + + - - - - + + + + + + diff --git a/lang-ar.js b/lang-ar.js new file mode 100755 index 00000000..62fbb749 --- /dev/null +++ b/lang-ar.js @@ -0,0 +1,1398 @@ +/* + + lang-ar.js + + Arabic transelation for SNAP! + + written by Jens Mönig + + Copyright (C) 2014 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.ar = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ãœ, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'العربية', // the name as it should appear in the language menu + 'language_translator': + 'طارق جلال', // your name for the Translators tab + 'translator_e-mail': + 'tarekgalal46@hotmail.com', // optional + 'last_changed': + '2016-01-23', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'بدون عنوان', + 'development mode': + 'وضع التصميم', + + // categories: + 'Motion': + 'الحركة', + 'Looks': + 'المظهر', + 'Sound': + 'الصوت', + 'Pen': + 'القلم', + 'Control': + 'التحكم', + 'Sensing': + 'الاستشعار', + 'Operators': + 'العمليات', + 'Variables': + 'المتغيرات', + 'Lists': + 'قوائم(مصفوفات)', + 'Other': + 'لبنات اضافيه', + + // editor: + 'draggable': + 'قابل للسحب', + + // tabs: + 'Scripts': + 'المقاطع البرمجيه', + 'Costumes': + 'المظاهر', + 'Sounds': + 'الاصوات', + + // names: + 'Sprite': + 'كائن', + 'Stage': + 'المنصة', + + // rotation styles: + 'don\'t rotate': + 'غير قابل للدوران', + 'can rotate': + 'قابل للدوران', + 'only face left/right': + 'مواجهة يمين-يسار', + + // new sprite button: + 'add a new sprite': + 'اضافة كائن جديد', + + // tab help + 'costumes tab help': + 'استيراد الصور من الحاسوب او من الانترنت \n بسحب و افلات الملف هنا', + 'import a sound from your computer\nby dragging it into here': + 'استيراد الاصوات من الحاسوب او من الانترنت \n بسحب و افلات الملف هنا', + + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'B\u00fchne ausgew\u00e4hlt:\nkeine Standardbewegungsbl\u00f6cke\n' + + 'vorhanden', + + 'move %n steps': + 'خطوة %n تحرك', + 'turn %clockwise %n degrees': + 'درجة %n %clockwise استدر', + 'turn %counterclockwise %n degrees': + 'درجة %n %counterclockwise استدر', + 'point in direction %dir': + ' %dir الاتجاه نحو إتجه', + 'point towards %dst': + ' %dst نحو إتجه ', + 'go to x: %n y: %n': + '%n =س %n =ص للنقطة أذهب', + 'go to %dst': + ' %dst الي إذهب', + 'glide %n secs to x: %n y: %n': + 'ثوان %n خلال %n =س %n =ص النقطة إلي إنزلق', + 'change x by %n': + ' %n بمقدار س غير', + 'set x to %n': + '%n تساوي س إجعل', + 'change y by %n': + ' %n بمقدار ص غير', + 'set y to %n': + '%n تساوي ص إجعل', + 'if on edge, bounce': + 'الحافة عند كنت إذا أرتد', + 'x position': + 'س الموضع', + 'y position': + 'ص الموضع', + 'direction': + 'الاتجاه', + + // looks: + 'switch to costume %cst': + '%cst المظهر إلي إنتقل', + 'next costume': + 'التالي المظهر', + 'costume #': + 'المظهر ( ترتيب / رقم )', + 'say %s for %n secs': + ' %s قـل ثانية %n لمدة', + 'say %s': + '%s قـل', + 'think %s for %n secs': + '%s فكر ثانية %n لمدة', + 'think %s': + ' %s فكـر', + 'Hello!': + '!مـرحبـا', + 'Hmm...': + 'هممم...', + 'change %eff effect by %n': + ' %eff التأثير قيمـة %n بمقدار غيّر', + 'set %eff effect to %n': + ' %eff التأثير لقيـمة %n المقدار حدد', + 'clear graphic effects': + 'الرسومية التأثيرات أحذف', + 'change size by %n': + ' %n بمقدار الحجم غيّـر', + 'set size to %n %': + '% %n لـ مساوياً الحجم إجعل ', + 'size': + 'الحجم', + 'show': + 'إظهَر', + 'hide': + 'إختَفي', + 'go to front': + 'المقدمة الي إنتقل', + 'go back %n layers': + 'طبقات %n بمقدار الخلف الي انتقل', + + 'development mode \ndebugging primitives:': + 'نمط البرمجه \nو تصحيح الاخطاء', + 'console log %mult%s': + 'console log %mult%s', + 'alert %mult%s': + 'تنبيه: %mult%s', + + // sound: + 'play sound %snd': + '%snd الصـوت شغّـل', + 'play sound %snd until done': + 'أنتهـاءة إنتظر ثم %snd الصـوت شغّـل', + 'stop all sounds': + 'الأصوات جميع أوقف', + 'rest for %n beats': + 'إقـاع وحدة %n لمدة إستـرح', + 'play note %n for %n beats': + ' %n رقـم النوتـة أعزف ، إيـقاع وحـدة %n بمقدار', + 'change tempo by %n': + '%n بمقـدار الصوت شـدّة غيّـر', + 'set tempo to %n bpm': + '%n مسـاوية الصوت شدّة إجعل', + 'tempo': + 'الصوت شدّة مقـدار', + + // pen: + 'clear': + 'امسح', + 'pen down': + 'القلـم أنـزل', + 'pen up': + 'القلـم إرفـع', + 'set pen color to %clr': + ' %clr لـ مسـاوياً القلم لون إجعل', + 'change pen color by %n': + ' %n بمقدار القلم لون غيّـر', + 'set pen color to %n': + ' %n تسـاوي القلم لـون قيمة إجعـل', + 'change pen shade by %n': + '%n بمقدار القلم لون (تعتيم/سطوع) درجة غيّر', + 'set pen shade to %n': + '%n تساوي القلم لون (تعتيم/سطوع) درجة إجعل', + 'change pen size by %n': + '%n بمقدار القلم حجـم غيّـر', + 'set pen size to %n': + ' %n لـ مساوياً (حجم/سُـمك)القلـم إجعل', + 'stamp': + 'اطبع', + + // control: + 'when %greenflag clicked': + 'الأخضر العـَلم %greenflag نقر عنـد', + 'when %keyHat key pressed': + '%keyHat مفتـاح ضغط عند', + 'when I am %interaction': + 'الفـأرة مـؤشْـر %interaction لـ أتعرض عندما', + 'clicked': + 'نقـر', + 'pressed': + 'ضغـط', + 'dropped': + 'الإفـلات_من', + 'mouse-entered': + 'دخـول', + 'mouse-departed': + 'مغـادرة', + 'when %b': + '%b عندما', + 'when I receive %msgHat': + '%msgHat رسـالة أستقبال عند', + 'broadcast %msg': + ' %msg بث', + 'broadcast %msg and wait': + 'إنتظـر ثم %msg بِـث', + 'Message name': + 'اسم الرسالة', + 'message': + 'الرسالة', + 'any message': + 'اي رسالة', + 'wait %n secs': + ' %n لـ إنتظـر', + 'wait until %b': + '%b الشرط يتحقق حتي إنتظر', + 'forever %c': + 'باستمرار كرر %c', + 'repeat %n %c': + ' %n كرر %c', + 'repeat until %b %c': + '%b حتي كرر %c', + 'if %b %c': + ' %b اذا %c', + 'if %b %c else %c': + '%b اذا %c وإلا %c', + 'report %s': + '%s وَضِّـح', + 'stop %stopChoices': + '%stopChoices اوقف', + 'all': + 'الكل', + 'this script': + 'هذا_المقطع_البرمجي', + 'this block': + 'هذا_البلوك', + 'stop %stopOthersChoices': + '%stopOthersChoices أوقف', + 'all but this script': + 'كل_المقاطع_البرمجيه_للكائنات_عدا_هذا_المقطع', + 'other scripts in sprite': + 'كل_المقاطع_البرمجية_للكائن_عدا_هذا_المقطع', + 'pause all %pause': + '%pause مـؤقتاً التنفيذ أوقف', + 'run %cmdRing %inputs': + 'نفّذ %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'شغّل %cmdRing %inputs', + 'call %repRing %inputs': + 'إستدع %repRing %inputs', + 'run %cmdRing w/continuation': + 'نفّذ %cmdRing (الفاعلية استمرار بقاء مع)', + 'call %cmdRing w/continuation': + 'استدع %cmdRing (الفاعلية استمرار بقاء مع)', + 'warp %c': + 'تسريع %c', + 'when I start as a clone': + 'مطابقة كنسخةٌ أبدأ عندما', + 'create a clone of %cln': + '%cln من أستنسـاخاً أنشئ', + 'myself': + 'نفسي', + 'delete this clone': + 'الإستنساخ هذا إحذف', + + // sensing: + 'touching %col ?': + '؟ %col لـ ملامس هـل ', + 'touching %clr ?': + '؟ %clr لـ ملامس هـل', + 'color %clr is touching %clr ?': + '؟ %clr اللون ملامس %clr اللون هل', + 'ask %s and wait': + '%s اسأل و انتظر ', + 'what\'s your name?': + '؟ إسمك هـو مـا', + 'answer': + 'الاجابة', + 'mouse x': + 'للفأرة س الموضع', + 'mouse y': + 'للفأرة ص الموضع', + 'mouse down?': + '؟ مضغوط الايسر الفأرة زر هل', + 'key %key pressed?': + '؟ مضغوط %key المفتاح هل', + 'distance to %dst': + ' %dst إلي المسـافة', + 'reset timer': + 'المؤقت تعيين إعـادة', + 'timer': + 'المؤقت', + '%att of %spr': + '؟ %att قيمة ما %spr للكائن', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + '؟ التوربو وضع في التشغيل هل', + 'set turbo mode to %b': + '%b التوربو وضـع تفعيل ', + 'filtered for %clr': + 'خلال هذه الفتره %clr gefiltert', + 'stack size': + 'Stapelgr\u00f6\u00dfe', + 'frames': + 'Rahmenz\u00e4hler', + + // operators: + '%n mod %n': + '%n للرقم %n علي القسمة باقي', + 'round %n': + 'صحيح لعدد %n قَرّب', + '%fun of %n': + '%fun قيمة إحسب %n للعدد', + 'pick random %n to %n': + ' %n و %n بين عشوائي عدد إختر', + '%b and %b': + '%b و %b', + '%b or %b': + '%b أو %b', + 'not %b': + 'ليس %b', + 'true': + ' صحيح ', + 'false': + ' خـطأً ', + 'join %words': + '%words يلي مـا أَوصـل ', + 'split %s by %delim': + '%s جـَزَّء ،كفواصل %delim بإستخدام', + 'hello': + 'مرحبا', + 'world': + 'ايها العالم', + 'letter %n of %s': + '%n الحرف أوجد %s العبارة من', + 'length of %s': + '%s أحرف عدد', + 'unicode of %s': + ' %s للحرف يونيكود ترميز قيمة', + 'unicode %n as letter': + ' %n يونيكود لترميز المقابل الحرف ', + 'is %s a %typ ?': + '%s يوافق %typ النوع', + 'is %s identical to %s ?': + '؟ %s مع متماثل %s هل', + 'type of %s': + 'من نوع %s', + + // variables: + 'Make a variable': + 'انشئ متغيرا', + 'Variable name': + 'اسم المتغير', + 'Script variable name': + 'اسم الكائن', + 'Delete a variable': + 'احذف متغيرا', + 'set %var to %s': + '%var للمتغيّر %s القيمة خصص', + 'change %var by %n': + ' %var المتغير قيمة %n بمقدار غيّـر', + 'show variable %var': + ' %var المُتَغيّر أظهـِر', + 'hide variable %var': + ' %var المُتَغيّر أخفِ', + 'script variables %scriptVars': + '%scriptVars مَحَلْي مُتَغَيِّر', + + // lists: + 'list %exp': + '%exp المصفوفة', + '%s in front of %l': + '%s ادرج %l بداية في', + 'item %idx of %l': + '%idx العنصر أظهـِر %l في', + 'all but first of %l': + 'الأول عـدا الكل أظهـِر %l في', + 'length of %l': + '%l عناصر عدد', + '%l contains %s': + '%l محتويات ضمن %s القيمة', + 'thing': + 'شيئ', + 'add %s to %l': + '%s القيمة %l في أَدْرِج', + 'delete %ida of %l': + '%ida العنصر احذف %l من', + 'insert %s at %idx of %l': + '%s القيمة %idx بالموضع %l في أَدْرِج', + 'replace item %idx of %l with %s': + ' %idx العنصر بدل %l المصفوفة في %s القيمة ضـع', + + // other + 'Make a block': + 'إنشاء لبنة مخصصة', + + // menus + // snap menu + 'About...': + 'عن SNAP!...', + 'Reference manual': + 'دليل التشغيل', + 'Snap! website': + 'الموقع الرسمي', + 'Download source': + 'تنزيل البرنامج', + 'Switch back to user mode': + 'التبديل الى وضع المستخدم', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'user-friendlyعرض القوائم ', + 'Switch to dev mode': + 'التبديل الي وضع المطورين', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'user-friendlyعرض القوائم تعطيل ', + + // project menu + 'Project notes...': + 'ملاحظات عن المشروع...', + 'New': + 'جديد', + 'Open...': + 'فتح...', + 'Save': + 'حفظ', + 'Save to disk': + 'حفظ في المستعرض', + 'store this project\nin the downloads folder\n(in supporting browsers)': + 'حفظ المشروع فى مجلد التنزيلات الخاص بالمتصفح المحدد', + 'Save As...': + 'حفظ بأسم ...', + 'Import...': + 'استيراد...', + 'file menu import hint': + 'استيراد مشروع تم تصديره من قبل', + 'Export project as plain text...': + 'تصدير المشروع كمستند نصي ...', + 'Export project...': + 'تصدير المشروع...', + 'show project data as XML\nin a new browser window': + 'عرض المشروع في صيغة XML', + 'Export blocks...': + 'تصدير اللبنات...', + 'show global custom block definitions as XML\nin a new browser window': + 'عرض جميع اللبنات المخصصة في صيغة XML', + + 'Unused blocks...': + 'لبنات غير مستخدمة...', + 'find unused global custom blocks\nand remove their definitions': + 'إيجاد اللبنات المخصصة الغير مستخدمة لحذفها من المشروع', + 'Remove unused blocks': + 'حذف اللبنات المخصصة الغير مستخدمة', + 'there are currently no unused\nglobal custom blocks in this project': + 'لايوجد لبنات مخصصة غير مستخدمة في هذا المشروع', + 'unused block(s) removed': + 'تم ازالة اللبنات الغير مستخدمة', + 'Export summary...': + 'تصدير ملخص المشروع...', + 'open a new browser browser window\n with a summary of this project': + 'عرض ملخص المشروع فى نافذة مستعرض جديدة', + 'Contents': + 'محتويات', + 'Kind of': + 'نوع من أنواع', + 'Part of': + 'جزء من', + 'Parts': + 'أجزاء', + 'Blocks': + 'بلوكات', + 'For all Sprites': + 'لكل الكائنات', + 'Import tools': + 'استيراد أدوات', + 'load the official library of\npowerful blocks': + 'تحميل مكتبة اللبنات الرسمية لمزيد من التحكم', + 'Libraries...': + 'المكتبات...', + 'Import library': + 'استيراد مكتبة', + + // cloud menu + 'Login...': + 'تسجيل دخول...', + 'Signup...': + 'تسجيل خروج...', + + // settings menu + 'Language...': + 'تغيير اللغة...', + 'Zoom blocks...': + 'التحكم فى حجم اللبنات...', + 'Stage size...': + 'مساحة المنصة...', + 'Stage width': + 'عرض المنصة', + 'Stage height': + 'ارتفاع المنصة', + 'Default': + 'افتراضى', + 'Blurred shadows': + 'ظلال شبه شفافة', + 'uncheck to use solid drop\nshadows and highlights': + 'ازل لاستخدام الظلال المعتمة', + 'check to use blurred drop\nshadows and highlights': + 'حدد لاستخدام الظلال الضبابية', + 'Zebra coloring': + 'تلوين ZEBRA', + 'check to enable alternating\ncolors for nested blocks': + 'حدد لتفعيل اختيار الوان\n متبادلة للبلوكات المتداخلة', + 'uncheck to disable alternating\ncolors for nested block': + 'ازل لعدم لتفعيل اختيار الوان\n متبادلة للبلوكات المتداخلة ', + 'Dynamic input labels': + 'بطاقات الادخال الديناميكية', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'ازل لالغاء تفعيل بطاقات الادخال الديناميكية للمدخلات', + 'check to enable dynamic\nlabels for variadic inputs': + 'حدد لتفعيل بطاقات الادخال الديناميكية للمدخلات', + 'Prefer empty slot drops': + 'Prefer empty slot drops', + 'settings menu prefer empty slots hint': + 'settings menu prefer empty slots hint', + 'uncheck to allow dropped\nreporters to kick out others': + 'uncheck to allow dropped\nreporters to kick out others', + 'Long form input dialog': + 'صندوق حوار تفصيلي لتعريف المدخلات', + 'Plain prototype labels': + 'تسميات عادية لنماذج البلوكات', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'ازل التحديد لاظهار (+) \n في تسمسة نموذج البلوك', + 'check to hide (+) symbols\nin block prototype labels': + 'حدد لأخفاء (+) \n من تسمسة نموذج البلوك', + 'check to always show slot\ntypes in the input dialog': + 'حدد حتي تظهر دائما\n تصنيف بيانات الادخال \n في صندوق حوار تعريف المدخلات', + 'uncheck to use the input\ndialog in short form': + 'ازل التحديد لاستخدام صندوق الحوار المبسط لتعريف المدخلات', + 'Virtual keyboard': + 'لوحة المفاتيح الافتراضية', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'ازل لالغاء تفعيل لوحة المفاتيح الافتراضية للاجهزة اللوحية', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'حدد لتفعيل لوحة المفاتيح الافتراضية للاجهزة اللوحية', + 'Input sliders': + 'ألواح الأدخال', + 'uncheck to disable\ninput sliders for\nentry fields': + 'أزل لالغاء\nالواح الادخال للحقول', + 'check to enable\ninput sliders for\nentry fields': + 'حدد لتفعيل\nالواح الادخال للحقول', + 'Clicking sound': + 'المؤثرات الصوتية', + 'uncheck to turn\nblock clicking\nsound off': + 'أزل لإيقاف\n أصوات النقر على اللبنات', + 'check to turn\nblock clicking\nsound on': + 'حدد لتفعيل\nأصوات النقر على اللبنات', + 'Animations': + 'مؤثرات حركية', + 'uncheck to disable\nIDE animations': + 'أزل لابطال مؤثرات IDE-\nالحركة', + 'Turbo mode': + 'الوضع السريع', + 'check to prioritize\nscript execution': + 'حدد لرفع درجة\n أولوية تنفيذ الاسكربت', + 'uncheck to run scripts\nat normal speed': + 'أزل, ليتم تنفيذ\n الاسكربت بالسرعة العادية', + 'check to enable\nIDE animations': + 'حدد لتشغيل المؤثرات IDE-\nالحركية', + 'Flat design': + 'تصميم مُصطَّح بسيط', + 'Keyboard Editing': + 'دعم لوحة المفاتيح', + 'Thread safe scripts': + 'تأمين الاسكربتات', + 'uncheck to allow\nscript reentrance': + 'أزل للسماح\n للاسكربت باعادة الدخول', + 'check to disallow\nscript reentrance': + 'حدد, لمنع\n الاسكربت من اعادة الدخول', + 'Prefer smooth animations': + 'الرسوم المتحركة على نحو سلس', + 'uncheck for greater speed\nat variable frame rates': + 'أزل للحصول علي سرعه اعلي\nمع معدلات تتبابع اطارات متغيره', + 'check for smooth, predictable\nanimations across computers': + 'حدد للحصول على\nحركة ناعمة', + 'Flat line ends': + 'نهايات الخطوط', + 'check for flat ends of lines': + 'حدد لجعل نهايات الخطوط\n قائمة الزاوية', + 'uncheck for round ends of lines': + 'أزل لجعل نهايات الخطوط \nدائرية', + 'Inheritance support': + ' توريث الخصائص بين الكائنات', + 'uncheck to disable\nsprite inheritance features': + 'أزل لألغاء تفعيل توريث الخصائص بين الكائنات', + 'check for sprite\ninheritance features': + 'حدد لتفعيل توريث الخصائص بين الكائنات', + + // inputs + 'with inputs': + 'مستخدماً القيم التالية', + 'input names:': + 'مع المدخلات', + 'Input Names:': + 'أسماء المدخلات', + 'input list:': + 'قائمة المدخلات', + + // context menus: + 'help': + 'مساعدة', + + // palette: + 'hide primitives': + 'أخفاء اللبنات', + 'show primitives': + 'إظهار اللبنات', + + // blocks: + 'help...': + 'مساعدة...', + 'relabel...': + 'اعادة تسمية...', + 'duplicate': + 'مضاعفة', + 'make a copy\nand pick it up': + 'اصنع نسخة و التقطها', + 'only duplicate this block': + 'ضاعف هذا البلوك فقط', + 'delete': + 'حذف', + 'script pic...': + 'صورة نقطية للبلوك...', + 'open a new window\nwith a picture of this script': + 'افتح نافذه جديده و اعرض النص البرمجي خلالها', + 'ringify': + 'احاطة', + 'unringify': + 'عدم احاطة', + + // custom blocks: + 'delete block definition...': + 'حذف تعريف البلوك', + 'edit...': + 'تحرير...', + + // sprites: + 'edit': + 'تحرير', + 'move': + 'تحرك', + 'detach from': + 'افصل عن', + 'detach all parts': + 'افصل كل الاجزاء', + 'export...': + 'تصدير...', + + // stage: + 'show all': + 'إظهار الكل', + 'pic...': + 'الصورة المصدره...', + 'open a new window\nwith a picture of the stage': + 'فتح نافذه جديده مع لقطه من المسرح', + + // scripting area + 'clean up': + 'محاذاة اللبنات', + 'arrange scripts\nvertically': + 'محاذا اللبنات عموديا', + 'add comment': + 'اضافة تعليق', + 'undrop': + 'تراجع عن الافلات', + 'undo the last\nblock drop\nin this pane': + 'تراجع عن الافلات الاخير للبلوك', + 'scripts pic...': + 'تصوير لقطة من الاسكربت...', + 'open a new window\nwith a picture of all scripts': + 'فتح نافذه جديده\n مع صورة لجميع الاسكربتات', + 'make a block...': + 'أنشئ لَبِـنَة جديدة...', + + // costumes + 'rename': + 'اعادة تسمية', + 'export': + 'تصدير', + 'rename costume': + 'اعاده تسمية', + + // sounds + 'Play sound': + 'شغل الصوت', + 'Stop sound': + 'اوقف الصوت', + 'Stop': + 'قف', + 'Play': + 'شغل', + 'rename sound': + 'اعد تسمية الصوت', + + // dialogs + // buttons + 'OK': + 'موافق', + 'Ok': + 'موافق', + 'Cancel': + 'الغاء الامر', + 'Yes': + 'نعم', + 'No': + 'لا', + + // help + 'Help': + 'مساعده', + + // zoom blocks + 'Zoom blocks': + 'حجم اللبنات', + 'build': + ' لَبِناتِك إصنع', + 'your own': + 'الخاصة', + 'blocks': + 'بنفسك', + 'normal (1x)': + 'عادي (1x)', + 'demo (1.2x)': + 'تجريبي (1.2x)', + 'presentation (1.4x)': + 'استعراضي (1.4x)', + 'big (2x)': + 'كبير (2x)', + 'huge (4x)': + 'ضخم (4x)', + 'giant (8x)': + 'عملاق (8x)', + 'monstrous (10x)': + 'عملاق جدا (10x)', + + // Project Manager + 'Untitled': + 'بدون عنوان', + 'Open Project': + 'فتح مشروع', + '(empty)': + '(فارغ)', + 'Saved!': + 'تم الحفظ!', + 'Delete Project': + 'حذف مشروع', + 'Are you sure you want to delete': + 'هل انت متأكد من رغبتك في الحذف?', + 'rename...': + 'اعاده تسميه...', + + // costume editor + 'Costume Editor': + 'محرر المظاهر', + 'click or drag crosshairs to move the rotation center': + 'انقر أو اسحب علامة المركز لنقل مركز دوران الكائن', + + // project notes + 'Project Notes': + 'ملاحظات المشروع', + + // new project + 'New Project': + 'مشروع جديد', + 'Replace the current project with a new one?': + 'استبدال المشروع الحالي بأخر جديد?', + + // save project + 'Save Project As...': + 'حفظ المشروع باسم...', + + // export blocks + 'Export blocks': + 'تصدير البلوكات', + 'Import blocks': + 'استيراد البلوكات', + 'this project doesn\'t have any\ncustom global blocks yet': + 'هذا المشروع لا يحتوى علي بلوكات مخصصة ', + 'select': + 'حدد', + 'none': + 'لا شيء', + + // variable dialog + 'for all sprites': + 'لجميع الكائنات', + 'for this sprite only': + 'لهذا الكائن فقط', + + // block dialog + 'Change block': + 'تغيير البلوك', + 'Command': + 'امر', + 'Reporter': + 'Reporter مُقَرِرات', + 'Predicate': + 'Predicate اسنادات تأكيدية', + + // block editor + 'Block Editor': + 'محرر البلوكات', + 'Apply': + 'طبق', + + // block deletion dialog + 'Delete Custom Block': + 'حذف بلوك مخصص', + 'block deletion dialog text': + 'هل تريد حقا حذف هذه الكتلة مع جميع النسخ منها', + + // input dialog + 'Create input name': + 'إنشاء تسمية لمُدخَل جديد', + 'Edit input name': + 'تعديل تسمية مُدخَل', + 'Edit label fragment': + 'Edit label fragment', + 'Title text': + 'نص العنوان', + 'Input name': + 'أسم المُدخَل', + 'Delete': + 'حذف', + 'Object': + 'كائن', + 'Number': + 'رقم', + 'Text': + 'نص', + 'List': + 'لائحة', + 'Any type': + 'اي نوع', + 'Boolean (T/F)': + 'منطقي (W/F)', + 'Command\n(inline)': + 'لبنة مستطيلة الشكل', + 'Command\n(C-shape)': + 'لبنة هلالية الشكل', + 'Any\n(unevaluated)': + 'أي نوع\n(خام لم يُقَيَّم)', + 'Boolean\n(unevaluated)': + 'منطقي\n(خام لم يُقَيَّم)', + 'Single input.': + 'ادخال مفرد.', + 'Default Value:': + 'القيمة الافتراضية:', + 'Multiple inputs (value is list of inputs)': + 'متعدد الادخالات (عبارة عن لائحة او مصفوفة من المدخلات)', + 'Upvar - make internal variable visible to caller': + 'Upvar - اجعل المتغيرات الداخلية مرئية بواسطة المُستَدعي', + + // About Snap + 'About Snap': + 'عن Snap', + 'Back...': + 'للخلف...', + 'License...': + 'الترخيص...', + 'Modules...': + 'البرمجه...', + 'Credits...': + 'الاشارات...', + 'Translators...': + 'المترجمون', + 'License': + 'الترخيص', + 'current module versions:': + 'الاصدار الحالي', + 'Contributors': + 'المساهمون', + 'Translations': + 'المترجمون', + + // variable watchers + 'normal': + 'عادي', + 'large': + 'كبير', + 'slider': + 'شريط التمرير', + 'slider min...': + 'ادني حد...', + 'slider max...': + 'اقصي حد...', + 'import...': + 'استيراد...', + 'Slider minimum value': + 'القيمة الصغري لشريط التمرير', + 'Slider maximum value': + 'القيمة العظمي لشريط التمرير', + + // list watchers + 'length: ': + 'الطول: ', + + // coments + 'add comment here...': + 'اضف تعليق هنا', + + // drow downs + // directions + '(90) right': + '(90) يمين', + '(-90) left': + '(-90) يسار', + '(0) up': + '(0) اعلي', + '(180) down': + '(180) اسفل', + + // collision detection + 'mouse-pointer': + 'مؤشر_الفأرة', + 'edge': + 'الحافة', + 'pen trails': + 'اثار_القلم', + + // costumes + 'Turtle': + 'السلحف', + 'Empty': + 'فارغ', + + // graphical effects + 'brightness': + 'التوهج', + 'ghost': + 'شبح', + 'negative': + 'معكوس', + 'comic': + 'كوميدي', + 'confetti': + 'تحول اللون', + + // keys + 'space': + 'المسافه', + 'up arrow': + 'السهم العلوي', + 'down arrow': + 'السهم السفلي', + 'right arrow': + 'السهم الايمن', + 'left arrow': + 'السهم الايسر', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'جديد...', + + // math functions + 'abs': + 'abs', + 'floor': + 'floor', + 'sqrt': + 'الجذر التربيعي', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // delimiters + 'letter': + 'الحروف', + 'whitespace': + 'الفراغات_البينية', + 'line': + 'علامات_الأسطر', + 'tab': + 'المسافات_البادئة', + 'cr': + 'أكتب_ما_تريد', + + // data types + 'number': + 'رقم', + 'text': + 'نص', + 'Boolean': + 'منطقي', + 'list': + 'مصفوفة', + 'command': + 'لبنات_اجرائية', + 'reporter': + 'لبنات_تقريرية', + 'predicate': + 'لبنات_تأكيدية', + + // list indices + 'last': + 'الاخير', + 'any': + 'أي موضع', + + + // miscellaneous + 'find blocks...': + 'البحث عن لبنة...', + + 'Reset Password...': + 'إعادة تعيين كلمة المرور', + + 'Codification support': + 'مساعد التكويد', + 'uncheck to disable\nblock to text mapping features': + 'أزل التحديد لألغاء مساعد التكويد', + 'check for block\nto text mapping features': + 'حَدد لتفعيل مساعد التكويد', + 'current %dates': + 'التاريخ الحالي %dates', + 'year':'سنة', + 'month':'شهر', + 'date':'يوم', + 'hour':'ساعة', + 'minute':'دقيقة', + 'second':'ثانية', + 'time in milliseconds': + 'ملي_ثانية', + 'day of week': + 'ترتيب_اليوم_في_الاسبوع', + + 'JavaScript function ( %mult%s ) { %code }': + ' ( %mult%s ) { %code } جافاسكربت دالة', + + + // Copy / Paste + 'Press CTRL+C one more time to effectively copy to clipboard.': + 'إضغط CTRL+C مرة أخري لتأكيد نسخ محتويات الحافظة.', + 'Press CTRL+V one more time to effectively paste from clipboard.': + 'إضغط CTRL+V مرة أخري لتأكيد لصق محتويات الحافظة.', + 'Press CTRL+X one more time to effectively cut to clipboard.': + 'إضغط CTRL+X مرة أخري لتأكيد لصق محتويات الحافظة.', + + // Paint.js + 'undo':'تراجع', + 'Paintbrush tool\n(free draw)': + 'اداة الرسم الحر', + 'Stroked Rectangle\n(shift: square)': + 'اداة رسم المستطيل', + 'Stroked Ellipse\n(shift: circle)': + 'اداة رسم الشكل البيضاوى', + 'Eraser tool': + 'اداة الممحاة', + 'Set the rotation center': + 'ضبط مركز الدوران', + 'Line tool\n(shift: vertical/horizontal)': + 'اداة رسم الخط المستقيم رأسيا أو أفقيا', + 'Filled Rectangle\n(shift: square)': + 'أداة رسم مستطيل ممتلئ بالون محدد', + 'Filled Ellipse\n(shift: circle)': + 'أداة رسم شكل بيضاوى ممتلئ بلون محدد', + 'Fill a region': + 'أداة الملئ بالون', + 'Pipette tool\n(pick a color anywhere)': + 'أداة إلتقاط الألوان', + 'grow':'تكبير', + 'shrink':'تصغير', + 'flip \u2194': + 'إنعكاس \u2194', + 'flip \u2195': + 'إنعكاس \u2195', + 'Brush size': + 'حجم الفرشـاة', + 'Constrain proportions of shapes?\n(you can also hold shift)': + 'تأمين نسبة الأرتفاع الي العرض?\n(يمكنك ايضا استخدام مفتاح Shift)' + + +}; diff --git a/lang-bg.js b/lang-bg.js new file mode 100644 index 00000000..c8c0372b --- /dev/null +++ b/lang-bg.js @@ -0,0 +1,1125 @@ +/* + + lang-bg.js + + Bulgarian translation for SNAP! + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.bg = { + +/* + Special characters: (see ) + + €, Š \u00c4, \u00e4 + +, š \u00d6, \u00f6 + †, Ÿ \u00dc, \u00fc + § \u00df +*/ + + // translations meta information + 'language_name': + 'Български', // the name as it should appear in the language menu + 'language_translator': + 'Иван Савов', // your name for the Translators tab + 'translator_e-mail': + 'ivan.savov@gmail.com', // optional + 'last_changed': + '2015-11-02', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'Без име', + 'development mode': + 'Режим за програмисти', + + // categories: + 'Motion': + 'Движение', + 'Looks': + 'Външност', + 'Sound': + 'Звуци', + 'Pen': + 'Молив', + 'Control': + 'Управление', + 'Sensing': + 'Сензори', + 'Operators': + 'Оператори', + 'Variables': + 'Променливи', + 'Lists': + 'Списъци', + 'Other': + 'Други', + + // editor: + 'draggable': + 'движимо', + + // tabs: + 'Scripts': + 'Скриптове', + 'Costumes': + 'Костюми', + 'Sounds': + 'Звуци', + + // names: + 'Sprite': + 'Спрайт', + 'Stage': + 'Сцена', + + // rotation styles: + 'don\'t rotate': + 'не се върти', + 'can rotate': + 'върти се', + 'only face left/right': + 'само ляво-дясно ориентация', + + // new sprite button: + 'add a new sprite': + 'Добави нов спрайт', + + // tab help + 'costumes tab help': + 'импортирай изображения от друг уеб-сайт\nили от твоя компютър пускайки ги тук', + 'import a sound from your computer\nby dragging it into here': + 'добави звуци от твоя компютър\nпускайки ги тук ', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Избрана сцена:\nняма блокове с движение', + + 'move %n steps': + 'напред с %n стъпки', + 'turn %clockwise %n degrees': + ' %clockwise с %n градуса', + 'turn %counterclockwise %n degrees': + 'завърти %counterclockwise с %n градуса', + 'point in direction %dir': + 'обърни се в посока %dir', + 'point towards %dst': + 'обърни се към %dst', + 'go to x: %n y: %n': + 'премини към x %n y %n', + 'go to %dst': + 'премини в точка %dst', + 'glide %n secs to x: %n y: %n': + 'плъзгане %n сек до x %n y %n', + 'change x by %n': + 'промени х с %n', + 'set x to %n': + 'настрой х на %n', + 'change y by %n': + 'промени y с %n', + 'set y to %n': + 'настрой y на %n', + 'if on edge, bounce': + 'ако е в края, отблъсни се', + 'x position': + 'x позиция', + 'y position': + 'y позиция', + 'direction': + 'посока', + + // looks: + 'switch to costume %cst': + 'смени костюм с %cst', + 'next costume': + 'следващия костюм', + 'costume #': + 'костюм №', + 'say %s for %n secs': + 'кажи %s за %n сек', + 'say %s': + 'кажи %s', + 'think %s for %n secs': + 'мисли %s за %n сек', + 'think %s': + 'мисли %s', + 'Hello!': + 'Здрасти!', + 'Hmm...': + 'Хмм...', + 'change %eff effect by %n': + 'смени %eff ефект с %n', + 'set %eff effect to %n': + 'настрой ефект %eff на %n', + 'clear graphic effects': + 'махни ефектите', + 'change size by %n': + 'промени размера с %n', + 'set size to %n %': + 'настрой размера на %n', + 'size': + 'размер', + 'show': + 'покажи', + 'hide': + 'скрий', + 'go to front': + 'премини най-отпред', + 'go back %n layers': + 'премини с %n слоя назад', + + 'development mode \ndebugging primitives:': + 'Режим за програмисти \nпримитиви за дебъгиране:', + 'console log %mult%s': + 'напиши в конзолата %mult%s', + 'alert %mult%s': + 'предупреждение %mult%s', + + // sound: + 'play sound %snd': + 'пусни звук %snd', + 'play sound %snd until done': + 'пусни звук %snd до край', + 'stop all sounds': + 'спри всички звуци', + 'rest for %n beats': + 'пауза за %n такта', + 'play note %n for %n beats': + 'пусни нота %n за %n такта', + 'change tempo by %n': + 'промени темпото с %n', + 'set tempo to %n bpm': + 'настрой темпо %n удара в мин.', + 'tempo': + 'темпо', + + // pen: + 'clear': + 'изчисти всичко', + 'pen down': + 'натисни молива', + 'pen up': + 'вдигни молива', + 'set pen color to %clr': + 'избери молив с цвят %clr', + 'change pen color by %n': + 'промени цвята на молива с %n', + 'set pen color to %n': + 'избери цвят %n', + 'change pen shade by %n': + 'промени яркостта с %n', + 'set pen shade to %n': + 'настрой яркостта на %n', + 'change pen size by %n': + 'промени размера с %n', + 'set pen size to %n': + 'ибери молив с размер %n', + 'stamp': + 'печатче', + + // control: + 'when %greenflag clicked': + 'когато %greenflag е кликнат', + 'when %keyHat key pressed': + 'когато бутон %keyHat е натиснат', + 'when I am clicked': + 'когато кликнеш върху мен', + 'when I receive %msgHat': + 'когато получа %msgHat', + 'broadcast %msg': + 'изпрати %msg към всички', + 'broadcast %msg and wait': + 'изпрати %msg към всички и изчакай', + 'Message name': + 'Име на съобщение', + 'wait %n secs': + 'изчакай %n сек', + 'wait until %b': + 'изчакай до %b', + 'forever %c': + 'завинаги %c', + 'repeat %n %c': + 'повтори %n %c', + 'repeat until %b %c': + 'повтори докато %b %c', + 'if %b %c': + 'ако %b %c', + 'if %b %c else %c': + 'ако %b %c иначе %c', + 'report %s': + 'резултат %s', + 'stop block': + 'спри блока', + 'stop script': + 'спри скрипта', + 'stop all %stop': + 'спри всичко %stop', + 'run %cmdRing %inputs': + 'изпълни %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'пусни %cmdRing %inputs', + 'call %repRing %inputs': + 'извикай %repRing %inputs', + 'run %cmdRing w/continuation': + 'изпълни %cmdRing с продължение', + 'call %cmdRing w/continuation': + 'извикай %cmdRing с продължение', + 'warp %c': + 'warp %c', + + // sensing: + 'touching %col ?': + 'допира ли %col ?', + 'touching %clr ?': + 'допира ли %clr ?', + 'color %clr is touching %clr ?': + 'цвят %clr допира ли %clr ?', + 'ask %s and wait': + 'попитай %s и изчакай', + 'what\'s your name?': + 'как се казваш?', + 'answer': + 'отговор', + 'mouse x': + 'мишка x-позиция', + 'mouse y': + 'мишка y-позиция', + 'mouse down?': + 'натиснат бутон на мишката?', + 'key %key pressed?': + 'бутон %key натиснат?', + 'distance to %dst': + 'растояние до %dst', + 'reset timer': + 'нулирай таймер', + 'timer': + 'таймер', + 'http:// %s': + 'http:// %s', + + 'filtered for %clr': + 'филтър за %clr', + 'stack size': + 'размер на стека', + 'frames': + 'рамки', + + // operators: + '%n mod %n': + '%n модул %n', + 'round %n': + 'закръгли %n', + '%fun of %n': + '%fun от %n', + 'pick random %n to %n': + 'произволно число между %n и %n', + '%b and %b': + '%b и %b', + '%b or %b': + '%b или %b', + 'not %b': + 'не %b', + 'true': + 'true', + 'false': + 'false', + 'join %words': + 'съедини %words', + 'hello': + 'здравейте', + 'world': + 'хора', + 'letter %n of %s': + 'буква %n от %s', + 'length of %s': + 'дължина на %s', + 'unicode of %s': + 'Unicode на %s', + 'unicode %n as letter': + 'буква с Unicode %n', + 'is %s a %typ ?': + '%s от тип %typ ли е ?', + 'is %s identical to %s ?': + '%s идентичен с %s ?', + + 'type of %s': + 'тип на %s', + + // variables: + 'Make a variable': + 'Направи променлива', + 'Variable name': + 'Име на променливата', + 'Delete a variable': + 'Изтрий променлива', + + 'set %var to %s': + 'настрой %var на стойност %s', + 'change %var by %n': + 'промени %var с %n', + 'show variable %var': + 'покажи променлива %var', + 'hide variable %var': + 'скрий променлива %var', + 'script variables %scriptVars': + 'променливи на скрипта %scriptVars', + + // lists: + 'list %exp': + 'списък %exp', + '%s in front of %l': + '%s пред %l', + 'item %idx of %l': + 'елемент %idx от %l', + 'all but first of %l': + 'всичко осрен първия от %l', + 'length of %l': + 'дължина на %l', + '%l contains %s': + '%l съдържа %s', + 'thing': + 'нещо', + 'add %s to %l': + 'добави %s към %l', + 'delete %ida of %l': + 'изтрий %ida от %l', + 'insert %s at %idx of %l': + 'вмъкни %s на позиция %idx в %l', + 'replace item %idx of %l with %s': + 'замести елемент %idx в %l с %s', + + // other + 'Make a block': + 'Нов блок', + + // menus + // snap menu + 'About...': + 'За Snap!', + 'Snap! website': + 'Уебсайт на Snap!', + 'Download source': + 'Издърпай програмния код', + 'Switch back to user mode': + 'Премини към режим на потребителя', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'изключи deep-Morphic\nконтекст меню', + 'Switch to dev mode': + 'Премини към режим за порграмисти', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'включи Morphic\nконтекст менюта\nи инспектори,\nмното сложно!', + + // project menu + 'Project notes...': + 'Записки по проекта...', + 'New': + 'Нов проект', + 'Open...': + 'Отвори...', + 'Save': + 'Запиши', + 'Save As...': + 'Запиши като...', + 'Import...': + 'Импорт...', + 'file menu import hint': + 'Зареди проект,\nблокова библиотека,\nспрайт или звук', + 'Export project as plain text...': + 'Експорт проекта како текст файл...', + 'Export project...': + 'Експорт на проект...', + 'show project data as XML\nin a new browser window': + 'Покажи XML данните на проекта\nв нов прозорец на браузъра', + 'Export blocks...': + 'Експорт на блокове...', + 'show global custom block definitions as XML\nin a new browser window': + 'Покажи XML дефинициите на custom блокове\nв нов прозорец на браузъра', + 'Import tools': + 'Импорт опции', + 'load the official library of\npowerful blocks': + 'Зареди официалната библиотеката от мощните блокове', + + // settings menu + 'Language...': + 'Език...', + 'Blurred shadows': + 'Размити сенки', + 'uncheck to use solid drop\nshadows and highlights': + 'откажи за да използваш плътни\nсенки и очертания', + 'check to use blurred drop\nshadows and highlights': + 'избери за да използваш плътни\nсенки и очертания', + 'Zebra coloring': + 'Зеброви цветове', + 'check to enable alternating\ncolors for nested blocks': + 'избери за да включиш\nалтениращи цветове за блоковете', + 'uncheck to disable alternating\ncolors for nested block': + 'откажи за да изключиш\nалтениращи цветове за блоковете', + 'Dynamic input labels': + 'Динамични входни етикети', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'откажи за да изключиш динамични входни етикети\nза входни с множество стйности', + 'check to enable dynamic\nlabels for variadic inputs': + 'избери за да използваш динамични входни етикети\nза входни с множество стйности', + 'Prefer empty slot drops': + 'Предпочиай несвързани блокове', + 'settings menu prefer empty slots hint': + 'избери и новите блокове ще\nотместват старите', + 'uncheck to allow dropped\nreporters to kick out others': + 'откажи за да позволиш новите блокове\nда изместват старите', + 'Long form input dialog': + 'Дълга форма за входни', + 'check to always show slot\ntypes in the input dialog': + 'избери за да са покаже типът\nна всички входните', + 'uncheck to use the input\ndialog in short form': + 'откажи за да използвап кратка форма\nза входни променливи', + 'Virtual keyboard': + 'Виртуална клавиатура', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'откажи за да изключиш виртуалната клавиатура', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'избери за да изпозваш виртуална\nклавиатура за мобилни устройства', + 'Input sliders': + 'Слайдери', + 'uncheck to disable\ninput sliders for\nentry fields': + 'откажи за да изключиш слайдерите\nза входни полета', + 'check to enable\ninput sliders for\nentry fields': + 'избери за да изпозваш слайдери\nза входни полета', + 'Clicking sound': + 'Звук на клик', + 'uncheck to turn\nblock clicking\nsound off': + 'откажи за да изключиш звука\nпри кликване върху блок', + 'check to turn\nblock clicking\nsound on': + 'избери за да включиш звука\nпри кликване върху блок', + 'Animations': + 'Aнимации', + 'uncheck to disable\nIDE animations': + 'откажи за да изключиш\nIDE aнимациите', + 'check to enable\nIDE animations': + 'избери за да включиш\nIDE aнимациите', + 'Thread safe scripts': + 'Thread safe скриптове', + 'uncheck to allow\nscript reentrancy': + 'откажи за да изключиш\nthread safe скриптове', + 'check to disallow\nscript reentrancy': + 'избери за да включиш\nthread safe скриптове', + + // inputs + 'with inputs': + 'с вход на данни', + 'input names:': + 'имена на входните данни:', + 'Input Names:': + 'Имена на входните данни:', + 'input list:': + 'Вход на списък:', + + // context menus: + 'help': + 'помощ', + + // blocks: + 'help...': + 'помощ...', + 'relabel...': + 'смени етикета...', + 'duplicate': + 'дупликация', + 'make a copy\nand pick it up': + 'копирай\nи вземи', + 'only duplicate this block': + 'копирай само този блок', + 'delete': + 'изтрий', + 'script pic...': + 'изображение на скрипта...', + 'open a new window\nwith a picture of this script': + 'отвори нов екран\n с изображение на скрипта', + 'ringify': + 'ringify', + 'unringify': + 'unringify', + + // custom blocks: + 'delete block definition...': + 'изтрий дефиницията на блока', + 'edit...': + 'редактирай...', + + // sprites: + 'edit': + 'редактирай', + 'export...': + 'експорт...', + + // stage: + 'show all': + 'почажи всичко', + + // scripting area + 'clean up': + 'разчисти', + 'arrange scripts\nvertically': + 'вертикално подреждане на скриптовере', + 'add comment': + 'добави коментар', + 'make a block...': + 'нов блок...', + + // costumes + 'rename': + 'Преименуване', + 'export': + 'Експорт', + 'rename costume': + 'Преименуване на костюм', + + // sounds + 'Play sound': + 'Пусни звука', + 'Stop sound': + 'Спри звука', + 'Stop': + 'Стоп', + 'Play': + 'Пусни', + 'rename sound': + 'Преименувай звука', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'Ok', + 'Cancel': + 'Отмени', + 'Yes': + 'Да', + 'No': + 'Не', + + // help + 'Help': + 'Помощ', + + // Project Manager + 'Untitled': + 'Без име', + 'Open Project': + 'Отвори Проект', + '(empty)': + '(празно)', + 'Saved!': + 'Записан!', + 'Delete Project': + 'Изтрий Проект', + 'Are you sure you want to delete': + 'Сирурен ли си че искаш да изтриеш?', + 'rename...': + 'Преименуване...', + + // costume editor + 'Costume Editor': + 'Редактор на Костюми', + 'click or drag crosshairs to move the rotation center': + 'кликни за да преместиш центра на ротацията', + + // project notes + 'Project Notes': + 'Записки по проекта', + + // new project + 'New Project': + 'Нов Проект', + 'Replace the current project with a new one?': + 'Замени проекта с нов?', + + // save project + 'Save Project As...': + 'Запиши проекта като...', + + // export blocks + 'Export blocks': + 'Експорт на блокове', + 'Import blocks': + 'Импорт на блокове', + 'this project doesn\'t have any\ncustom global blocks yet': + 'Този проект не съдъжа\nглобални custom\nблокове', + 'select': + 'избери', + 'all': + 'всичко', + 'none': + 'нищо', + + // variable dialog + 'for all sprites': + 'за вскички спрайтове', + 'for this sprite only': + 'само за този спрайт', + + // block dialog + 'Change block': + 'Замени блок', + 'Command': + 'Команда', + 'Reporter': + 'Репортер', + 'Predicate': + 'Предикат', + + // block editor + 'Block Editor': + 'Редактор на блокове', + 'Apply': + 'Приложи', + + // block deletion dialog + 'Delete Custom Block': + 'Изтрий custom блок', + 'block deletion dialog text': + 'Сигурен ли си че искаш да изтиреш този блок?', + + // input dialog + 'Create input name': + 'Направи нов вход с име', + 'Edit input name': + 'Редактирай име на вход', + 'Edit label fragment': + 'Редактирай текста етикет', + 'Title text': + 'Текст заглавие', + 'Input name': + 'Име на входа на данни', + 'Delete': + 'Изтрий', + 'Object': + 'Обект', + 'Number': + 'Число', + 'Text': + 'Tекст', + 'List': + 'Списък', + 'Any type': + 'Произволен тип', + 'Boolean (T/F)': + 'Булев (Т/F)', + 'Command\n(inline)': + 'Команда\n(inline)', + 'Command\n(C-shape)': + 'Команда\n(С-форма)', + 'Any\n(unevaluated)': + 'Произволен\n(unevaluated)', + 'Boolean\n(unevaluated)': + 'Булев\n(unevaluated)', + 'Single input.': + 'Единичен вход', + 'Default Value:': + 'Default стойност:', + 'Multiple inputs (value is list of inputs)': + 'Множество входни (спиък от данни)', + 'Upvar - make internal variable visible to caller': + 'Upvar - направи вътрешна променлива видима от извиквача', + + // About Snap + 'About Snap': + 'За Snap!', + 'Back...': + 'Назад...', + 'License...': + 'Лиценз...', + 'Modules...': + 'Модули...', + 'Credits...': + 'Кредити...', + 'Translators...': + 'Преводачи', + 'License': + 'Лиценз', + 'current module versions:': + 'Версии на модулие', + 'Contributors': + 'Участници', + 'Translations': + 'Преводи', + + // variable watchers + 'normal': + 'нормален', + 'large': + 'голям', + 'slider': + 'слайдер', + 'slider min...': + 'слайдер min...', + 'slider max...': + 'слайдер max...', + 'import...': + 'импорт...', + 'Slider minimum value': + 'Слайдер с min стойност', + 'Slider maximum value': + 'Слайдер с max стойност', + + // list watchers + 'length: ': + 'дължина: ', + + // coments + 'add comment here...': + 'добави коментар тук...', + + // drow downs + // directions + '(90) right': + '(90) надясно', + '(-90) left': + '(-90) наляво', + '(0) up': + '(0) нагоре', + '(180) down': + '(180) надолу', + + // collision detection + 'mouse-pointer': + 'курсор на мишката', + 'edge': + 'край', + 'pen trails': + 'линии след молива', + + // costumes + 'Turtle': + 'Костенурка', + + // graphical effects + 'ghost': + 'прозрачност', + + // keys + 'space': + 'интервал', + 'up arrow': + 'стрелка нагоре', + 'down arrow': + 'стрелка надолу', + 'right arrow': + 'стрелка надясно', + 'left arrow': + 'стрелка наляво', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'нов...', + + // math functions + 'abs': + 'абсолютна стойност', + 'sqrt': + 'корен квадратен', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // data types + 'number': + 'число', + 'text': + 'текст', + 'Boolean': + 'булев', + 'list': + 'списък', + 'command': + 'команда', + 'reporter': + 'репортер', + 'predicate': + 'предикат', + + // list indices + 'last': + 'последен', + 'any': + 'някой', + 'now connected': + 'конектиран', + 'undo': + 'въстанови' +}; diff --git a/lang-ca.js b/lang-ca.js index c57d0d24..71c71fde 100644 --- a/lang-ca.js +++ b/lang-ca.js @@ -6,7 +6,7 @@ written by Jens Mönig - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -181,11 +181,11 @@ SnapTranslator.dict.ca = { 'language_name': 'Català', // the name as it should appear in the language menu 'language_translator': - 'Bernat Romagosa Carrasquer', // your name for the Translators tab + 'Bernat Romagosa Carrasquer, Joan Guillén i Pelegay', // your name for the Translators tab 'translator_e-mail': - 'bromagosa@citilab.eu', // optional + 'bernat@arduino.org, jguille2@xtec.cat', // optional 'last_changed': - '2015-01-21', // this, too, will appear in the Translators tab + '2016-07-07', // this, too, will appear in the Translators tab // GUI // control bar: @@ -236,15 +236,15 @@ SnapTranslator.dict.ca = { // rotation styles: 'don\'t rotate': - 'no girar', + 'no gira', 'can rotate': 'pot girar', 'only face left/right': - 'només mirar esquerra/dreta', + 'només mira a esquerra/dreta', // new sprite button: 'add a new sprite': - 'afegir un nou objecte', + 'afegeix un nou objecte', // tab help 'costumes tab help': @@ -291,7 +291,7 @@ SnapTranslator.dict.ca = { + 'disponibles', 'move %n steps': - 'moure %n passos', + 'mou-te %n passos', 'turn %clockwise %n degrees': 'gira %clockwise %n graus', 'turn %counterclockwise %n degrees': @@ -411,14 +411,28 @@ SnapTranslator.dict.ca = { 'fixa la mida del llapis en %n', 'stamp': 'estampa', + 'fill': + 'omple', // control: 'when %greenflag clicked': 'Quan la %greenflag es premi', 'when %keyHat key pressed': 'Quan la tecla %keyHat es premi', - 'when I am clicked': - 'Quan es cliqui aquest personatge', + 'when I am %interaction': + 'Quan %interaction aquest personatge', + 'clicked': + 'es cliqui', + 'pressed': + 'es premi', + 'dropped': + 'es deixi anar', + 'mouse-entered': + 'el ratolí toqui', + 'mouse-departed': + 'el ratolí surti d\'', + 'when %b': + 'quan %b', 'when I receive %msgHat': 'Quan rebi %msgHat', 'broadcast %msg': @@ -484,7 +498,6 @@ SnapTranslator.dict.ca = { 'delete this clone': 'esborra aquest clon', - // sensing: 'touching %col ?': 'tocant %col ?', @@ -514,6 +527,8 @@ SnapTranslator.dict.ca = { 'cronòmetre', '%att of %spr': '%att de %spr', + 'my %get': + 'atribut %get', 'http:// %s': 'http:// %s', 'turbo mode?': @@ -560,26 +575,26 @@ SnapTranslator.dict.ca = { 'length of %s': 'longitud de %s', 'unicode of %s': - 'Valor Unicode de %s', + 'valor Unicode de %s', 'unicode %n as letter': - 'Lletra amb valor Unicode %n', + 'lletra amb valor Unicode %n', 'is %s a %typ ?': - 'És %s un %typ ?', + 'és %s un %typ ?', 'is %s identical to %s ?': - 'És %s idèntic a %s ?', + 'és %s idèntic a %s ?', 'type of %s': 'tipus de %s', // variables: 'Make a variable': - 'Crear una variable', + 'Crea una variable', 'Variable name': 'Nom de variable', 'Script variable name': 'Nom de la variable de programa', 'Delete a variable': - 'Esborrar una variable', + 'Esborra una variable', 'set %var to %s': 'assigna a %var el valor %s', @@ -596,7 +611,7 @@ SnapTranslator.dict.ca = { 'list %exp': 'llista %exp', '%s in front of %l': - '%s afegir davant de %l', + 'afegeix %s davant de %l', 'item %idx of %l': 'element %idx de %l', 'all but first of %l': @@ -629,15 +644,15 @@ SnapTranslator.dict.ca = { 'Snap! website': 'Web de Snap!', 'Download source': - 'Descarregar codi font', + 'Descarrega el codi font', 'Switch back to user mode': - 'Tornar a mode d\'usuari', + 'Torna a mode d\'usuari', 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': - 'canviar menús contextuals\nprimitius de Morphic\nper menús més amigables', + 'canvia els menús contextuals\nprimitius de Morphic\nper menús més amigables', 'Switch to dev mode': - 'Canviar a mode desenvolupador', + 'Canvia a mode desenvolupador', 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': - 'habilitar menús\ncontextuals de\nMorphic i inspectors,\nmode expert!', + 'habilita els menús\ncontextuals de\nMorphic i inspectors,\nmode expert!', // project menu 'Project notes...': @@ -645,43 +660,77 @@ SnapTranslator.dict.ca = { 'New': 'Nou', 'Open...': - 'Obrir...', + 'Obre...', 'Save': - 'Desar', + 'Desa', + 'Save to disk': + 'Desa al disc', + 'store this project\nin the downloads folder\n(in supporting browsers)': + 'desa aquest projecte\na la carpeta de descàrregues\n' + + '(en navegadors que ho suportin)', 'Save As...': - 'Desar com...', + 'Anomena i desa...', 'Import...': - 'Importar...', + 'Importa...', 'file menu import hint': 'carrega una llibreria de projecte\no de blocs exportada, un vestit\no un so', + + 'Export project as plain text...': - 'Exportar projecte en text pla...', + 'Exporta el projecte en text pla...', 'Export project...': - 'Exportar projecte...', + 'Exporta el projecte...', 'show project data as XML\nin a new browser window': - 'mostrar tot el projecte en format XML\nen una altra finestra del navegador', + 'mostra tot el projecte en format XML\nen una altra finestra del navegador', 'Export blocks...': - 'Exportar blocs...', + 'Exporta els blocs...', 'show global custom block definitions as XML\nin a new browser window': - 'mostrar definicions de blocs personalitzats\nen format XML en una altra finestra del\nnavegador', + 'mostra les definicions de blocs personalitzats\nen format XML en una altra finestra del\nnavegador', + 'Unused blocks...': + 'Blocs no utilitzats...', + 'find unused global custom blocks\nand remove their definitions': + 'busca blocs personalitzats globals\nno utilitzats i esborra\'ls', + 'Remove unused blocks': + 'Esborra blocs no utilitzats', + 'there are currently no unused\nglobal custom blocks in this project': + 'no hi ha cap bloc\npersonalitzat no utilitzat\nen aquest projecte', + 'unused block(s) removed': + 'bloc(s) personalitzats no utilitzats esborrats', + 'Export summary...': + 'Exporta el resum...', + 'open a new browser browser window\n with a summary of this project': + 'obre una finestra nova del navegador\namb un resum d\'aquest projecte', + + 'Contents': + 'Continguts', + 'Kind of': + 'Espècie de', + 'Part of': + 'Part de', + 'Parts': + 'Parts', + 'Blocks': + 'Blocs', + 'For all Sprites': + 'Per a tots els objectes', 'Import tools': - 'Importar eines', + 'Importa eines', 'load the official library of\npowerful blocks': - 'carregar la llibreria\noficial de blocs avançats', + 'carrega la llibreria\noficial de blocs avançats', 'Libraries...': 'Llibreries...', 'Import library': - 'Importar llibreria', + 'Importa una llibreria', // cloud menu 'Login...': - 'Iniciar sessió...', + 'Inicia la sessió...', 'Signup...': - 'Registrar-se...', + 'Registra\'t...', // settings menu 'Language...': - 'Idioma...', + 'Llengua...', 'Zoom blocks...': 'Mida dels blocs...', 'Stage size...': @@ -707,20 +756,22 @@ SnapTranslator.dict.ca = { 'uncheck to disable alternating\ncolors for nested block': 'desmarca\'m per deshabilitar la coloració\nalternada per a blocs imbricats', 'Dynamic input labels': - 'Etiquetes d\'entrada dinàmiques', + 'Etiquetes de camps d\'entrada dinàmics', 'uncheck to disable dynamic\nlabels for variadic inputs': - 'desmarca\'m per inhabilitar les\netiquetes dinàmiques d\'entrada de variables', + 'marca\'m per desactivar les\netiquetes dinàmiques en camps\namb aritat variable', 'check to enable dynamic\nlabels for variadic inputs': - 'marca\'m per habilitar les\netiquetes dinàmiques d\'entrada de variables', + 'marca\'m per habilitar les\netiquetes dinàmiques en camps\namb aritat variable', 'Prefer empty slot drops': 'Dóna preferència a les ranures buides', 'settings menu prefer empty slots hint': 'marca\'m per a fer que les ranures\nbuides tinguin preferència sobre les plenes\na l\'hora de deixar-hi caure peces', + 'uncheck to allow dropped\nreporters to kick out others': 'marca\'m per a fer que les ranures\nbuides tinguin la mateixa preferència que les\nplenes a l\'hora de deixar-hi caure peces', + 'Long form input dialog': - 'Forçar diàleg de selecció de tipus', - 'Plain prototype labels': + 'Força el diàleg de selecció de tipus', + 'Plain prototype labels': 'Etiquetes de prototip simples', 'uncheck to always show (+) symbols\nin block prototype labels': 'desmarca\'m per mostrar sempre el\nsímbol (+) en les etiquetes de prototip\nde bloc (a l\'editor de blocs)', @@ -734,8 +785,10 @@ SnapTranslator.dict.ca = { 'Teclat virtual', 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': 'desmarca\'m per inhabilitar\nel suport per al teclat virtual\nen dispositius mòbils', + 'check to enable\nvirtual keyboard support\nfor mobile devices': 'marca\'m per habilitar\nel suport per al teclat virtual\nen dispositius mòbils', + 'Input sliders': 'Lliscadors d\'entrada', 'uncheck to disable\ninput sliders for\nentry fields': @@ -760,6 +813,14 @@ SnapTranslator.dict.ca = { 'desmarca\'m per executar\nels programes a la velocitat\nnormal', 'check to enable\nIDE animations': 'marca\'m per habilitar\nles animacions de la interfície', + 'Flat design': + 'Disseny pla', + 'Keyboard Editing': + 'Edició per teclat', + 'Table support': + 'Edició de taules', + 'Table lines': + 'Línies de taules', 'Thread safe scripts': 'Fil d\'execució segur', 'uncheck to allow\nscript reentrance': @@ -778,6 +839,8 @@ SnapTranslator.dict.ca = { 'marca\'m per fer que els\nextrems de les línies del\nllapis siguin rectes', 'uncheck for round ends of lines': 'desmarca\'m per fer que\nels extrems de les línies\ndel llapis siguin arrodonits', + 'Inheritance support': + 'Suport per a herència', // inputs 'with inputs': @@ -795,15 +858,15 @@ SnapTranslator.dict.ca = { // palette: 'hide primitives': - 'amaga blocs primitius', + 'amaga els blocs primitius', 'show primitives': - 'mostra blocs primitius', + 'mostra els blocs primitius', // blocks: 'help...': 'ajuda...', 'relabel...': - 'canvia\'m el nom...', + 'blocs similars...', 'duplicate': 'duplica\'m', 'make a copy\nand pick it up': @@ -820,6 +883,12 @@ SnapTranslator.dict.ca = { 'encapsula\'m', 'unringify': 'des-encapsula\'m', + 'transient': + 'no persistent', + 'uncheck to save contents\nin the project': + 'desactiveu l\'opció per desar els continguts\nen el projecte', + 'check to prevent contents\nfrom being saved': + 'activeu l\'opció per evitar que els continguts\nes desin', // custom blocks: 'delete block definition...': @@ -829,21 +898,21 @@ SnapTranslator.dict.ca = { // sprites: 'edit': - 'editar', + 'edita', 'move': - 'moure', + 'mou', 'detach from': 'desenganxa de', 'detach all parts': 'desenganxa totes les parts', 'export...': - 'exportar...', + 'exporta...', // stage: 'show all': - 'mostrar-los tots', + 'mostra\'ls tots', 'pic...': - 'exportar com a imatge...', + 'exporta com a imatge...', 'open a new window\nwith a picture of the stage': 'obre una nova finestra\namb una foto de l\'escenari', @@ -859,7 +928,7 @@ SnapTranslator.dict.ca = { 'undo the last\nblock drop\nin this pane': 'recupera l\'últim bloc\nque s\'hagi llençat', 'scripts pic...': - 'exportar com a imatge...', + 'exporta com a imatge...', 'open a new window\nwith a picture of all scripts': 'obre una nova finestra\namb una foto d\'aquests programes', 'make a block...': @@ -885,6 +954,18 @@ SnapTranslator.dict.ca = { 'rename sound': 'canvia el nom del so', + // lists and tables + 'list view...': + 'vista en format de llista...', + 'table view...': + 'vista en format de taula...', + 'open in dialog...': + 'obre en una finestra...', + 'reset columns': + 'reinicialitza les columnes', + 'items': + 'elements', + // dialogs // buttons 'OK': @@ -929,14 +1010,14 @@ SnapTranslator.dict.ca = { // Project Manager 'Untitled': 'Sense títol', - 'Open Project': + 'Open un Project': 'Obre projecte', '(empty)': '(buit)', 'Saved!': 'Desat!', 'Delete Project': - 'Esborra projecte', + 'Esborra un projecte', 'Are you sure you want to delete': 'Segur que vols esborrar', 'rename...': @@ -960,7 +1041,7 @@ SnapTranslator.dict.ca = { // save project 'Save Project As...': - 'Anomena i desa projecte...', + 'Anomena i desa el projecte...', // export blocks 'Export blocks': @@ -970,7 +1051,7 @@ SnapTranslator.dict.ca = { 'this project doesn\'t have any\ncustom global blocks yet': 'aquest projecte encara no\nté cap bloc personalitzat', 'select': - 'seleccionar', + 'selecciona', 'none': 'cap bloc', @@ -998,17 +1079,18 @@ SnapTranslator.dict.ca = { // block deletion dialog 'Delete Custom Block': - 'Esborrar un bloc personalitzat', + 'Esborra el bloc personalitzat', 'block deletion dialog text': 'Segur que vols esborrar la definició\nd\'aquest bloc?', + // input dialog 'Create input name': - 'Crear ranura', + 'Crea una ranura', 'Edit input name': - 'Editar ranura', + 'Edita la ranura', 'Edit label fragment': - 'Editar fragment d\'etiqueta', + 'Edita el fragment d\'etiqueta', 'Title text': 'Text del títol', 'Input name': @@ -1060,7 +1142,7 @@ SnapTranslator.dict.ca = { 'License': 'Llicència', 'current module versions:': - 'versions del mòdul actual', + 'versions actuals dels mòduls', 'Contributors': 'Contribuïdors', 'Translations': @@ -1090,7 +1172,7 @@ SnapTranslator.dict.ca = { // coments 'add comment here...': - 'afegir un comentari aquí...', + 'afegeix un comentari aquí...', // drow downs // directions @@ -1132,6 +1214,8 @@ SnapTranslator.dict.ca = { // keys 'space': 'espai', + 'any key': + 'qualsevol tecla', 'up arrow': 'fletxa amunt', 'down arrow': @@ -1220,6 +1304,8 @@ SnapTranslator.dict.ca = { // math functions 'abs': 'valor absolut', + 'ceiling': + 'sostre', 'floor': 'part entera', 'sqrt': @@ -1273,5 +1359,36 @@ SnapTranslator.dict.ca = { 'last': 'últim', 'any': - 'qualsevol' + 'qualsevol', + + // attributes + 'neighbors': + 'veins', + 'self': + 'un mateix', + 'other sprites': + 'els altres objectes', + 'parts': + 'parts', + 'anchor': + 'àncora', + 'parent': + 'pare', + 'children': + 'fill', + 'clones': + 'clons', + 'other clones': + 'altres clons', + 'dangling?': + 'Baumeln?', + 'rotation x': + 'rotació x', + 'rotation y': + 'rotació y', + 'center x': + 'centre x', + 'center y': + 'centre y' + }; diff --git a/lang-cs.js b/lang-cs.js old mode 100644 new mode 100755 index 727fd68d..b452c557 --- a/lang-cs.js +++ b/lang-cs.js @@ -181,11 +181,11 @@ SnapTranslator.dict.cs = { 'language_name': 'Česky', // the name as it should appear in the language menu 'language_translator': - 'Michal Moc', // your name for the Translators tab + 'Michal Moc, Jan Tomsa', // your name for the Translators tab 'translator_e-mail': - 'info@iguru.eu', // optional + 'info@iguru.eu, jan.tomsa.1976@gmail.com', // optional 'last_changed': - '2013-03-011', // this, too, will appear in the Translators tab + '2015-11-16', // this, too, will appear in the Translators tab // GUI // control bar: @@ -245,6 +245,8 @@ SnapTranslator.dict.cs = { // new sprite button: 'add a new sprite': 'přidat nový sprite', + 'add a new Turtle sprite': + 'přidat nový sprite želvy', // tab help 'costumes tab help': @@ -416,8 +418,18 @@ SnapTranslator.dict.cs = { 'Po klepnutí na %greenflag', 'when %keyHat key pressed': 'po stisku klávesy %keyHat', - 'when I am clicked': - 'po kliknutí na', + 'when I am %interaction': + 'když %interaction', + 'clicked': + 'na mě kliknou', + 'pressed': + 'mě stisknou', + 'dropped': + 'mě upustí', + 'mouse-entered': + 'na mě najede myš', + 'mouse-departed': + 'ze mě sjede myš', 'when I receive %msgHat': 'po přijetí zprávy %msgHat', 'broadcast %msg': @@ -425,7 +437,11 @@ SnapTranslator.dict.cs = { 'broadcast %msg and wait': 'poslat všem %msg a čekat', 'Message name': - 'jméno zprávy', + 'název zprávy', + 'message': + 'zpráva', + 'any message': + 'jakákoli zpráva', 'wait %n secs': 'čekej %n sekund', 'wait until %b': @@ -435,19 +451,23 @@ SnapTranslator.dict.cs = { 'repeat %n %c': 'opakuj %n krát %c', 'repeat until %b %c': - 'opakuj dokud %b %c', + 'opakuj dokud nenastane %b %c', 'if %b %c': 'když %b %c', 'if %b %c else %c': 'když %b %c jinak %c', 'report %s': 'vrátit %s', - 'stop block': - 'zastav blok', - 'stop script': - 'zastav skript', - 'stop all %stop': - 'zastav vše %stop', + 'stop %stopChoices': + 'stop %stopChoices', + 'all': + 'vše', + 'this script': + 'tento skript', + 'this block': + 'tento blok', + 'stop %stopOthersChoices': + 'stop %stopOthersChoices', 'run %cmdRing %inputs': 'spusť %cmdRing %inputs', 'launch %cmdRing %inputs': @@ -455,17 +475,26 @@ SnapTranslator.dict.cs = { 'call %repRing %inputs': 'zavolat %repRing %inputs', 'run %cmdRing w/continuation': - 'spustit %cmdRing s podmínkou', + 'spustit %cmdRing s pokračováním', 'call %cmdRing w/continuation': - 'zavolat %cmdRing s podmínkou', + 'zavolat %cmdRing s pokračováním', 'warp %c': 'obal %c', 'when I start as a clone': 'začít po naklonování', 'create a clone of %cln': 'vytvořit klon %cln', + 'myself': + 'sama sebe', 'delete this clone': 'odstranit klon', + 'all but this script': + 'vše kromě tohoto skriptu', + 'other scripts in sprite': + 'ostatní skripty tohoto objektu', + 'pause all %pause': + 'zastavit vše %pause', + // sensing: 'touching %col ?': @@ -502,6 +531,16 @@ SnapTranslator.dict.cs = { 'turbo mód?', 'set turbo mode to %b': 'nastavit turbo mód na %b', + 'current %dates': + 'aktuální %dates', + 'year' : 'rok', + 'month' : 'měsíc', + 'date' : 'datum', + 'day of week' : 'den v týdnu', + 'hour' : 'hodina', + 'minute' : 'minuta', + 'second' : 'sekunda', + 'time in milliseconds' : 'čas v milisekundách', 'filtered for %clr': 'filtrovaný pro %clr', @@ -535,6 +574,8 @@ SnapTranslator.dict.cs = { 'ahoj', 'world': 'světe', + 'split %s by %delim': + 'rozděl %s podle %delim', 'letter %n of %s': 'písmeno %n z %s', 'length of %s': @@ -556,6 +597,8 @@ SnapTranslator.dict.cs = { 'Vytvoř proměnnou', 'Variable name': 'Jméno proměnné', + 'Script variable name': + 'Jméno skriptové proměnné', 'Delete a variable': 'Smaž proměnnou', @@ -568,19 +611,19 @@ SnapTranslator.dict.cs = { 'hide variable %var': 'schovej proměnnou %var', 'script variables %scriptVars': - 'Vytvoř seznam %scriptVars', + 'Vytvoř skriptové proměnné %scriptVars', // lists: 'list %exp': 'seznam %exp', '%s in front of %l': - '%s v popředí z %l', + '%s na začátek %l', 'item %idx of %l': - 'položka %idx ze %l', + 'položka %idx z %l', 'all but first of %l': - 'vše, ale první z %l', + 'vše kromě první položky z %l', 'length of %l': - 'velikost %l', + 'délka %l', '%l contains %s': '%l obsahuje %s', 'thing': @@ -590,9 +633,9 @@ SnapTranslator.dict.cs = { 'delete %ida of %l': 'smazat %ida z %l', 'insert %s at %idx of %l': - 'vložit %s na %idx v %l', + 'vložit %s na %idx pozici v %l', 'replace item %idx of %l with %s': - 'nahraď prvek %idx v %l za %s', + 'nahraď položku %idx v %l hodnotou %s', // other 'Make a block': @@ -647,10 +690,100 @@ SnapTranslator.dict.cs = { 'Importovat nástroje', 'load the official library of\npowerful blocks': 'nahraje oficialní knihovnu\npokročilých bloků', + 'Libraries...': + 'Knihovny...', + 'Import library': + 'Importovat knihovnu', + + 'Select a costume from the media library': + 'Vyberte kostým z knihovny médií', + 'Select a sound from the media library': + 'Vyberte si zvuk z knihovny médií', + // cloud menu + 'Login...': + 'Přihlásit...', + 'Signup...': + 'Vytvořit účet...', + 'Reset Password...': + 'Resetovat heslo...', + + 'Sign in': + 'Přihlásit se', + 'User name:': + 'Uživatelské jméno:', + 'Password:': + 'Heslo:', + 'stay signed in on this computer\nuntil logging out': + 'zůstaň přihlášen na tomto počítači\naž do odhlášení', + 'Reset password': + 'Resetuj heslo', + 'Sign up': + 'Vytvořit účet', + 'Birth date:': + 'Datum narození:', + 'year:': + 'rok:', + 'E-mail address:': + 'E-mailová adresa:', + 'E-mail address of parent or guardian:': + 'E-mailová adresa rodiče či opatrovníka:', + 'Terms of Service...': + 'Podmínky služby...', + 'Privacy...': + 'Politika soukromí...', + 'I have read and agree\nto the Terms of Service': + 'Četl jsem a souhlasím s podímkami služby', + 'January': + 'leden', + 'February': + 'únor', + 'March': + 'březen', + 'April': + 'duben', + 'May': + 'květen', + 'June': + 'červen', + 'July': + 'červenec', + 'August': + 'srpen', + 'September': + 'září', + 'October': + 'říjen', + 'November': + 'listopad', + 'December': + 'prosinec', + 'please fill out\nthis field': + 'prosím vyplňte\ntoto pole', + 'please agree to\nthe TOS': + 'prosím zaškrtněte souhlas\ns Podmínkami služby', + 'User name must be four\ncharacters or longer': + 'Uživatelské jméno musí být\ndlouhé alespoň čtyři znaky.', + 'please provide a valid\nemail address': + 'Zadejte, prosím, platnou emailovou adresu.', + 'password must be six\ncharacters or longer': + 'Heslo musí být dlouhé\nalespoň šest znaků.', + 'passwords do\nnot match': + 'Hesla se neshodují.', + // settings menu 'Language...': 'Jazyk...', + 'Zoom blocks...': + 'Velikost bloků...', + 'Stage size...': + 'Velikost scény...', + 'Stage size': + 'Velikost scény', + 'Stage width': + 'Šířka scény', + 'Stage height': + 'Výška scény', 'Blurred shadows': 'Měkké stíny', 'uncheck to use solid drop\nshadows and highlights': @@ -675,6 +808,12 @@ SnapTranslator.dict.cs = { 'Zaškrtnutím povolit zobrazování typů slotů ve vstupním dialogu', 'uncheck to use the input\ndialog in short form': 'odškrtnutí použije vstupní dialogy v krátké formě', + 'Plain prototype labels': + 'Prosté nadpisy prototypů', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'odškrtněte pro používání symbolů (+) v editoru bloků', + 'check to hide (+) symbols\nin block prototype labels': + 'zaškrtněte pro skrytí symbolů (+) v editoru bloků', 'Virtual keyboard': 'Virtuální klávesnice', 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': @@ -696,9 +835,9 @@ SnapTranslator.dict.cs = { 'zaškrtnutí zapne zvuk přicvaknutí bloku', 'Thread safe scripts': 'Vláknově bezpečné skripty', - 'uncheck to allow\nscript reentrancy': + 'uncheck to allow\nscript reentrance': 'odškrtnutí povolí více vláken', - 'check to disallow\nscript reentrancy': + 'check to disallow\nscript reentrance': 'zaškrtnutí zakáže více vláken', 'Turbo mode': 'Turbo mód', @@ -706,12 +845,42 @@ SnapTranslator.dict.cs = { 'odškrtnutí spustí skript\nnormální rychlostí', 'check to prioritize\nscript execution': 'zaškrtnutí spustí skripty\nzvýšenou rychlostí', + 'Flat design': + 'Plochý design', + 'check for alternative\nGUI design': + 'zaškrtněte pro alternativní design GUI', + 'uncheck for default\nGUI design': + 'odškrtněte pro výchozí design GUI', + 'Keyboard Editing': + 'Editace klávesnicí', + 'uncheck to disable\nkeyboard editing support': + 'odškrtněte pro vypnutí podpory editace klávesnicí', + 'check to enable\nkeyboard editing support': + 'zaškrtněte pro podporu editace klávesnicí', 'Prefer smooth animations': 'Zapnout plynulou animaci', 'uncheck for greater speed\nat variable frame rates': - 'odškrtnout pro vyšší rychlost', + 'odškrtněte pro vyšší rychlost', 'check for smooth, predictable\nanimations across computers': 'zaškrtněte pro plynulé, předvídatelné\nanimace napříč počítači', + 'Flat line ends': + 'Ploché konce čar', + 'check for flat ends of lines': + 'zaškrtněte pro ploché konce čar', + 'uncheck for round ends of lines': + 'odškrtněte pro zakulacené konce čar', + 'Codification support': + 'Podpora kodifikace', + 'uncheck to disable\nblock to text mapping features': + 'odškrtněte pro vypnutí funkcí\nmapování bloků na text', + 'check for block\nto text mapping features': + 'zaškrtněte pro funkce\nmapování bloků na text', + 'Inheritance support': + 'Podpora dědičnosti', + 'uncheck to disable\nsprite inheritance features': + 'odškrtněte pro vypnutí funkcí\ndědičnosti spritů', + 'check for sprite\ninheritance features': + 'zaškrtněte pro funkce\ndědičnosti spritů', // inputs 'with inputs': @@ -756,8 +925,12 @@ SnapTranslator.dict.cs = { // sprites: 'edit': 'upravit', + 'move': + 'přesunout', 'export...': 'export...', + 'paint a new sprite': + 'nakreslit nový sprite', // stage: 'show all': @@ -780,6 +953,8 @@ SnapTranslator.dict.cs = { 'exportovat', 'rename costume': 'přejmenovat kostým', + 'Paint a new costume': + 'Nakresli nový kostým', // sounds 'Play sound': @@ -810,6 +985,30 @@ SnapTranslator.dict.cs = { 'Help': 'Nápověda', + // zoom blocks + 'Zoom blocks': + 'Velikost bloků', + 'build': + 'vytvoř si', + 'your own': + 'své vlastní', + 'blocks': + 'bloky', + 'normal (1x)': + 'normální (1x)', + 'demo (1.2x)': + 'demo (1.2x)', + 'presentation (1.4x)': + 'prezentace (1.4x)', + 'big (2x)': + 'velké (2x)', + 'huge (4x)': + 'obrovské (4x)', + 'giant (8x)': + 'gigantické (8x)', + 'monstrous (10x)': + 'monstrózní (10x)', + // Project Manager 'Untitled': 'Nepojmenovaný', @@ -999,8 +1198,17 @@ SnapTranslator.dict.cs = { 'želva', // graphical effects + 'brightness': + 'jas', 'ghost': 'duch', + 'negative': + 'negativ', + 'comic': + 'moaré', + 'confetti': + 'barevnost', + // keys 'space': @@ -1093,6 +1301,10 @@ SnapTranslator.dict.cs = { // math functions 'abs': 'absolutní hodnota', + 'ceiling': + 'zaokrouhlit nahoru', + 'floor': + 'zaokrouhlit dolů', 'sqrt': 'odmocnina', 'sin': @@ -1132,5 +1344,5 @@ SnapTranslator.dict.cs = { 'last': 'poslední', 'any': - 'jakýkoliv' + 'kterákoli' }; diff --git a/lang-de.js b/lang-de.js index ce2b220e..65f54927 100644 --- a/lang-de.js +++ b/lang-de.js @@ -6,7 +6,7 @@ written by Jens Mönig - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -185,7 +185,7 @@ SnapTranslator.dict.de = { 'translator_e-mail': 'jens@moenig.org', // optional 'last_changed': - '2015-08-06', // this, too, will appear in the Translators tab + '2016-11-10', // this, too, will appear in the Translators tab // GUI // control bar: @@ -225,6 +225,8 @@ SnapTranslator.dict.de = { 'Skripte', 'Costumes': 'Kost\u00fcme', + 'Backgrounds': + 'Hintergr\u00fcnde', 'Sounds': 'Kl\u00e4nge', @@ -411,6 +413,8 @@ SnapTranslator.dict.de = { 'setze Stiftdicke auf %n', 'stamp': 'stemple', + 'fill': + 'male aus', // control: 'when %greenflag clicked': @@ -429,6 +433,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': @@ -523,6 +529,8 @@ SnapTranslator.dict.de = { 'Stoppuhr', '%att of %spr': '%att von %spr', + 'my %get': + 'Attribut %get', 'http:// %s': 'http:// %s', 'turbo mode?': @@ -680,6 +688,33 @@ SnapTranslator.dict.de = { 'Bl\u00f6cke exportieren...', 'show global custom block definitions as XML\nin a new browser window': 'zeigt globale Benutzerblockdefinitionen\nals XML im Browser an', + 'Unused blocks...': + 'Ungebrauchte Bl\u00f6cke...', + 'find unused global custom blocks\nand remove their definitions': + 'nicht verwendete Bl\u00f6cke finden\nund entfernen', + 'Remove unused blocks': + 'Ungebrauchte Bl\u00f6cke entfernen', + 'there are currently no unused\nglobal custom blocks in this project': + 'momentan keine nicht verwendeten\nBl\u00f6cke in diesem Projekt', + 'unused block(s) removed': + 'ungebrauchte Bl\u00f6cke entfernt', + 'Export summary...': + 'Zusammenfassung exportieren...', + 'open a new browser browser window\n with a summary of this project': + 'eine Zusammenfassung diese Projects\nin einem neuen Browserfenster' + + 'anzeigen', + 'Contents': + 'Inhalt', + 'Kind of': + 'Eine Art', + 'Part of': + 'Ein Teil von', + 'Parts': + 'Teile', + 'Blocks': + 'Bausteine', + 'For all Sprites': + 'Allen gemeinsam', 'Import tools': 'Tools laden', 'load the official library of\npowerful blocks': @@ -784,6 +819,12 @@ SnapTranslator.dict.de = { 'Helles Design', 'Keyboard Editing': 'Tastaturunterstützung', + 'Table support': + 'Tabellenunterstützung', + 'Table lines': + 'Tabellen mit Linien', + 'Visible stepping': + 'Programmausführung verfolgen', 'Thread safe scripts': 'Threadsicherheit', 'uncheck to allow\nscript reentrance': @@ -846,6 +887,12 @@ SnapTranslator.dict.de = { 'Umringen', 'unringify': 'Entringen', + 'transient': + 'nicht persistent', + 'uncheck to save contents\nin the project': + 'ausschalten, um den Inhalt\nim Projekt zu speichern', + 'check to prevent contents\nfrom being saved': + 'einschalten, um das Speichern des Inhalts\nim Projekt zu verhindern', // custom blocks: 'delete block definition...': @@ -911,6 +958,18 @@ SnapTranslator.dict.de = { 'rename sound': 'Klang umbenennen', + // lists and tables + 'list view...': + 'Listenansicht...', + 'table view...': + 'tabellarische Ansicht...', + 'open in dialog...': + 'in neuem Fenster \u00f6ffnen', + 'reset columns': + 'Spaltenbreiten zur\u00fccksetzen', + 'items': + 'Elemente', + // dialogs // buttons 'OK': @@ -1145,6 +1204,18 @@ SnapTranslator.dict.de = { 'Leer', // graphical effects + 'color': + 'Farbe', + 'fisheye': + 'Fischauge', + 'whirl': + 'Wirbel', + 'pixelate': + 'Pixel', + 'mosaic': + 'Mosaik', + 'saturation': + 'Sättigung', 'brightness': 'Helligeit', 'ghost': @@ -1167,6 +1238,8 @@ SnapTranslator.dict.de = { 'Pfeil nach rechts', 'left arrow': 'Pfeil nach links', + 'any key': + 'beliebige Taste', 'a': 'a', 'b': @@ -1247,6 +1320,8 @@ SnapTranslator.dict.de = { // math functions 'abs': 'Betrag', + 'ceiling': + 'Aufgerundet', 'floor': 'Abgerundet', 'sqrt': @@ -1295,10 +1370,46 @@ SnapTranslator.dict.de = { 'Funktionsblock', 'predicate': 'Pr\u00e4dikat', + 'sprite': + 'Objekt', // list indices 'last': 'letztes', 'any': - 'beliebiges' + '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', }; diff --git a/lang-et.js b/lang-et.js new file mode 100644 index 00000000..ad576911 --- /dev/null +++ b/lang-et.js @@ -0,0 +1,1579 @@ +/* + + lang-et.js + + Estonian translation for SNAP! + + written by Hasso Tepper + + Copyright (C) 2014 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.et = { + + // translations meta information + 'language_name': + 'Eesti', // the name as it should appear in the language menu + 'language_translator': + 'Hasso Tepper', // your name for the Translators tab + 'translator_e-mail': + 'hasso.tepper@gmail.com', // optional + 'last_changed': + '2016-03-01', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'Nimetu', + 'development mode': + 'Arendusrežiim', + + // categories: + 'Motion': + 'Liikumine', + 'Looks': + 'Välimus', + 'Sound': + 'Heli', + 'Pen': + 'Pliiats', + 'Control': + 'Juhtimine', + 'Sensing': + 'Taju', + 'Operators': + 'Operaatorid', + 'Variables': + 'Muutujad', + 'Lists': + 'Loendid', + 'Other': + 'Muud', + + // editor: + 'draggable': + 'Hiirega lohistatav', + + // tabs: + 'Scripts': + 'Skriptid', + 'Costumes': + 'Kostüümid', + 'Sounds': + 'Helid', + + // names: + 'Sprite': + 'Sprait', + 'Stage': + 'Taust', + + // rotation styles: + 'don\'t rotate': + 'Ei pöörle üldse', + 'can rotate': + 'Saab vabalt pöörelda', + 'only face left/right': + 'Saab osutada ainult paremale ja vasakule', + + // new sprite button: + 'add a new sprite': + 'Lisa uus sprait', + + // tab help + 'costumes tab help': + 'Pilte saad Snapi\'i lisada lohistades neid\n' + + 'oma arvutist või veebilehtedelt siia.', + 'import a sound from your computer\nby dragging it into here': + 'Helifaile saad Snap\'i lisada lohistades\n' + + 'neid oma arvutist siia.', + + // primitive blocks: + + // motion: + 'Stage selected:\nno motion primitives': + 'Hetkel on valitud taust\nja sellel liikumiskäske pole.', + + 'move %n steps': + 'liigu %n sammu', + 'turn %clockwise %n degrees': + 'pööra %clockwise %n kraadi', + 'turn %counterclockwise %n degrees': + 'pööra %counterclockwise %n kraadi', + 'point in direction %dir': + 'osuta suunda %dir', + 'point towards %dst': + 'osuta %dst suunas', + 'go to x: %n y: %n': + 'liigu punkti x: %n y: %n', + 'go to %dst': + 'liigu %dst juurde', + 'glide %n secs to x: %n y: %n': + 'liigu %n s punkti x: %n y: %n', + 'change x by %n': + 'muuda x väärtust %n võrra', + 'set x to %n': + 'määra x väärtuseks %n', + 'change y by %n': + 'muuda y väärtust %n võrra', + 'set y to %n': + 'määra y väärtuseks %n', + 'if on edge, bounce': + 'kui serval, põrka tagasi', + 'x position': + 'asukoht x-teljel', + 'y position': + 'asukoht y-teljel', + 'direction': + 'suund', + + // looks: + 'switch to costume %cst': + 'vaheta kostüümiks %cst', + 'next costume': + 'järgmine kostüüm', + 'costume #': + 'kostüümi nr', + 'say %s for %n secs': + 'ütle %s %n sekundit', + 'say %s': + 'ütle %s', + 'think %s for %n secs': + 'mõtle %s %n sekundit', + 'think %s': + 'mõtle %s', + 'Hello!': + 'Tere!', + 'Hmm...': + 'Hmm ...', + 'change %eff effect by %n': + 'muuda efekti %eff %n võrra', + 'set %eff effect to %n': + 'määra efekti %eff väärtuseks %n', + 'clear graphic effects': + 'eemalda graafikaefektid', + 'change size by %n': + 'muuda suurust %n võrra', + 'set size to %n %': + 'määra suuruseks %n %', + 'size': + 'suurus', + 'show': + 'näita', + 'hide': + 'peida', + 'go to front': + 'too ette', + 'go back %n layers': + 'vii %n kihti tahapoole', + + 'development mode \ndebugging primitives:': + 'Arendusrežiimi \nsilumismeetodid:', + 'console log %mult%s': + 'Kirjuta konsoolile: %mult%s', + 'alert %mult%s': + 'Popup dialoog: %mult%s', + + // sound: + 'play sound %snd': + 'mängi heli %snd', + 'play sound %snd until done': + 'mängi heli %snd lõpuni', + 'stop all sounds': + 'peata kõigi helide mängimine', + 'rest for %n beats': + 'paus %n lööki', + 'play note %n for %n beats': + 'noot %n %n lööki', + 'change tempo by %n': + 'muuda tempot %n võrra', + 'set tempo to %n bpm': + 'määra tempoks %n lööki/min', + 'tempo': + 'tempo', + + // pen: + 'clear': + 'puhasta', + 'pen down': + 'pliiats alla', + 'pen up': + 'pliiats üles', + 'set pen color to %clr': + 'määra pliiatsi värviks %clr', + 'change pen color by %n': + 'muuda pliiatsi värvi %n võrra', + 'set pen color to %n': + 'määra pliiatsi värviks %n', + 'change pen shade by %n': + 'muuta pliiatsi heledust %n võrra.', + 'set pen shade to %n': + 'määra pliiatsi heleduseks %n %', + 'change pen size by %n': + 'muuta pliiatsi jämedust %n võrra', + 'set pen size to %n': + 'määra pliiatsi jämeduseks %n', + 'stamp': + 'tempel', + 'fill': + 'täida värviga', + + // control: + 'when %greenflag clicked': + 'kui vajutatakse %greenflag', + 'when %keyHat key pressed': + 'kui vajutatakse klahvi %keyHat', + 'when I am %interaction': + 'kui minul %interaction', + 'clicked': + 'vajutatakse hiirega', + 'pressed': + 'vajutatakse klahvi', + 'dropped': + 'kukutatakse', + 'mouse-entered': + 'hiirekursor saabub', + 'mouse-departed': + 'hiirekursor lahkub', + 'when %b': + 'kui %b', + 'when I receive %msgHat': + 'kui saan teate %msgHat', + 'broadcast %msg': + 'saada teade %msg', + 'broadcast %msg and wait': + 'saada teade %msg ja oota', + 'Message name': + 'Teate nimi', + 'message': + 'teade', + 'any message': + 'Mistahes teade', + 'wait %n secs': + 'oota %n sekundit', + 'wait until %b': + 'oota kuni %b', + 'forever %c': + 'lõputult %c', + 'repeat %n %c': + 'korda %n korda %c', + 'repeat until %b %c': + 'korda kuni %b %c', + 'if %b %c': + 'kui %b %c', + 'if %b %c else %c': + 'kui %b %c vastasel juhul %c', + 'report %s': + 'tagasta %s', + 'stop %stopChoices': + 'lõpeta %stopChoices', + 'all': + 'kõik tööd', + 'this script': + 'selle skripti töö', + 'this block': + 'selle ploki töö', + 'stop %stopOthersChoices': + 'lõpeta %stopOthersChoices', + 'all but this script': + 'kõigi teiste skriptide töö', + 'other scripts in sprite': + 'kõigi selle spraidi teiste skriptide töö', + 'pause all %pause': + 'peata kõik %pause', + 'run %cmdRing %inputs': + 'käivita %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'käivita taustal %cmdRing %inputs', + 'call %repRing %inputs': + 'kutsu välja %repRing %inputs', + 'run %cmdRing w/continuation': + 'käivita jätkuga %cmdRing', + 'call %cmdRing w/continuation': + 'kutsu jätkuga välja %cmdRing', + 'warp %c': + 'warpkiirusega %c', + 'when I start as a clone': + 'kui alustan kloonina', + 'create a clone of %cln': + 'tekita %cln kloon', + 'myself': + 'minu', + 'delete this clone': + 'kustuta see kloon', + + // sensing: + 'touching %col ?': + 'kas puudutab objekti %col ?', + 'touching %clr ?': + 'kas puudutab värvi %clr ?', + 'color %clr is touching %clr ?': + 'kas värv %clr puudutab värvi %clr ?', + 'ask %s and wait': + 'küsi %s ja oota', + 'what\'s your name?': + 'Mis su nimi on?', + 'answer': + 'vastus', + 'mouse x': + 'hiire asukoht x-teljel', + 'mouse y': + 'hiire asukoht y-teljel', + 'mouse down?': + 'kas hiire nupp on all?', + 'key %key pressed?': + 'kas klahv %key on all?', + 'distance to %dst': + 'kaugus objektini %dst', + 'reset timer': + 'nulli taimer', + 'timer': + 'taimer', + '%att of %spr': + '%att kostüümil %spr', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'turborežiim?', + 'set turbo mode to %b': + 'määra turborežiimi väärtuseks %b', + + 'filtered for %clr': + 'filtered for %clr', + 'stack size': + 'pinu suurus', + 'frames': + 'frames', + + // operators: + '%n mod %n': + '%n / %n jääk', + 'round %n': + 'ümardatud %n', + '%fun of %n': + '%fun %n', + 'pick random %n to %n': + 'juhuslik arv %n ja %n vahel', + '%b and %b': + '%b ja %b', + '%b or %b': + '%b või %b', + 'not %b': + 'pole %b', + 'true': + 'tõene', + 'false': + 'väär', + 'join %words': + 'ühendatud %words', + 'split %s by %delim': + 'tükeldatud %s kohalt %delim', + 'hello': + 'Tere', + 'world': + 'maailm', + 'letter %n of %s': + 'sümbol nr %n tekstis %s', + 'length of %s': + 'teksti %s pikkus', + 'unicode of %s': + '%s Unicode', + 'unicode %n as letter': + 'Unicode %n sümbol', + 'is %s a %typ ?': + 'on %s %typ ?', + 'is %s identical to %s ?': + 'on %s sama kui %s ?', + + 'type of %s': + '%s tüüp', + + // variables: + 'Make a variable': + 'Uus muutuja', + 'Variable name': + 'Muutuja nimi', + 'Script variable name': + 'Skriptimuutuja nimi', + 'Delete a variable': + 'Kustuta muutuja', + + 'set %var to %s': + 'aseta muutujasse %var väärtus %s', + 'change %var by %n': + 'muuda muutujat %var %n võrra', + 'show variable %var': + 'näita muutujat %var', + 'hide variable %var': + 'peida muutuja %var', + 'script variables %scriptVars': + 'skriptimuutujad %scriptVars', + + // lists: + 'list %exp': + 'loend %exp', + '%s in front of %l': + '%s loendi %l algusesse', + 'item %idx of %l': + '%idx kirje loendis %l', + 'all but first of %l': + 'kõik kirjed peale esimese loendis %l', + 'length of %l': + 'loendi %l pikkus', + '%l contains %s': + '%l sisaldab %s', + 'thing': + 'midagi', + 'add %s to %l': + 'lisa %s loendisse %l', + 'delete %ida of %l': + 'kustuta %ida kirje(d) loendist %l', + 'insert %s at %idx of %l': + 'lisa %s asukohta %idx loendis %l', + 'replace item %idx of %l with %s': + 'asenda %idx kirje loendis %l väärtusega %s', + + // other + 'Make a block': + 'Uus plokk', + + // menus + // snap menu + 'About...': + 'Snap! info ...', + 'Reference manual': + 'Reference manual', + 'Snap! website': + 'Snap! koduleht', + 'Download source': + 'Laadi lähtekood alla', + 'Switch back to user mode': + 'Tagasi kasutajarežiimi', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'Lülitab Morphic kontekstmenüüd välja,\net kasutajasõbralikke näidata.', + 'Switch to dev mode': + 'Lülitu arendusrežiimi', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'Lülitab sisse Morphic kontekstmenüüd\nja inspektorid. See pole kasutajale.', + + // project menu + 'Project notes...': + 'Projekti märkmed ...', + 'New': + 'Uus', + 'Open...': + 'Ava ...', + 'Save': + 'Salvesta', + 'Save to disk': + 'Salvesta kettale', + 'store this project\nin the downloads folder\n(in supporting browsers)': + 'Salvestab selle projekti\nallalaadimiste kausta\n(kui brauser seda toetab).\n', + 'Save As...': + 'Salvesta kui ...', + 'Import...': + 'Import ...', + 'file menu import hint': + 'Eksporditud projekti,\nplokkide teegi, kostüümi\nvõi heli laadimine.', + 'Export project as plain text...': + 'Ekspordi projekt tavatekstiks ...', + 'Export project...': + 'Ekspordi projekt ...', + 'show project data as XML\nin a new browser window': + 'Näitab uues brauseri aknas\nXML vormingus projekti.', + 'Export blocks...': + 'Ekspordi plokid ...', + 'show global custom block definitions as XML\nin a new browser window': + 'Näitab uues brauseri aknas XML vormingus\nkasutaja loodud plokkide definitsioone.', + 'Unused blocks...': + 'Kasutamata plokid ...', + 'find unused global custom blocks\nand remove their definitions': + 'Otsib projektist üles kasutamata kasutaja loodud\nplokid ja eemaldab nende definitsioonid.', + 'Remove unused blocks': + 'Eemalda kasutamata plokid', + 'there are currently no unused\nglobal custom blocks in this project': + 'Selles projektis pole hetkel\nkasutaja loodud kasutamata plokke.', + 'unused block(s) removed': + 'Kasutamata plokid on eemaldatud.', + 'Export summary...': + 'Ekspordi kokkuvõte ...', + 'open a new browser browser window\n with a summary of this project': + 'Avab uue brauseriakna selle\nprojekti kokkuvõttega.', + 'Contents': + 'Sisu', + 'Kind of': + 'Kind of', + 'Part of': + 'Part of', + 'Parts': + 'Osad', + 'Blocks': + 'Plokid', + 'For all Sprites': + 'Kõikidele spraitidele', + 'Import tools': + 'Impordi tööriistad', + 'load the official library of\npowerful blocks': + 'Laadib juurde ametliku võimalusterohke\nplokkide teegi.', + 'Libraries...': + 'Teegid ...', + 'Import library': + 'Impordi teek', + + // cloud menu + 'Login...': + 'Logi sisse ...', + 'Signup...': + 'Tekita konto ...', + + // settings menu + 'Language...': + 'Keel ...', + 'Zoom blocks...': + 'Plokkide suurendus ...', + 'Stage size...': + 'Lava suurus ...', + 'Stage size': + 'Lava suurus', + 'Stage width': + 'Lava laius', + 'Stage height': + 'Lava kõrgus', + 'Default': + 'Vaikeväärtus', + 'Blurred shadows': + 'Udused varjud', + 'uncheck to use solid drop\nshadows and highlights': + 'Konkreetsete piirete kasutamiseks varjudel ja\nesiletõstudel lülita see välja.', + 'check to use blurred drop\nshadows and highlights': + 'Uduste piirete kasutamiseks varjudel ja\nesiletõstudel lülita see sisse.', + 'Zebra coloring': + 'Sebravärvid', + 'check to enable alternating\ncolors for nested blocks': + 'check to enable alternating\ncolors for nested blocks', + 'uncheck to disable alternating\ncolors for nested block': + 'uncheck to disable alternating\ncolors for nested block', + 'Dynamic input labels': + 'Dynamic input labels', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'uncheck to disable dynamic\nlabels for variadic inputs', + 'check to enable dynamic\nlabels for variadic inputs': + 'check to enable dynamic\nlabels for variadic inputs', + 'Prefer empty slot drops': + 'Tühjade pesade eelistamine', + 'settings menu prefer empty slots hint': + 'Funktsioonide lohistamisel ja\nkukutamisel tühjadele pesadele\nkeskendumiseks lülita see sisse.', + 'uncheck to allow dropped\nreporters to kick out others': + 'Et lubada kukutatavatel\nfunktsioonidel teisi oma kohalt\nvälja lüüa, lülita see välja.', + 'Long form input dialog': + 'Long form input dialog', + 'Plain prototype labels': + 'Plain prototype labels', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'uncheck to always show (+) symbols\nin block prototype labels', + 'check to hide (+) symbols\nin block prototype labels': + 'check to hide (+) symbols\nin block prototype labels', + 'check to always show slot\ntypes in the input dialog': + 'check to always show slot\ntypes in the input dialog', + 'uncheck to use the input\ndialog in short form': + 'uncheck to use the input\ndialog in short form', + 'Virtual keyboard': + 'Virtuaalne klaviatuur', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'Mobiilsetel seadmetel virtuaalse\nklaviatuuri kasutamise keelamiseks\nlülita see välja.', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'Mobiilsetel seadmetel virtuaalse\nklaviatuuri kasutamiseks lülita\nsee sisse.', + 'Input sliders': + 'Liugurid sisenditel', + 'uncheck to disable\ninput sliders for\nentry fields': + 'uncheck to disable\ninput sliders for\nentry fields', + 'check to enable\ninput sliders for\nentry fields': + 'check to enable\ninput sliders for\nentry fields', + 'Clicking sound': + 'Klõpsuv heli', + 'uncheck to turn\nblock clicking\nsound off': + 'Plokkide ühendamisel tekkiva\nklõpsuva heli vaigistamiseks\nlülita see välja.', + 'check to turn\nblock clicking\nsound on': + 'Plokkide ühendamisel tekkiva\nklõpsuva heli kuulmiseks\nlülita see sisse.', + 'Animations': + 'Animatsioonid', + 'uncheck to disable\nIDE animations': + 'IDE animatsioonide keelamiseks\nlülita see välja.', + 'Turbo mode': + 'Turbokiirus', + 'check to prioritize\nscript execution': + 'Skriptide jooksutamise eelistamiseks\nlülita see sisse.', + 'uncheck to run scripts\nat normal speed': + 'Skriptide normaalkiirusel jooksutamiseks\nlülita see välja.', + 'check to enable\nIDE animations': + 'IDE-s animatsioonide kasutamiseks\nlülita see sisse.', + 'Flat design': + 'Lame kasutajaliides', + 'Keyboard Editing': + 'Klaviatuurilt muudatuste tegemise tugi', + 'Table support': + 'Tabelite tugi', + 'Table lines': + 'Jooned tabelitel', + 'Thread safe scripts': + 'Skriptide ohutu käivitamine', + 'uncheck to allow\nscript reentrance': + 'Skriptide töö katkestamise ja jätkamise\n(reentrance) lubamiseks lülita see välja.', + 'check to disallow\nscript reentrance': + 'Skriptide töö katkestamise ja jätkamise\n(reentrance) keelamiseks lülita see sisse.', + 'Prefer smooth animations': + 'Sujuvate animatsioonide eelistamine', + 'uncheck for greater speed\nat variable frame rates': + 'Parema kiiruse, kuid kõikuva kaadrisageduse\nkasutamiseks lülita see sisse.', + 'check for smooth, predictable\nanimations across computers': + 'Kui soovid, et animatsioonid oleks kõigil\nplatvormidel sujuvad, lülita see sisse.', + 'Flat line ends': + 'Sirged jooneotsad', + 'check for flat ends of lines': + 'Sirgete jooneotste tekitamiseks lülita see sisse.', + 'uncheck for round ends of lines': + 'Ümarate jooneotste tekitamiseks lülita see välja.', + 'Inheritance support': + 'Pärimise tugi', + + // inputs + 'with inputs': + 'with inputs', + 'input names:': + 'input names:', + 'Input Names:': + 'Input Names:', + 'input list:': + 'input list:', + + // context menus: + 'help': + 'Abi', + + // palette: + 'hide primitives': + 'Peida primitiivid', + 'show primitives': + 'Näita primitiive', + + // blocks: + 'help...': + 'Abi ...', + 'relabel...': + 'Nimeta ümber ...', + 'duplicate': + 'Kopeeri', + 'make a copy\nand pick it up': + 'Tekitab koopia ja korjab selle üles', + 'only duplicate this block': + 'Kopeerib ainult selle ploki', + 'delete': + 'Kustuta', + 'script pic...': + 'Skripti pilt ...', + 'open a new window\nwith a picture of this script': + 'Avab uue akna vaid selle skripti pildiga', + 'ringify': + 'Ümbritse ringiga', + 'unringify': + 'Eemalda ring', + 'transient': + 'Ajutine', + 'uncheck to save contents\nin the project': + 'Sisu projekti salvestamiseks lülita see välja.', + 'check to prevent contents\nfrom being saved': + 'Sisu projekti mitte salvestamiseks lülita see sisse.', + + // custom blocks: + 'delete block definition...': + 'Kustuta ploki definitsioon ...', + 'edit...': + 'Muuda ...', + + // sprites: + 'edit': + 'Muuda', + 'move': + 'Liiguta', + 'detach from': + 'Eralda', + 'detach all parts': + 'Eralda kõik osad', + 'export...': + 'Eksport ...', + + // stage: + 'show all': + 'Näita kõiki', + 'pic...': + 'Pilt ...', + 'open a new window\nwith a picture of the stage': + 'Avab uues aknas lava pildi.', + + // scripting area + 'clean up': + 'Puhasta', + 'arrange scripts\nvertically': + 'Paiguta skriptid vertikaalselt', + 'add comment': + 'Lisa kommentaar', + 'undrop': + 'Tõsta üles', + 'undo the last\nblock drop\nin this pane': + 'Tõstab viimase asetatud\nploki uuesti üles.', + 'scripts pic...': + 'Skriptide pilt ...', + 'open a new window\nwith a picture of all scripts': + 'Avab uues aknas pildi\nkõigi skriptidega.', + 'make a block...': + 'Tekita uus plokk ...', + + // costumes + 'rename': + 'Nimeta ümber', + 'export': + 'Eksport', + 'rename costume': + 'Kostüümi ümbernimetamine', + + // sounds + 'Play sound': + 'Helifaili esitamine', + 'Stop sound': + 'Esitamise peatamine', + 'Stop': + 'Peata', + 'Play': + 'Esita', + 'rename sound': + 'Nimeta ümber', + + // lists and tables + 'list view...': + 'Nimekirja vaade ...', + 'table view...': + 'Tabeli vaade ...', + 'open in dialog...': + 'Ava dialoogis ...', + 'reset columns': + 'Lähtesta veerud', + 'items': + 'Elemendid', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'OK', + 'Cancel': + 'Loobu', + 'Yes': + 'Jah', + 'No': + 'Ei', + + // help + 'Help': + 'Abi', + + // zoom blocks + 'Zoom blocks': + 'Plokkide suurendus', + 'build': + 'ehita', + 'your own': + 'ise oma', + 'blocks': + 'plokid', + 'normal (1x)': + 'normaalsed (1x)', + 'demo (1.2x)': + 'demo (1.2x)', + 'presentation (1.4x)': + 'esitus (1.4x)', + 'big (2x)': + 'suured (2x)', + 'huge (4x)': + 'väga suured (4x)', + 'giant (8x)': + 'hiiglaslikud (8x)', + 'monstrous (10x)': + 'koletud (10x)', + + // Project Manager + 'Untitled': + 'Nimetu', + 'Open Project': + 'Ava projekt', + '(empty)': + '(tühi)', + 'Saved!': + 'Salvestatud.', + 'Delete Project': + 'Kustuta projekt', + 'Are you sure you want to delete': + 'Oled kindel, et soovid projekti kustutada?', + 'rename...': + 'Nimeta ümber ...', + + // costume editor + 'Costume Editor': + 'Kostüümi redaktor', + 'click or drag crosshairs to move the rotation center': + 'Pöörlemiskeskme muutmiseks lohista risti.', + + // project notes + 'Project Notes': + 'Projekti märkmed', + + // new project + 'New Project': + 'Uus projekt', + 'Replace the current project with a new one?': + 'Kas asendada see projekt uuega?', + + // save project + 'Save Project As...': + 'Salvesta projekt kui ...', + + // export blocks + 'Export blocks': + 'Plokkide eksport', + 'Import blocks': + 'Plokkide import', + 'this project doesn\'t have any\ncustom global blocks yet': + 'Selles projektis pole veel ühtegi omaloodud plokki.', + 'select': + 'select', + 'none': + 'none', + + // variable dialog + 'for all sprites': + 'Kõigile spraitidele', + 'for this sprite only': + 'Ainult sellele spraidile', + + // block dialog + 'Change block': + 'Ploki muutmine', + 'Command': + 'Käsk', + 'Reporter': + 'Funktsioon', + 'Predicate': + 'Predikaat', + + // block editor + 'Block Editor': + 'Ploki redaktor', + 'Apply': + 'Rakenda', + + // block deletion dialog + 'Delete Custom Block': + 'Kustuta omaloodud plokk', + 'block deletion dialog text': + 'Kas kustutada see plokk ja kõik selle koopiad?', + + + // input dialog + 'Create input name': + 'Argumendi loomine', + 'Edit input name': + 'Argumendi muutmine', + 'Edit label fragment': + 'Ploki fragmendi muutmine', + 'Title text': + 'Nimi', + 'Input name': + 'Argument', + 'Delete': + 'Kustuta', + 'Object': + 'Objekt', + 'Number': + 'Arv', + 'Text': + 'Tekst', + 'List': + 'Loend', + 'Any type': + 'Mistahes tüüpi', + 'Boolean (T/F)': + 'Tõeväärtus', + 'Command\n(inline)': + 'Käsuplokk', + 'Command\n(C-shape)': + 'Käsuplokk\n(C-kujuline)', + 'Any\n(unevaluated)': + 'Mistahes tüüp\n(ei arvestata)', + 'Boolean\n(unevaluated)': + 'Tõeväärtus\n(eo arvestata)', + 'Single input.': + 'Üksik sisend.', + 'Default Value:': + 'Vaikeväärtus:', + 'Multiple inputs (value is list of inputs)': + 'Mitu sisendit (sisendite loend)', + 'Upvar - make internal variable visible to caller': + 'Sisemine muutuja tehakse väljakutsujale nähtavaks', + + // About Snap + 'About Snap': + 'Snap! info', + 'Back...': + 'Tagasi ...', + 'License...': + 'Litsents ...', + 'Modules...': + 'Moodulid ...', + 'Credits...': + 'Tänud ...', + 'Translators...': + 'Tõlkijad ...', + 'License': + 'Litsents', + 'current module versions:': + 'Hetkel kasutusel olevad moodulid:', + 'Contributors': + 'Kaastööd teinud', + 'Translations': + 'Tõlkijad', + + // variable watchers + 'normal': + 'Tavaline', + 'large': + 'Suur', + 'slider': + 'Liugur', + 'slider min...': + 'Miinimum ...', + 'slider max...': + 'Maksimum ...', + 'import...': + 'Import ...', + 'Slider minimum value': + 'Liuguri miinimumväärtus', + 'Slider maximum value': + 'Liuguri maksimumväärtus', + + // list watchers + 'length: ': + 'pikkus: ', + + // coments + 'add comment here...': + 'Kommentaarid kirjuta siia ...', + + // drow downs + // directions + '(90) right': + '(90) paremale', + '(-90) left': + '(-90) vasakule', + '(0) up': + '(0) üles', + '(180) down': + '(180) alla', + + // collision detection + 'mouse-pointer': + 'hiirekursori', + 'edge': + 'serva', + 'pen trails': + 'pliiatsi joone', + + // costumes + 'Turtle': + 'Nool', + 'Empty': + 'Tühi', + + // graphical effects + 'brightness': + 'heledus', + 'ghost': + 'läbipaistvus', + 'negative': + 'negatiiv', + 'comic': + 'muaree', + 'confetti': + 'värvimuutus', + + // keys + 'space': + 'tühik', + 'up arrow': + 'nool üles', + 'down arrow': + 'nool alla', + 'right arrow': + 'nool paremale', + 'left arrow': + 'nool vasakule', + 'any key': + 'mistahes klahv', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'Uus ...', + + // math functions + 'abs': + 'absoluutväärtus', + 'ceiling': + 'ümardamine üles', + 'floor': + 'ümardamine alla', + 'sqrt': + 'ruutjuur', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // delimiters + 'letter': + 'sümbol', + 'whitespace': + 'tühik', + 'line': + 'realõpp (lf)', + 'tab': + 'tabulaator (tab)', + 'cr': + 'reavahetus (cr)', + + // data types + 'number': + 'arv', + 'text': + 'tekst', + 'Boolean': + 'tõeväärtus', + 'list': + 'loend', + 'command': + 'käsuplokk', + 'reporter': + 'funktsioon', + 'predicate': + 'predikaat', + + // list indices + 'last': + 'viimane', + 'any': + 'mistahes', + + // Saksa keele failist puuduvad stringid + 'grow': + 'Suurenda', + 'shrink': + 'Vähenda', + 'flip ↔': + 'Flipi ↔', + 'flip ↕': + 'Flipi ↕', + 'Export all scripts as pic...': + 'Ekspordi kõik skriptid pilti ...', + 'show a picture of all scripts\nand block definitions': + 'Näitab pilti kõigi skriptide\nja plokkide definitsioonidega.', + 'current %dates': + 'current %dates', + 'year': + 'year', + 'month': + 'month', + 'date': + 'date', + 'day of week': + 'day of week', + 'hour': + 'hour', + 'minute': + 'minute', + 'second': + 'second', + 'time in milliseconds': + 'aeg (ms)', + 'find blocks...': + 'Otsi plokke ...', + 'costume name': + 'kostüümi nimi', + 'Open': + 'Ava', + 'Share': + 'Jaga', + 'Snap!Cloud': + 'Snap! pilv', + 'Cloud': + 'Pilv', + 'could not connect to:': + 'Ühendust ei õnnestunud luua:', + 'Service:': + 'Teenus:', + 'login': + 'login', + 'ERROR: INVALID PASSWORD': + 'VIGA: VALE PAROOL', + 'Browser': + 'Brauser', + 'Sign up': + 'Sign up', + 'Signup': + 'Signup', + 'Sign in': + 'Logi sisse', + 'Logout': + 'Logi välja', + 'Change Password...': + 'Muuda parooli ...', + 'Change Password': + 'Parooli muutmine', + 'Account created.': + 'Konto on loodud.', + 'An e-mail with your password\nhas been sent to the address provided': + 'E-kiri parooliga saadeti sinu\nantud aadressile.', + 'now connected.': + 'now connected.', + 'disconnected.': + 'disconnected.', + 'Reset password': + 'Parooli lähtestamine', + 'Reset Password...': + 'Lähtesta parool ...', + 'User name:': + 'Kasutajanimi:', + 'Password:': + 'Parool:', + 'Old password:': + 'Kehtiv parool:', + 'New password:': + 'Uus parool:', + 'Repeat new password:': + 'Uue parooli kordus:', + 'Birth date:': + 'Sünnikuupäev:', + 'January': + 'jaanuar', + 'February': + 'veebruar', + 'March': + 'märts', + 'April': + 'áprill', + 'May': + 'mai', + 'June': + 'juuni', + 'July': + 'juuli', + 'August': + 'august', + 'September': + 'september', + 'October': + 'oktoober', + 'November': + 'november', + 'December': + 'detsember', + 'year:': + 'aasta:', + ' or before': + ' or before', + 'E-mail address:': + 'E-posti aadress:', + 'E-mail address of parent or guardian:': + 'Lapsevanema või hooldaja e-posti aadress:', + 'Terms of Service...': + 'Teenuse Tingimused ...', + 'Privacy...': + 'Privaatsus ...', + 'I have read and agree\nto the Terms of Service': + 'Ma olen Teenuse Tingimusi lugenud\nja nendega nõustunud', + 'stay signed in on this computer\nuntil logging out': + 'stay signed in on this computer\nuntil logging out', + 'please fill out\nthis field': + 'Palun täida\nsee väli.', + 'User name must be four\ncharacters or longer': + 'Kasutajanimes peab olema\nvähemalt neli sümbolit.', + 'please provide a valid\nemail address': + 'Palun sisesta korrektne\ne-posti aadress.', + 'password must be six\ncharacters or longer': + 'Paroolis peab olema\nvähemalt kuus sümbolit.', + 'passwords do\nnot match': + 'Paroolid ei kattu.', + 'please agree to\nthe TOS': + 'Palun nõustu teenuse tingimustega.', + 'Examples': + 'Näited', + 'You are not logged in': + 'Sa pole end sisse loginud', + 'Updating\nproject list...': + 'Projektide nimekirja uuendamine ...', + 'Opening project...': + 'Projekti avamine ...', + 'Fetching project\nfrom the cloud...': + 'Projekti allalaadimine\npilvest ...', + 'Saving project\nto the cloud...': + 'Projekti salvestamine\npilve ...', + 'Sprite Nesting': + 'Sprite Nesting', + 'uncheck to disable\nsprite composition': + 'uncheck to disable\nsprite composition', + 'Codification support': + 'Kodifitseerimise tugi', + 'check for block\nto text mapping features': + 'Kui soovid kasutada plokkide tekstiks\nteisendamist, lülita see sisse.', + 'saved.': + 'salvestatud.', + 'options...': + 'valikud ...', + 'read-only': + 'ainult loetav', + 'Input Slot Options': + 'Input Slot Options', + 'Enter one option per line.Optionally use "=" as key/value delimiter\ne.g.\n the answer=42': + 'Enter one option per line.Optionally use "=" as key/value delimiter\ne.g.\n the answer=42', + 'paint a new sprite': + 'paint a new sprite', + 'Paint a new costume': + 'Paint a new costume', + 'add a new Turtle sprite': + 'add a new Turtle sprite', + 'Flat design': + 'Lame kasutajaliides', + 'check for alternative\nGUI design': + 'Alternatiivse kasutajaliidese disaini\nkasutamiseks lülita see sisse.', + 'Rasterize SVGs': + 'SVG-de rasteriseerimine', + 'check to rasterize\nSVGs on import': + 'SVG graafika rasteriseerimiseks\nimpordil lülita see sisse.', + 'comment pic...': + 'megjegyzés képe…', + 'open a new window\nwith a picture of this comment': + 'új ablak megnyitása\nennek a megjegyzésnek a képével', + 'undo': + 'Võta tagasi', + 'Brush size': + 'Joone laius', + 'Constrain proportions of shapes?\n(you can also hold shift)': + 'Soovid kujundi proportsioone piirata?\n(proovi SHIFT klahvi all hoida)', + 'Eraser tool': + 'Kustukumm', + 'Paintbrush tool\n(free draw)': + 'Vabakäe joonistamine', + 'Line tool\n(shift: vertical/horizontal)': + 'Joon\n(shift: vertikaalne/horisontaalne)', + 'Stroked Rectangle\n(shift: square)': + 'Ristkülik\n(shift: ruut)', + 'Filled Rectangle\n(shift: square)': + 'Täidetud ristkülik\n(shift: ruut)', + 'Stroked Ellipse\n(shift: circle)': + 'Ellips\n(shift: ring)', + 'Filled Ellipse\n(shift: circle)': + 'Täidetud ellips\n(shift: ring)', + 'Fill a region': + 'Ala täitmine', + 'Set the rotation center': + 'Pöörlemiskeskme määramine', + 'Pipette tool\n(pick a color anywhere)': + 'Pipett\n(värvi võtmine)', + 'Paint Editor': + 'Joonistusala', + 'square': + 'ruut', + 'pointRight': + 'pointRight', + 'gears': + 'gears', + 'file': + 'file', + 'fullScreen': + 'fullScreen', + 'normalScreen': + 'normalScreen', + 'smallStage': + 'smallStage', + 'normalStage': + 'normalStage', + 'turtle': + 'kilpkonn', + 'stage': + 'lava', + 'turtleOutline': + 'turtleOutline', + 'pause': + 'paus', + 'flag': + 'lipp', + 'octagon': + 'octagon', + 'cloud': + 'pilv', + 'cloudOutline': + 'cloudOutline', + 'cloudGradient': + 'cloudGradient', + 'turnRight': + 'turnRight', + 'turnLeft': + 'turnLeft', + 'storage': + 'storage', + 'poster': + 'poster', + 'flash': + 'flash', + 'brush': + 'brush', + 'rectangle': + 'ristkülik', + 'rectangleSolid': + 'täidetud ristkülik', + 'circle': + 'ring', + 'circleSolid': + 'täidetud ring', + 'crosshairs': + 'crosshairs', + 'paintbucket': + 'värviämber', + 'eraser': + 'kustukumm', + 'pipette': + 'Pipett', + 'speechBubble': + 'jutumull', + 'speechBubbleOutline': + 'speechBubbleOutline', + 'arrowUp': + 'arrowUp', + 'arrowUpOutline': + 'arrowUpOutline', + 'arrowLeft': + 'arrowLeft', + 'arrowLeftOutline': + 'arrowLeftOutline', + 'arrowDown': + 'arrowDown', + 'arrowDownOutline': + 'arrowDownOutline', + 'arrowRight': + 'arrowRight', + 'arrowRightOutline': + 'arrowRightOutline', + 'robot': + 'robot', + 'turn pen trails into new costume...': + 'turn pen trails into new costume...', + 'turn all pen trails and stamps\ninto a new costume for the\ncurrently selected sprite': + 'turn all pen trails and stamps\ninto a new costume for the\ncurrently selected sprite', + 'pen': + 'Pliiats', + 'tip': + 'Tipus', + 'middle': + 'Keskkohas', + 'last changed': + 'Viimati muudetud', + 'Are you sure you want to publish': + 'Oled sa kindel, et soovid avaldalda?', + 'Are you sure you want to unpublish': + 'Oled sa kindel, et soovid avaldamise lõpetada?', + 'Share Project': + 'Projekti jagamine', + 'Unshare Project': + 'Projekti jagamise lõpetamine', + 'sharing\nproject...': + 'Projekti\njagamine ...', + 'unsharing\nproject...': + 'Projekti jagamise\nlõpetamine ...', + 'shared.': + 'jagatud.', + 'unshared.': + 'jagamine lõpetatud.', + 'Unshare': + 'Lõpeta jagamine', + 'password has been changed.': + 'parool on muudetud.', + 'SVG costumes are\nnot yet fully supported\nin every browser': + 'SVG kostüümid pole veel kõigis brauserites täielikult toetatud.', + 'Save Project': + 'Projekti salvestamine', + 'script pic with result...': + 'script pic with result...', + 'open a new window\nwith a picture of both\nthis script and its result': + 'open a new window\nwith a picture of both\nthis script and its result', + 'JavaScript function ( %mult%s ) { %code }': + 'JavaScript funktsioon ( %mult%s ) { %code }', + 'Select categories of additional blocks to add to this project.': + 'Select categories of additional blocks to add to this project.', + 'Import sound': + 'Helide import', + 'Select a sound from the media library': + 'Helifaili valimine meediateegist.', + 'Import': + 'Import -', + 'Select a costume from the media library': + 'Kostüümi valimine meediateegist.', + 'edit rotation point only...': + 'edit rotation point only...', + 'Export Project As...': + 'Projekti eksport kui ...', + 'a variable of name \'': + 'muutujat nimega „', + '\'\ndoes not exist in this context': + '“\nselles kontekstis ei eksisteeri', + '(temporary)': + '(ajutine)', + 'expecting': + 'Oodati', + 'input(s), but getting': + 'sisendit/sisendeid, kuid saadi', + + // code + 'map %cmdRing to %codeKind %code': + 'map %cmdRing to %codeKind %code', + 'map String to code %code': + 'map String to code %code', + 'map %codeListPart of %codeListKind to code %code': + 'map %codeListPart of %codeListKind to code %code', + 'code of %cmdRing': + '%cmdRing kood', + 'delimiter': + 'eraldaja', + 'collection': + 'collection', + 'variables': + 'muutujad', + 'parameters': + 'parameetrid', + 'code': + 'kood', + 'header': + 'päis', + 'header mapping...': + 'header mapping...', + 'code mapping...': + 'code mapping...', + 'Code mapping': + 'Code mapping', + 'Header mapping': + 'Header mapping', + 'Enter code that corresponds to the block\'s definition. Use the formal parameter\nnames as shown and to reference the definition body\'s generated text code.': + 'Enter code that corresponds to the block\'s definition. Use the formal parameter\nnames as shown and to reference the definition body\'s generated text code.', + 'Enter code that corresponds to the block\'s definition. Choose your own\nformal parameter names (ignoring the ones shown).': + 'Enter code that corresponds to the block\'s definition. Choose your own\nformal parameter names (ignoring the ones shown).', + 'Enter code that corresponds to the block\'s operation (usually a single\nfunction invocation). Use <#n> to reference actual arguments as shown.': + 'Enter code that corresponds to the block\'s operation (usually a single\nfunction invocation). Use <#n> to reference actual arguments as shown.' +}; diff --git a/lang-fr.js b/lang-fr.js index 20da4808..5128d83c 100644 --- a/lang-fr.js +++ b/lang-fr.js @@ -1,27 +1,27 @@ /* - lang-de.js + lang-de.js - German translation for SNAP! + German translation for SNAP! - written by Jens Mönig + written by Jens Mönig - Copyright (C) 2012 by Jens Mönig + Copyright (C) 2012 by Jens Mönig - This file is part of Snap!. + This file is part of Snap!. - Snap! is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of - the License, or (at your option) any later version. + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . @@ -171,28 +171,40 @@ SnapTranslator.dict.fr = { /* Special characters: (see ) - Ä, ä \u00c4, \u00e4 - Ö, ö \u00d6, \u00f6 - Ü, ü \u00dc, \u00fc - ß \u00df + À \u00C0 + à \u00E0 + É \u00C9 + è \u00E8 + é \u00E9 + ê \u00EA + ç \u00E7 + ï \u00EF + ô \u00F4 + ù \u00F9 + ° \u00B0 + ' \u0027 + « \u00AB + » \u00BB + ↔ \u2194 + ↕ \u2195 */ // translations meta information 'language_name': 'Fran\u00E7ais', // the name as it should appear in the language menu 'language_translator': - 'Jean-Jacques Valliet, Mark Rafter, Martin Quinson', // your name for the Translators tab + 'Jean-Jacques Valliet, Mark Rafter, Martin Quinson, Damien Caselli', // your name for the Translators tab 'translator_e-mail': 'i.scool@mac.com', // optional 'last_changed': - '2015-06-25', // this, too, will appear in the Translators tab + '2016-10-27', // this, too, will appear in the Translators tab // GUI // control bar: 'untitled': 'Sans Titre', 'development mode': - 'mode de d\u00E9veloppeur', + 'mode d\u00E9veloppeur', // categories: 'Motion': @@ -339,11 +351,11 @@ SnapTranslator.dict.fr = { 'think %s': 'penser %s', 'Hello!': - 'Salut!', + 'Salut !', 'Hmm...': 'Mmmh...', 'change %eff effect by %n': - 'ajouter \u00E0 l\u0027effet %eff %n', + 'ajouter \u00E0 l\u0027effet %eff %n', 'set %eff effect to %n': 'mettre l\u0027effet %eff \u00E0 %n', 'clear graphic effects': @@ -411,6 +423,8 @@ SnapTranslator.dict.fr = { 'choisir la taille %n pour le stylo', 'stamp': 'estampiller', + 'fill': + 'remplir', // control: 'when %greenflag clicked': @@ -419,8 +433,8 @@ SnapTranslator.dict.fr = { 'Quand %keyHat est press\u00E9', 'when I am clicked': 'Quand je suis press\u00E9 ', - 'when I am %interaction': - 'Quand je suis %interaction', + 'when I am %interaction': + 'Quand je suis %interaction', 'when I receive %msgHat': 'Quand je re\u00E7ois %msgHat', 'broadcast %msg': @@ -440,19 +454,19 @@ SnapTranslator.dict.fr = { 'repeat until %b %c': 'r\u00E9p\u00E9ter jusqu\u0027\u00E0 %b %c', 'if %b %c': - 'si %b %c', + 'si %b %c', 'if %b %c else %c': - 'si %b %c sinon %c', + 'si %b %c sinon %c', 'report %s': 'rapporte %s', 'stop block': 'arr\u00EAter le bloc', 'stop script': 'arr\u00EAter le script', - 'stop %stopOthersChoices': - 'arr\u00EAter %stopOthersChoices', - 'stop %stopChoices': - 'arr\u00EAter %stopChoices', + 'stop %stopOthersChoices': + 'arr\u00EAter %stopOthersChoices', + 'stop %stopChoices': + 'arr\u00EAter %stopChoices', 'stop all %stop': 'arr\u00EAter tout %stop', 'run %cmdRing %inputs': @@ -460,7 +474,7 @@ SnapTranslator.dict.fr = { 'launch %cmdRing %inputs': 'lance %cmdRing %inputs', 'call %repRing %inputs': - 'appelle %repRing %inputs', + 'appelle %repRing %inputs', 'run %cmdRing w/continuation': 'ex\u00E9cute %cmdRing avec continuation', 'call %cmdRing w/continuation': @@ -477,26 +491,26 @@ SnapTranslator.dict.fr = { 'supprime ce clone', 'pause all %pause': 'mettre en pause %pause', - 'all but this script': - 'tout sauf ce lutin', - 'other scripts in sprite': - 'les autres scripts de ce lutin', - 'this script': - 'ce script', - 'this block': - 'ce bloc', - + 'all but this script': + 'tout sauf ce script', + 'other scripts in sprite': + 'les autres scripts de ce lutin', + 'this script': + 'ce script', + 'this block': + 'ce bloc', + // sensing: 'touching %col ?': - ' %col touch\u00E9?', + '%col touch\u00E9 ?', 'touching %clr ?': - ' couleur %clr touch\u00E9e?', + 'couleur %clr touch\u00E9e ?', 'color %clr is touching %clr ?': 'couleur %clr touche %clr ?', 'ask %s and wait': 'demander %s et attendre', 'what\'s your name?': - 'Quel est ton nom?', + 'Quel est ton nom ?', 'answer': 'r\u00E9ponse', 'mouse x': @@ -504,9 +518,9 @@ SnapTranslator.dict.fr = { 'mouse y': 'souris y', 'mouse down?': - 'souris press\u00E9e?', + 'souris press\u00E9e ?', 'key %key pressed?': - 'touche %key press\u00E9e?', + 'touche %key press\u00E9e ?', 'distance to %dst': 'distance de %dst', 'reset timer': @@ -515,15 +529,17 @@ SnapTranslator.dict.fr = { 'chronom\u00E8tre', '%att of %spr': '%att de %spr', + 'my %get': + 'attribut %get', 'http:// %s': 'http:// %s', 'turbo mode?': - 'turbo mode activ\u00E9?', + 'turbo mode activ\u00E9 ?', 'set turbo mode to %b': 'turbo mode prend la valeur %b', 'filtered for %clr': - 'filtr\u00E9 pour %clr ', + 'filtr\u00E9 pour %clr', 'stack size': 'taille de la pile', 'frames': @@ -565,9 +581,9 @@ SnapTranslator.dict.fr = { 'unicode %n as letter': 'unicode %n comme lettre', 'is %s a %typ ?': - 'est %s un(e) %typ ?', + '%s est un(e) %typ ?', 'is %s identical to %s ?': - '%s est identique \u00E0 %s', + '%s est identique \u00E0 %s ?', 'type of %s': 'type de %s', @@ -583,9 +599,9 @@ SnapTranslator.dict.fr = { 'set %var to %s': '%var prend la valeur %s', 'change %var by %n': - 'ajouter \u00E0 %var %n ', + 'ajouter \u00E0 %var %n', 'show variable %var': - 'afficher la variable %var', + 'afficher la variable %var', 'hide variable %var': 'cacher la variable %var', 'script variables %scriptVars': @@ -599,7 +615,7 @@ SnapTranslator.dict.fr = { 'item %idx of %l': '\u00E9l\u00E9ment %idx de %l', 'all but first of %l': - 'tous sauf le premier de %l', + 'tous sauf le premier de %l', 'length of %l': 'longueur de %l', '%l contains %s': @@ -622,7 +638,9 @@ SnapTranslator.dict.fr = { // menus // snap menu 'About...': - 'A propos de Snap!...', + '\u00C0 propos de Snap!...', + 'Reference manual': + 'Manuel de r\u00E9f\u00E9rence', 'Snap! website': 'Snap! le site web', 'Download source': @@ -632,7 +650,7 @@ SnapTranslator.dict.fr = { 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': 'd\u00E9sactiver la fonction morphic', 'Switch to dev mode': - 'Passer en mode d\u00E9velopper', + 'Passer en mode d\u00E9veloppeur', 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': 'activer la fonction morphic', @@ -657,12 +675,28 @@ SnapTranslator.dict.fr = { 'Exporter le projet comme texte...', 'Export project...': 'Exporter le projet...', + 'save project data as XML\nto your downloads folder': + 'sauvegarder le projet au\nformat XML dans votre\ndossier T\u00E9l\u00E9chargements', 'show project data as XML\nin a new browser window': 'ouvrir le projet au format XML\ndans une nouvelle fen\u00EAtre de votre navigateur', 'Export blocks...': 'Exporter les blocs ', 'show global custom block definitions as XML\nin a new browser window': 'montrer les d\u00E9finitions de bloc global personnalis\u00E9 au format XML \ndans une nouvelle fen\u00EAtre de navigateur', + 'Unused blocks...': + 'Blocs inutilis\u00E9s...', + 'find unused global custom blocks\nand remove their definitions': + 'trouver et supprimer les blocs personnalis\u00E9s inutilis\u00E9s', + 'Remove unused blocks': + 'Supprimer les blocs inutilis\u00E9s', + 'there are currently no unused\nglobal custom blocks in this project': + 'Aucun bloc inutilis\u00E9 dans ce projet', + 'unused block(s) removed': + 'bloc(s) inutilis\u00E9(s) supprim\u00E9(s)', + 'Export summary...': + 'Exporter un r\u00E9sum\u00E9...', + 'open a new browser browser window\n with a summary of this project': + 'voir un résumé de ce projet dans\nune nouvelle fenêtre du navigateur', 'Import tools': 'Importer les outils', 'load the official library of\npowerful blocks': @@ -680,86 +714,98 @@ SnapTranslator.dict.fr = { 'uncheck to use solid drop\nshadows and highlights': 'D\u00E9cocher pour utiliser des rehauts et des ombres \n port\u00E9es floues', 'check to use blurred drop\nshadows and highlights': - 'Cocher pour utiliser des rehauts et des ombres \n port\u00E9es pleines', + 'cocher pour utiliser des rehauts et des ombres \n port\u00E9es pleines', 'Zebra coloring': 'Colorations altern\u00E9es', 'check to enable alternating\ncolors for nested blocks': - 'Cocher pour activer des couleurs altern\u00E9es \n pour les blocs embo\u00EEt\u00E9s', + 'cocher pour activer des couleurs altern\u00E9es \n pour les blocs embo\u00EEt\u00E9s', 'uncheck to disable alternating\ncolors for nested block': - 'D\u00E9cocher pour d\u00E9sactiver des couleurs altern\u00E9es \n pour les blocs embo\u00EEt\u00E9s', + 'd\u00E9cocher pour d\u00E9sactiver des couleurs altern\u00E9es \n pour les blocs embo\u00EEt\u00E9s', 'Prefer empty slot drops': 'Pr\u00E9f\u00E9rer des entr\u00E9es vides', 'settings menu prefer empty slots hint': - 'Cocher pour pr\u00E9f\u00E9rer des entr\u00E9es vides \n' - + 'lors du glisser-d\u00E9poser d\u0027un reporter', + 'cocher pour pr\u00E9f\u00E9rer des entr\u00E9es vides \n' + + 'lors du glisser-d\u00E9poser d\u0027un reporter', 'uncheck to allow dropped\nreporters to kick out others': - 'D\u00E9cocher pour ne pas pr\u00E9f\u00E9rer des entr\u00E9es vides \n' - + 'lors du glisser-d\u00E9poser d\u0027un reporter', + 'd\u00E9cocher pour ne pas pr\u00E9f\u00E9rer des entr\u00E9es vides \n' + + 'lors du glisser-d\u00E9poser d\u0027un reporter', 'Long form input dialog': 'Bo\u00EEte d\u0027entr\u00E9e en mode d\u00E9taill\u00E9', 'check to always show slot\ntypes in the input dialog': - 'Cocher pour toujours ouvrir la bo\u00EEte de dialogue \nd\u0027entr\u00E9e en mode d\u00E9taill\u00E9 : avec tous les types de blocs', + 'cocher pour toujours ouvrir la bo\u00EEte de dialogue \nd\u0027entr\u00E9e en mode d\u00E9taill\u00E9 : avec tous les types de blocs', 'uncheck to use the input\ndialog in short form': - 'D\u00E9cocher pour utiliser la bo\u00EEte de dialogue \nd\u0027entr\u00E9e en mode simple ', + 'd\u00E9cocher pour utiliser la bo\u00EEte de dialogue \nd\u0027entr\u00E9e en mode simple ', 'Virtual keyboard': 'Clavier virtuel', 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': - 'D\u00E9cocher pour d\u00E9sactiver le clavier virtuel pour \nles tablettes et smartphones : mobile devices ', + 'd\u00E9cocher pour d\u00E9sactiver le clavier virtuel pour \nles tablettes et smartphones : mobile devices ', 'check to enable\nvirtual keyboard support\nfor mobile devices': - 'Cocher pour activer le clavier virtuel pour \nles tablettes et smartphones : mobile devices ', + 'cocher pour activer le clavier virtuel pour \nles tablettes et smartphones : mobile devices ', 'Input sliders': 'Entr\u00E9e curseurs', 'uncheck to disable\ninput sliders for\nentry fields': - 'D\u00E9cocher pour d\u00E9sactiver le curseur coulissant \ndans le champ de saisie', + 'd\u00E9cocher pour d\u00E9sactiver le curseur coulissant \ndans le champ de saisie', 'check to enable\ninput sliders for\nentry fields': - 'Cocher pour activer un curseur coulissant \ndans le champ de saisie ', + 'cocher pour activer un curseur coulissant \ndans le champ de saisie ', 'Clicking sound': 'Cliquetis', 'uncheck to turn\nblock clicking\nsound off': - 'D\u00E9cocher pour d\u00E9sactiver le cliquetis \n' - +'lors de l\u0027embo\u00EEtement des blocs' , + 'd\u00E9cocher pour d\u00E9sactiver le cliquetis \n' + +'lors de l\u0027embo\u00EEtement des blocs' , 'check to turn\nblock clicking\nsound on': - 'Cocher pour activer le cliquetis \n' - +'lors de l\u0027embo\u00EEtement des blocs', + 'cocher pour activer le cliquetis \n' + +'lors de l\u0027embo\u00EEtement des blocs', 'Turbo mode': 'Mode turbo', 'check to prioritize\nscript execution': - 'Cocher pour favoriser l\'ex\u00E9cution du script', + 'cocher pour favoriser l\'ex\u00E9cution du script', 'uncheck to run scripts\nat normal speed': - 'D\u00E9cocher pour ex\u00E9cuter le script en vitesse normale', + 'd\u00E9cocher pour ex\u00E9cuter le script en vitesse normale', 'Flat design': 'Style al\u00E9g\u00E9', 'check for alternative\nGUI design': - 'Cocher pour un style d\interface alternatif', + 'cocher pour un style d\'interface alternatif', 'uncheck for default\nGUI design': - 'D\u00E9cocher pour le style classique d\interface', + 'd\u00E9cocher pour le style classique d\'interface', + 'Keyboard Editing': + '\u00C9dition au clavier', + 'uncheck to disable\nkeyboard editing support': + 'd\u00E9cocher pour d\u00E9sactiver l\'\u00E9dition au clavier', + 'check to enable\nkeyboard editing support': + 'cocher pour activer l\'\u00E9dition au clavier', 'Thread safe scripts': 'Scripts réentrants', 'check to disallow\nscript reentrance': - 'Cocher pour interdire\n la r\u00E9entrance des scripts\n' - +'et les ex\u00E9cuter s\u00E9par\u00E9ment', + 'cocher pour interdire\n la r\u00E9entrance des scripts\n' + + 'et les ex\u00E9cuter s\u00E9par\u00E9ment', 'uncheck to allow\nscript reentrance': - 'D\u00E9cocher pour permettre\n la r\u00E9entrance des scripts\n' - +'o\u00F9 certains s\'ex\u00E9cutent en paral\u00E8lle', + 'd\u00E9cocher pour permettre\n la r\u00E9entrance des scripts\n' + + 'o\u00F9 certains s\'ex\u00E9cutent en paral\u00E8lle', 'Prefer smooth animations': 'Vitesse d\'animation fixe', 'uncheck for greater speed\nat variable frame rates': - 'D\u00E9cocher pour une vitesse\nd\'animation maximale (mais variable)', + 'd\u00E9cocher pour une vitesse\nd\'animation maximale (mais variable)', 'check for smooth, predictable\nanimations across computers': - 'Cocher pour une vitesse d\'animation\nfixe et identique sur tous les ordinateurs', - + 'cocher pour une vitesse d\'animation\nfixe et identique sur tous les ordinateurs', + // inputs 'with inputs': 'avec entr\u00E9es', 'input names:': - 'renseigner un nom:', + 'renseigner un nom :', 'Input Names:': - 'renseigner un nom:', + 'Renseigner un nom :', // context menus: 'help': 'Aide', + // palette: + 'hide primitives': + 'Masquer les blocs de base', + 'show primitives': + 'Afficher les blocs de base', + // blocks: 'help...': 'Aide...', @@ -767,6 +813,8 @@ SnapTranslator.dict.fr = { 'dupliquer', 'make a copy\nand pick it up': 'faire une copie\n et le d\u00E9placer', + 'only duplicate this block': + 'ne dupliquer que ce bloc', 'delete': 'supprimer', 'script pic...': @@ -787,8 +835,14 @@ SnapTranslator.dict.fr = { // sprites: 'edit': '\u00E9diter', + 'move': + 'd\u00E9placer', + 'detach from': + 'D\u00E9tacher de', + 'detach all parts': + 'D\u00E9tacher toutes les parties', 'export...': - 'exporter...', + 'Exporter...', 'paint a new sprite': 'dessiner un nouveau lutin', @@ -802,7 +856,7 @@ SnapTranslator.dict.fr = { 'make a block...': 'cr\u00E9er un nouveau bloc...', - // costumes + // costumes 'rename': 'renommer', 'export': @@ -836,6 +890,12 @@ SnapTranslator.dict.fr = { 'Oui', 'No': 'Non', + 'Open': + 'Ouvrir', + 'Browser': + 'Navigateur', + 'Examples': + 'Exemples', // help 'Help': @@ -853,9 +913,9 @@ SnapTranslator.dict.fr = { 'Delete Project': 'Supprimer un projet', 'Are you sure you want to delete': - 'Est ce que vous voulez le supprimer?', + 'Souhaitez-vous vraiment supprimer ?', 'rename...': - 'Renommer', + 'Renommer...', // costume editor 'Costume Editor': @@ -871,7 +931,7 @@ SnapTranslator.dict.fr = { 'New Project': 'Nouveau projet', 'Replace the current project with a new one?': - 'Remplacer le projet actuel par un nouveau?', + 'Remplacer le projet actuel par un nouveau ?', // open project 'Open Projekt': @@ -879,7 +939,7 @@ SnapTranslator.dict.fr = { // save project 'Save Project As...': - 'Sauvegarder un projet sous...', + 'Sauvegarder le projet sous...', // export blocks 'Export blocks': @@ -909,7 +969,7 @@ SnapTranslator.dict.fr = { 'Predicate': 'Pr\u00E9dicat', - // block editor + // block editor 'Block Editor': '\u00C9diteur de bloc', 'Apply': @@ -919,11 +979,11 @@ SnapTranslator.dict.fr = { 'Delete Custom Block': 'Effacer le bloc personnalis\u00E9', 'block deletion dialog text': - 'Etes-vous s\u00FBr de supprimer ce bloc personnalis\u00E9 \net ' + - 'toutes ces instances?', + '\u00CAtes-vous s\u00FBr de vouloir supprimer ce bloc personnalis\u00E9 \net ' + + 'toutes ses instances ?', // input dialog - 'Create input name': + 'Create input name': 'Cr\u00E9er le nom de l\u0027entr\u00E9e', 'Edit input name': '\u00C9diter le nom de l\u0027entr\u00E9e', @@ -958,7 +1018,7 @@ SnapTranslator.dict.fr = { 'Single input.': 'Entr\u00E9e unique.', 'Default Value:': - 'Valeur par d\u00E9faut:', + 'Valeur par d\u00E9faut :', 'Multiple inputs (value is list of inputs)': 'Entr\u00E9es multiples (la valeur est une liste des entr\u00E9es)', 'Upvar - make internal variable visible to caller': @@ -972,12 +1032,12 @@ SnapTranslator.dict.fr = { 'tabulations', 'cr': 'retours de ligne', - 'letter': - 'lettres', - - // About Snap + 'letter': + 'lettres', + + // About Snap 'About Snap': - 'A propos de Snap', + '\u00C0 propos de Snap', 'Back...': 'Retour...', 'License...': @@ -987,11 +1047,11 @@ SnapTranslator.dict.fr = { 'Credits...': 'Contributeurs...', 'Translators...': - 'Traducteurs', + 'Traducteurs...', 'License': 'License', 'current module versions:': - 'Versions du module courant', + 'Versions du module courant :', 'Contributors': 'Contributeurs', 'Translations': @@ -1015,7 +1075,7 @@ SnapTranslator.dict.fr = { // list watchers 'length: ': - 'Longueur: ', + 'Longueur : ', // coments 'add comment here...': @@ -1043,10 +1103,32 @@ SnapTranslator.dict.fr = { // costumes 'Turtle': 'Pointeur', + 'Empty': + 'Vide', // graphical effects + 'color': + 'couleur', + 'fisheye': + 'fisheye', + 'whirl': + 'tourbillon', + 'pixelate': + 'pixelisation', + 'mosaic': + 'mosa\u00EFque', + 'saturation': + 'saturation', + 'brightness': + 'luminosit\u00E9', 'ghost': 'transparence', + 'negative': + 'n\u00E9gatif', + 'comic': + 'moir\u00E9', + 'confetti': + 'confetti', // keys 'space': @@ -1180,131 +1262,145 @@ SnapTranslator.dict.fr = { 'any': 'n\u0027importe quel', - // miscellaneous - 'find blocks...': - 'chercher des blocs...', - 'hide primitives': - 'cacher les primitives', - 'show primitives': - 'montrer les primitives', - 'Login...': - 'Connexion...', - 'Signup...': - 'S\u0027enregistrer...', - 'Reset Password...': - 'Remise \u00E0 z\u00E9ro du mot de passe', - 'show all': - 'tout montrer', - 'pic...': - 'image...', - 'open a new window\nwith a picture of the stage': - 'ouvre une nouvelle fen\u00EAtre\navec une image de la sc\u00E8ne', - 'scripts pic...': - 'image des scripts...', - 'open a new window\nwith a picture of all scripts': - 'ouvre une nouvelle fen\u00EAtre\navec une image de tous les scripts', - 'Stage size...': - 'Taille de la sc\u00E8ne...', - 'Zoom blocks...': - 'Agrandir les blocs...', + // miscellaneous + 'find blocks...': + 'chercher des blocs...', + 'hide primitives': + 'cacher les primitives', + 'show primitives': + 'montrer les primitives', + 'Login...': + 'Connexion...', + 'Signup...': + 'S\u0027enregistrer...', + 'Reset Password...': + 'Remise \u00E0 z\u00E9ro du mot de passe...', + 'show all': + 'tout montrer', + 'pic...': + 'image...', + 'open a new window\nwith a picture of the stage': + 'ouvre une nouvelle fen\u00EAtre\navec une image de la sc\u00E8ne', + 'scripts pic...': + 'image des scripts...', + 'open a new window\nwith a picture of all scripts': + 'ouvre une nouvelle fen\u00EAtre\navec une image de tous les scripts', + 'Stage size...': + 'Taille de la sc\u00E8ne...', + 'Zoom blocks...': + 'Agrandir les blocs...', - 'Plain prototype labels': - '\u00C9tiquettes simples de d\u00E9finition', - 'uncheck to always show (+) symbols\nin block prototype labels': - 'd\u00E9cocher pour montrer en permance le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', - 'check to hide (+) symbols\nin block prototype labels': - 'cocher pour cacher le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', + 'Plain prototype labels': + '\u00C9tiquettes simples de d\u00E9finition', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'd\u00E9cocher pour montrer en permanance le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', + 'check to hide (+) symbols\nin block prototype labels': + 'cocher pour cacher le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', - 'check for flat ends of lines': - 'cocher pour dessiner des fins de ligne plates', - 'uncheck for round ends of lines': - 'd\u00E9cocher pour dessiner des fins de lignes arrondies', - 'Flat line ends': - 'Fins de ligne plates', + 'check for flat ends of lines': + 'cocher pour dessiner des fins de ligne plates', + 'uncheck for round ends of lines': + 'd\u00E9cocher pour dessiner des fins de lignes arrondies', + 'Flat line ends': + 'Fins de ligne plates', - 'Codification support': - 'Support de la \u00AB codification \u00BB', - 'uncheck to disable\nblock to text mapping features': - 'd\u00E9cocher pour d\u00E9activer\nla fonction de transformation :\nbloc vers texte', - 'check for block\nto text mapping features': - 'cocher pour activer\nla fonction de transformation :\nbloc vers texte', + 'Codification support': + 'Support de la \u00AB codification \u00BB', + 'uncheck to disable\nblock to text mapping features': + 'd\u00E9cocher pour d\u00E9activer\nla fonction de transformation :\nbloc vers texte', + 'check for block\nto text mapping features': + 'cocher pour activer\nla fonction de transformation :\nbloc vers texte', - 'current %dates': - 'date courante %dates', - 'year':'ann\u00E9e', - 'month':'mois', - 'date':'jour', - 'hour':'heure', - 'minute':'minute', - 'second':'seconde', - 'time in milliseconds': - 'heure en millisecondes', - 'day of week': - 'jour de la semaine', + 'Inheritance support': + 'Support de l\'h\u00E9ritage', - 'brightness': - 'luminosit\u00E9', - 'transparence': - 'transparence', - 'negative': - 'n\u00E9gatif', - 'comic': - 'bande dessin\u00E9e', + 'current %dates': + 'date courante %dates', + 'year': + 'ann\u00E9e', + 'month': + 'mois', + 'date': + 'jour', + 'hour': + 'heure', + 'minute': + 'minute', + 'second': + 'seconde', + 'time in milliseconds': + 'heure en millisecondes', + 'day of week': + 'jour de la semaine', - 'clicked': - 'cliqu\u00E9', - 'pressed': - 'press\u00E9', - 'dropped': - 'd\u00E9pos\u00E9', - 'mouse-entered': - 'survol\u00E9', - 'mouse-departed': - 'quitt\u00E9', + 'brightness': + 'luminosit\u00E9', + 'transparence': + 'transparence', + 'negative': + 'n\u00E9gatif', + 'comic': + 'bande dessin\u00E9e', - 'JavaScript function ( %mult%s ) { %code }': - 'fonction JavaScript ( %mult%s ) { %code }', + 'clicked': + 'cliqu\u00E9', + 'pressed': + 'press\u00E9', + 'dropped': + 'd\u00E9pos\u00E9', + 'mouse-entered': + 'survol\u00E9', + 'mouse-departed': + 'quitt\u00E9', + 'when %b': + 'Quand %b', + + 'JavaScript function ( %mult%s ) { %code }': + 'fonction JavaScript ( %mult%s ) { %code }', - // Copy / Paste - 'Press CTRL+C one more time to effectively copy to clipboard.': - 'Taper une nouvelle fois sur CTRL+C pour copier effectivement vers le presse-papier.', - 'Press CTRL+V one more time to effectively paste from clipboard.': - 'Taper une nouvelle fois sur CTRL+V pour coller effectivement depuis le presse-papier.', - 'Press CTRL+X one more time to effectively cut to clipboard.': - 'Taper une nouvelle fois sur CTRL+X pour couper effectivement vers le presse-papier.', + // Copy / Paste + 'Press CTRL+C one more time to effectively copy to clipboard.': + 'Taper une nouvelle fois sur CTRL+C pour copier effectivement vers le presse-papier.', + 'Press CTRL+V one more time to effectively paste from clipboard.': + 'Taper une nouvelle fois sur CTRL+V pour coller effectivement depuis le presse-papier.', + 'Press CTRL+X one more time to effectively cut to clipboard.': + 'Taper une nouvelle fois sur CTRL+X pour couper effectivement vers le presse-papier.', - // Paint.js - 'undo':'d\u00E9faire', - 'Paintbrush tool\n(free draw)': - 'Pinceau\n(dessin \u00E0 main lev\u00E9)', - 'Stroked Rectangle\n(shift: square)': - 'Rectangle\n(Maj: carr\u00E9)', - 'Stroked Ellipse\n(shift: circle)': - 'Ellipse\n(Maj: cercle)', - 'Eraser tool': - 'Gomme', - 'Set the rotation center': - 'Fixe le centre de rotation', - 'Line tool\n(shift: vertical/horizontal)': - 'Ligne\n(Maj: verticale/horizontale)', - 'Filled Rectangle\n(shift: square)': - 'Rectangle plein\n(Maj: carr\u00E9)', - 'Filled Ellipse\n(shift: circle)': - 'Ellipse pleine\n(Maj: cercle)', - 'Fill a region': - 'Remplir une r\u00E9gion', - 'Pipette tool\n(pick a color anywhere)': - 'Pipette\n(s\u00E9lectionnez une couleur n\u0027importe o\u00F9', - 'grow':'agrandir', - 'shrink':'r\u00E9duire', - 'flip \u2194': - 'miroir \u2194', - 'flip \u2195': - 'miroir \u2195', - 'Brush size': - 'Taille de pinceau', - 'Constrain proportions of shapes?\n(you can also hold shift)': - 'Contrainte sur les proportions de la forme ?\n(vous pouvez aussi maintenir appuy\u00E9 Maj)' + // Paint.js + 'undo': + 'd\u00E9faire', + 'Paintbrush tool\n(free draw)': + 'Pinceau\n(dessin \u00E0 main lev\u00E9e)', + 'Stroked Rectangle\n(shift: square)': + 'Rectangle\n(Maj : carr\u00E9)', + 'Stroked Ellipse\n(shift: circle)': + 'Ellipse\n(Maj : cercle)', + 'Eraser tool': + 'Gomme', + 'Set the rotation center': + 'Fixer le centre de rotation', + 'Line tool\n(shift: vertical/horizontal)': + 'Ligne\n(Maj: verticale/horizontale)', + 'Filled Rectangle\n(shift: square)': + 'Rectangle plein\n(Maj: carr\u00E9)', + 'Filled Ellipse\n(shift: circle)': + 'Ellipse pleine\n(Maj: cercle)', + 'Fill a region': + 'Remplir une r\u00E9gion', + 'Pipette tool\n(pick a color anywhere)': + 'Pipette\n(s\u00E9lectionnez une couleur n\u0027importe o\u00F9)', + 'grow': + 'agrandir', + 'shrink': + 'r\u00E9duire', + 'flip \u2194': + 'miroir \u2194', + 'flip \u2195': + 'miroir \u2195', + 'Brush size': + 'Taille de pinceau', + 'Constrain proportions of shapes?\n(you can also hold shift)': + 'Contraindre les proportions de la forme ?\n(vous pouvez aussi maintenir appuy\u00E9 Maj)' }; diff --git a/lang-gl.js b/lang-gl.js new file mode 100644 index 00000000..5349b551 --- /dev/null +++ b/lang-gl.js @@ -0,0 +1,1909 @@ +/* + + lang-gl.js + + galician translation for SNAP! + + written by tecnoloxia + + CC By 2016 by tecnoloxia + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + gl - Galician => => SnapTranslator.dict.gl = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + gl - Galician => => lang-gl.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.gl = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Galego', // the name as it should appear in the language menu + 'language_translator': + 'tecnoloxia', // your name for the Translators tab + 'translator_e-mail': + '', // optional + 'last_changed': + '2016-11-09', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'Sen título', + 'development mode': + 'modo de desenvolvemento', + + // categories: + 'Motion': + 'Movemento', + 'Looks': + 'Aparencia', + 'Sound': + 'Son', + 'Pen': + 'Lapis', + 'Control': + 'Control', + 'Sensing': + 'Sensores', + 'Operators': + 'Operadores', + 'Variables': + 'Variables', + 'Lists': + 'Listas', + 'Other': + 'Outros', + + // editor: + 'draggable': + 'arrastrable', + + // tabs: + 'Scripts': + 'Programas', + 'Costumes': + 'Vestimentas', + 'Sounds': + 'Sons', + + // names: + 'Sprite': + 'Obxecto', + 'Stage': + 'escenario', + + // rotation styles: + 'don\'t rotate': + 'non xira', + 'can rotate': + 'pode xirar', + 'only face left/right': + 'xira unicamente á esquerda ou á dereita', + + // new sprite button: + 'add a new sprite': + 'Engade un novo obxecto', + + // tab help + 'costumes tab help': + 'Importa unha imaxe de internet ou do \n' + + 'teu ordenador arrastrándoa aquí', + 'import a sound from your computer\nby dragging it into here': + 'Importa un son do teu ordenador arrastrándoo aquí', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'escenario seleccionado:\nsem primitivas de movemento', + + 'move %n steps': + 'mover %n pasos', + 'turn %clockwise %n degrees': + 'xirar %clockwise %n graos', + 'turn %counterclockwise %n degrees': + 'xirar %counterclockwise %n graos', + 'point in direction %dir': + 'apuntar na dirección %dir', + 'point towards %dst': + 'apuntar cara a %dst', + 'go to x: %n y: %n': + 'ir a x: %n y: %n', + 'go to %dst': + 'ir a %dst', + 'glide %n secs to x: %n y: %n': + 'esvarar %n seg cara a x: %n y: %n', + 'change x by %n': + 'engade %n á coordenada x', + 'set x to %n': + 'fixar x en %n', + 'change y by %n': + 'engade %n á coordenada y', + 'set y to %n': + 'fixar y en %n', + 'if on edge, bounce': + 'rebotar se toca un bordo', + 'x position': + 'posición x', + 'y position': + 'posición y', + 'direction': + 'dirección', + + // looks: + 'switch to costume %cst': + 'mudar vestimenta a %cst', + 'next costume': + 'seguinte vestimenta', + 'costume #': + 'vestimenta #', + 'say %s for %n secs': + 'dicir %s durante %n s', + 'say %s': + 'dicir %s', + 'think %s for %n secs': + 'pensar %s durante %n s', + 'think %s': + 'pensar %s', + 'Hello!': + 'Ola!', + 'Hmm...': + 'mmm...', + 'change %eff effect by %n': + 'engade ao efecto %eff o valor %n', + 'set %eff effect to %n': + 'fixar efecto %eff a %n', + 'clear graphic effects': + 'eliminar efectos gráficos', + 'change size by %n': + 'engade un %n % ao tamaño', + 'set size to %n %': + 'fixar tamaño a %n %', + 'size': + 'tamaño', + 'show': + 'amosar', + 'hide': + 'agochar', + 'go to front': + 'enviar á fronte', + 'go back %n layers': + 'enviar atrás %n capas', + + 'development mode \ndebugging primitives:': + 'primitivas de depuración \ndo modo de desenvolvemento:', + 'console log %mult%s': + 'rexistra %mult%s na consola', + 'alert %mult%s': + 'mostra alerta con %mult%s', + + // sound: + 'play sound %snd': + 'reproducir son %snd', + 'play sound %snd until done': + 'reproducir son %snd ata rematar', + 'stop all sounds': + 'deter todos os sons', + 'rest for %n beats': + 'silencio por %n pulsos', + 'play note %n for %n beats': + 'tocar nota %n durante %n pulsos', + 'change tempo by %n': + 'aumenta o tempo en %n', + 'set tempo to %n bpm': + 'fixar tempo a %n bpm', + 'tempo': + 'tempo', + + // pen: + 'clear': + 'limpar o escenario', + 'pen down': + 'baixar lapis', + 'pen up': + 'subir lapis', + 'set pen color to %clr': + 'fixar a cor do lapis a %clr', + 'change pen color by %n': + 'engade %n á cor do lapis', + 'set pen color to %n': + 'fixar a cor do lapis a %n', + 'change pen shade by %n': + 'engade %n á intensidade do lapis', + 'set pen shade to %n': + 'fixar a intensidade en %n', + 'change pen size by %n': + 'engade %n ao tamaño do lapis', + 'set pen size to %n': + 'fixar o tamaño do lapis en %n', + 'stamp': + 'selar', + 'fill': + 'encher o escenario', + + // control: + 'when %greenflag clicked': + 'Ao premer %greenflag', + 'when %keyHat key pressed': + 'Ao premer a tecla %keyHat', + 'when I am %interaction': + 'Cando o rato %interaction', + 'clicked': + 'clica en min', + 'pressed': + 'preme en min', + 'dropped': + 'me solta', + 'mouse-entered': + 'entra en min', + 'mouse-departed': + 'sae de min', + 'when %b': + 'Cando %b', + 'when I receive %msgHat': + 'Ao recibir %msgHat', + 'broadcast %msg': + 'enviar a todos %msg', + 'broadcast %msg and wait': + 'enviar a todos %msg e agardar', + 'Message name': + 'nome da mensaxe', + 'message': + 'mensaxe', + 'any message': + 'calquera mensaxe', + 'wait %n secs': + 'agardar %n s', + 'wait until %b': + 'agardar ata %b', + 'forever %c': + 'para sempre %c', + 'repeat %n %c': + 'repetir %n %c', + 'repeat until %b %c': + 'repetir ata %b %c', + 'if %b %c': + 'se %b %c', + 'if %b %c else %c': + 'se %b %c se non %c', + 'report %s': + 'reportar %s', + 'stop %stopChoices': + 'deter %stopChoices', + 'all': + 'todo', + 'this script': + 'este programa', + 'this block': + 'este bloque', + 'stop %stopOthersChoices': + 'deter %stopOthersChoices', + 'all but this script': + 'todos os programas agás este', + 'other scripts in sprite': + 'outros programas na figura', + 'pause all %pause': + 'deter todo %pause', + 'run %cmdRing %inputs': + 'executa %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'invoca %cmdRing %inputs', + 'call %repRing %inputs': + 'resultado de %repRing %inputs', + 'run %cmdRing w/continuation': + 'executa %cmdRing con continuación', + 'call %cmdRing w/continuation': + 'o resultado de %cmdRing con continuación', + 'warp %c': + 'Warp %c', + 'when I start as a clone': + 'cando comece como clon', + 'create a clone of %cln': + 'crear clon de %cln', + 'myself': + 'eu mesmo', + 'delete this clone': + 'eliminar este clon', + + // sensing: + 'touching %col ?': + 'tocando %col ?', + 'touching %clr ?': + 'tocanco na cor %clr ?', + 'color %clr is touching %clr ?': + 'cor %clr tocando na cor %clr ?', + 'ask %s and wait': + 'pregunta %s e agarda pola resposta', + 'what\'s your name?': + 'Como te chamas?', + 'answer': + 'resposta', + 'mouse x': + 'coordenada x do rato', + 'mouse y': + 'coordenada y do rato', + 'mouse down?': + 'botón do rato presionado?', + 'key %key pressed?': + 'tecla %key presionada?', + 'distance to %dst': + 'distancia ata %dst', + 'reset timer': + 'reinicia o cronómero', + 'timer': + 'cronómetro', + '%att of %spr': + '%att de %spr', + 'my %get': + '%get', + 'http:// %s': + 'o recurso http:// %s', + 'turbo mode?': + 'modo turbo?', + 'set turbo mode to %b': + 'cambiar modo turbo a %b', + + 'filtered for %clr': + 'filtrado para %clr', + 'stack size': + 'altura da pila', + 'frames': + 'marcos', + + // operators: + '%n mod %n': + 'o resto de %n ao dividilo por %n', + 'round %n': + 'arredondar %n', + '%fun of %n': + '%fun de %n', + 'pick random %n to %n': + 'número ao chou entre %n e %n', + '%b and %b': + '%b e %b', + '%b or %b': + '%b ou %b', + 'not %b': + 'non %b', + 'true': + 'verdadeiro', + 'false': + 'falso', + 'join %words': + 'xuntar %words', + 'split %s by %delim': + 'lista cos anacos de %s entre %delim', + 'hello': + 'Ola', + 'world': + 'mundo!', + 'letter %n of %s': + 'letra %n de %s', + 'length of %s': + 'lonxitude de %s', + 'unicode of %s': + 'código Unicode do carácter %s', + 'unicode %n as letter': + 'carácter cuxo código Unicode é %n', + 'is %s a %typ ?': + '%s é un/unha %typ ?', + 'is %s identical to %s ?': + '%s é idéntico a %s ?', + + 'type of %s': + 'tipo de %s', + + // variables: + 'Make a variable': + 'Crear unha variable', + 'Variable name': + 'Nome da variable', + 'Script variable name': + 'Nome da variable do programa', + 'Delete a variable': + 'Eliminar variable', + + 'set %var to %s': + 'fixar %var a %s', + 'change %var by %n': + 'engade a %var o valor %n', + 'show variable %var': + 'amosar variable %var', + 'hide variable %var': + 'agochar variable %var', + 'script variables %scriptVars': + 'crear as variables de programa %scriptVars', + + // lists: + 'list %exp': + 'lista %exp', + '%s in front of %l': + 'inserir %s ao principio de %l', + 'item %idx of %l': + 'elemento %idx de %l', + 'all but first of %l': + 'todo agás o primeiro elemento de %l', + 'length of %l': + 'lonxitude de %l', + '%l contains %s': + 'a lista %l contén %s', + 'thing': + 'cousa', + 'add %s to %l': + 'agregar %s á lista %l', + 'delete %ida of %l': + 'borrar %ida da lista %l', + 'insert %s at %idx of %l': + 'inserir %s na posición %idx de %l', + 'replace item %idx of %l with %s': + 'substituír %idx de %l por %s', + + // other + 'Make a block': + 'Crear un bloque', + + // menus + // snap menu + 'About...': + 'Acerca do Snap!...', + 'Reference manual': + 'Manual de referencia', + 'Snap! website': + 'Ir á web de Snap!', + 'Download source': + 'Descargar o código fonte', + 'Switch back to user mode': + 'Volver ao modo usuario/a', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'Desactivar menús de contexto do Morphic\n e mostrar menús amigables.', + 'Switch to dev mode': + 'Pasar a modo de desenvolvemento', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'Activar menús de contexto do Morphic non amigables.', + + // project menu + 'Project notes...': + 'Notas deste proxecto', + 'New': + 'Novo proxecto', + 'Open...': + 'Abrir un proxecto', + 'Save': + 'Gardar', + 'Save to disk': + 'Gardar no disco', + 'store this project\nin the downloads folder\n(in supporting browsers)': + 'Gardar este proxecto\nno directorio de descargas\n' + + '(em navegadores que o suportem).', + 'Save As...': + 'Gardar como...', + 'Import...': + 'Importar...', + 'file menu import hint': + 'Abrir un proxecto exportado,\nsubstituíndo o proxecto actual, ou\n' + + 'importar unha biblioteca de bloques, unha\n' + + 'vestimenta ou um son para o proxecto actual.', + 'Export project as plain text...': + 'Exportar proxecto como texto...', + 'Export project...': + 'Exportar proxecto...', + 'show project data as XML\nin a new browser window': + 'mostrar información do proxecto como XML\nnunha nova xanela', + 'Export blocks...': + 'Exportar bloques...', + 'show global custom block definitions as XML\nin a new browser window': + 'mostrar definicións de bloques globais personalizados como XML\nnunha nova xanela', + 'Unused blocks...': + 'Bloques non utilizados...', + 'find unused global custom blocks\nand remove their definitions': + 'Atopar bloques globais personalizados sen usar\ne borrar as súas definicións', + 'Remove unused blocks': + 'Borrar bloques non utilizados', + 'there are currently no unused\nglobal custom blocks in this project': + 'Actualmente non hai bloques globais\npersonalizados sen usar neste proxecto', + 'unused block(s) removed': + 'Borrados os bloques non utilizados', + 'Export summary...': + 'Sumario da exportación...', + 'open a new browser browser window\n with a summary of this project': + 'Abra o navegador cun resumo deste proxecto', + 'Contents': + 'Índice', + 'Kind of': + 'Do tipo de', + 'Part of': + 'Unha parte de', + 'Parts': + 'Partes', + 'Blocks': + 'Bloques', + 'For all Sprites': + 'Para todos os obxectos', + 'Import tools': + 'Importar ferramentas', + 'load the official library of\npowerful blocks': + 'cargar a biblioteca oficial de\nbloques', + 'Libraries...': + 'Bibliotecas...', + 'Import library': + 'Importar biblioteca...', + + // cloud menu + 'Login...': + 'Entrar na súa conta...', + 'Signup...': + 'Rexistrar unha nova conta...', + + // settings menu + 'Language...': + 'Língua...', + 'Zoom blocks...': + 'Ampliación dos bloques...', + 'Stage size...': + 'Tamaño do escenario...', + 'Stage size': + 'Tamaño do escenario', + 'Stage width': + 'Anchura do escenario', + 'Stage height': + 'Altura do escenario', + 'Default': + 'Normal', + 'Blurred shadows': + 'Sombras desenfocadas', + 'uncheck to use solid drop\nshadows and highlights': + 'Desmarcar para usar sombras\ne realces nítidos', + 'check to use blurred drop\nshadows and highlights': + 'Marcar para usar sombras\ne realces desenfocados', + 'Zebra coloring': + 'Coloración de cebra', + 'check to enable alternating\ncolors for nested blocks': + 'Marcar para alternar as\ncores de bloques aniñados', + 'uncheck to disable alternating\ncolors for nested block': + 'Desmarcar para deixar de alternar\nas cores de bloques aniñados', + 'Dynamic input labels': + 'Etiquetas de entrada dinámicas', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'Desmarcar para desactivar etiquetas\ndinámicas para entradas variádicas', + 'check to enable dynamic\nlabels for variadic inputs': + 'Marcar para activar etiquetas\ndinámicas para entradas variádicas', + 'Prefer empty slot drops': + 'Prefer empty slot drops', + 'settings menu prefer empty slots hint': + 'settings menu prefer empty slots hint', + 'uncheck to allow dropped\nreporters to kick out others': + 'uncheck to allow dropped\nreporters to kick out others', + 'Long form input dialog': + 'Forma longa da caixa de diálogo dos parámetros', + 'Plain prototype labels': + 'Etiquetas dos prototipos simples', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'Desmarcar para mostrar os símbolos (+)\nno texto dos prototipos dos bloques', + 'check to hide (+) symbols\nin block prototype labels': + 'Marcar para agochar os símbolos (+)\nno texto dos prototipos dos bloques', + 'check to always show slot\ntypes in the input dialog': + 'Marcar para mostrar sempre os tipos de espazos na caizxa de diálogo', + 'uncheck to use the input\ndialog in short form': + 'Desmarcar para usar a forma curta da caixa de diálogo', + 'Virtual keyboard': + 'Teclado virtual', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'Desmarcar para desactivar\no soporte para teclado virtual\npara dispositivos móbiles', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'Marcar para activar\no soporte para teclado virtual\npara dispositivos móbiles', + 'Input sliders': + 'Controis deslizantes de entradas', + 'uncheck to disable\ninput sliders for\nentry fields': + 'Desmarcar para desactivar\nos controis de entrada deslizantes.', + 'check to enable\ninput sliders for\nentry fields': + 'Marcar para activar\n os controis de entrada deslizantes', + 'Clicking sound': + 'Son de clic', + 'uncheck to turn\nblock clicking\nsound off': + 'Desmarcar para desactivar\n o son ao facer clic', + 'check to turn\nblock clicking\nsound on': + 'marcar para activar o son\n ao facer clic', + 'Animations': + 'Animacións', + 'uncheck to disable\nIDE animations': + 'Desmarcar para desactivar\nanimacións IDE', + 'check to enable\nIDE animations': + 'Marcar para activar\nanimacións IDE', + 'Turbo mode': + 'Modo turbo', + 'check to prioritize\nscript execution': + 'Marcar para priorizar a\nexecución dos programas', + 'uncheck to run scripts\nat normal speed': + 'Desmarcar para executar os\nprogramas á velocidade normal', + 'Flat design': + 'Deseño plano', + 'Keyboard Editing': + 'Edición usando teclado', + 'Table support': + 'Soporte de táboas', + 'Table lines': + 'Táboas con liñas', + 'Visible stepping': + 'trazado paso a paso visible', + 'Thread safe scripts': + 'Procesos seguros', + 'uncheck to allow\nscript reentrance': + 'Desmarcar para permitir a reentrada nos programas', + 'check to disallow\nscript reentrance': + 'Marcar para denegar a reentrada nos programas', + 'Prefer smooth animations': + 'Animacións suaves', + 'uncheck for greater speed\nat variable frame rates': + 'Desmarcar para aumentar a velocidade\npermitindo ritmos variables das tramas', + 'check for smooth, predictable\nanimations across computers': + 'marcar para obter animacións máis suaves\ne previsibles entre computadoras', + 'Flat line ends': + 'Extremos das liñas planos', + 'check for flat ends of lines': + 'Marcar para facer que os\nextremos das liñas sexan planos', + 'uncheck for round ends of lines': + 'Desmarcar para facer que os\nextremos das liñas sexan redondeados', + 'Inheritance support': + 'Soporte para herdanza', + + // inputs + 'with inputs': + 'con argumentos', + 'input names:': + 'nomes dos parámetros:', + 'Input Names:': + 'Parámetros:', + 'input list:': + 'Lista de entradas:', + + // context menus: + 'help': + 'axuda', + + // palette: + 'hide primitives': + 'Agochar bloques primitivos', + 'show primitives': + 'Mostrar bloques primitivos', + + // blocks: + 'help...': + 'axuda...', + 'relabel...': + 'renomear...', + 'duplicate': + 'duplicar', + 'make a copy\nand pick it up': + 'Facer unha copia do bloque\n e suxeitala', + 'only duplicate this block': + 'Duplicar só este bloque', + 'delete': + 'Eliminar', + 'script pic...': + 'Fotografía do programa...', + 'open a new window\nwith a picture of this script': + 'Abrir unha nova xanela\ncunha foto deste programa', + 'ringify': + 'Engadir anel', + 'unringify': + 'Eliminar anel', + 'transient': + 'transitorio', + 'uncheck to save contents\nin the project': + 'Desmarcar para gardar\no contido no proxecto', + 'check to prevent contents\nfrom being saved': + 'marcar para non gardar\no contido no proxecto', + + // custom blocks: + 'delete block definition...': + 'Eliminar definición do bloque...', + 'edit...': + 'Editar...', + + // sprites: + 'edit': + 'editar', + 'move': + 'mover', + 'detach from': + 'soltar de', + 'detach all parts': + 'soltar todas as partes', + 'export...': + 'exportar...', + + // stage: + 'show all': + 'mostrar todos', + 'pic...': + 'fotografía...', + 'open a new window\nwith a picture of the stage': + 'Abrir unha nova xanela cunha fotografía do escenario', + + // scripting area + 'clean up': + 'limpar', + 'arrange scripts\nvertically': + 'Organizar os programas verticalmente', + 'add comment': + 'Engadir comentario', + 'undrop': + 'Desfacer a última acción', + 'undo the last\nblock drop\nin this pane': + 'Desfacer a última acción nun bloque neste separador', + 'scripts pic...': + 'Fotografíar os programas', + 'open a new window\nwith a picture of all scripts': + 'Afre unha nova xanela cunha\nfotografía de todos os programas', + 'make a block...': + 'Crear un bloque...', + + // costumes + 'rename': + 'renomear', + 'export': + 'exportar', + 'rename costume': + 'Cal é o novo nome da vestimenta?', + + // sounds + 'Play sound': + 'Tocar son', + 'Stop sound': + 'Deter son', + 'Stop': + 'parar', + 'Play': + 'Tocar', + 'rename sound': + 'renomear son', + + // lists and tables + 'list view...': + 'Vista de lista...', + 'table view...': + 'vista de táboa...', + 'open in dialog...': + 'abrir na caixa de diálogo...', + 'reset columns': + 'reiniciar columnas', + 'items': + 'elementos', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'OK', + 'Cancel': + 'Cancelar', + 'Yes': + 'Si', + 'No': + 'Non', + + // help + 'Help': + 'Axuda', + + // zoom blocks + 'Zoom blocks': + 'Ampliación dos bloques', + 'build': + 'crear', + 'your own': + 'os teus prototipos', + 'blocks': + 'bloques', + 'normal (1x)': + 'normal (1x)', + 'demo (1.2x)': + 'demostración (1.2x)', + 'presentation (1.4x)': + 'presentación (1.4x)', + 'big (2x)': + 'grande (2x)', + 'huge (4x)': + 'enorme (4x)', + 'giant (8x)': + 'xigante (8x)', + 'monstrous (10x)': + 'monstruoso (10x)', + + // Project Manager + 'Untitled': + 'Sen título', + 'Open Project': + 'Abrir proxecto', + '(empty)': + '(baleiro)', + 'Saved!': + 'Gardado!', + 'Delete Project': + 'Eliminar proxecto', + 'Are you sure you want to delete': + 'Seguro de que queres eliminalo?', + 'rename...': + 'renomear...', + + // costume editor + 'Costume Editor': + 'Editor de vestimentas', + 'click or drag crosshairs to move the rotation center': + 'Clica e arrastra a mira para alterar o centro de rotación', + + // project notes + 'Project Notes': + 'Notas do proxecto', + + // new project + 'New Project': + 'Novo proxecto', + 'Replace the current project with a new one?': + 'Substituír este proxecto por un novo?', + + // save project + 'Save Project As...': + 'Gardar proxecto como...', + + // export blocks + 'Export blocks': + 'Exportar bloques', + 'Import blocks': + 'Importar bloques', + 'this project doesn\'t have any\ncustom global blocks yet': + 'Este proxecto aínda non ten\nningún bloque personalizado global', + 'select': + 'seleccionar', + 'none': + 'ningún', + + // variable dialog + 'for all sprites': + 'para todos os obxectos', + 'for this sprite only': + 'só para este obxecto', + + // block dialog + 'Change block': + 'Cambiar tipo de bloque', + 'Command': + 'Comando', + 'Reporter': + 'Función', + 'Predicate': + 'Predicado', + + // block editor + 'Block Editor': + 'Editor de bloques', + 'Apply': + 'Aplicar', + + // block deletion dialog + 'Delete Custom Block': + 'Eliminar bloque personalizado', + 'block deletion dialog text': + 'Queres eliminar este bloque e' + + 'todas as súas utilizacións?', + // input dialog + 'Create input name': + 'Crear parámetro', + 'Edit input name': + 'Editar parámetro', + 'Edit label fragment': + 'Editar etiqueta', + 'Title text': + 'Título', + 'Input name': + 'Parámetro', + 'Delete': + 'Eliminar', + 'Object': + 'Obxecto', + 'Number': + 'Número', + 'Text': + 'Texto', + 'List': + 'Lista', + 'Any type': + 'Calquera tipo', + 'Boolean (T/F)': + 'Booleano (V/F)', + 'Command\n(inline)': + 'Comando\n(en liña)', + 'Command\n(C-shape)': + 'Comando\n(forma C)', + 'Any\n(unevaluated)': + 'Calquera\n(sen avaliar)', + 'Boolean\n(unevaluated)': + 'Booleano\n(sen avaliar)', + 'Single input.': + 'Parámetro único', + 'Default Value:': + 'Valor por defecto:', + 'Multiple inputs (value is list of inputs)': + 'Múltiples argumentos (o valor do parámetro é a lista de argumentos)', + 'Upvar - make internal variable visible to caller': + 'Facer o parámetro visible ao invocador', + + // About Snap + 'About Snap': + 'Acerca de Snap', + 'Back...': + 'Atrás...', + 'License...': + 'Licenza...', + 'Modules...': + 'Módulos...', + 'Credits...': + 'Créditos...', + 'Translators...': + 'Tradutores', + 'License': + 'Licenza', + 'current module versions:': + 'Versións actuais dos módulos', + 'Contributors': + 'Contribuidores', + 'Translations': + 'Traduccións', + + // variable watchers + 'normal': + 'normal', + 'large': + 'grande', + 'slider': + 'potenciómetro', + 'slider min...': + 'mínimo do potenciómetro...', + 'slider max...': + 'máximo do potenciómetro...', + 'import...': + 'importar...', + 'Slider minimum value': + 'Valor mínimo do potenciómetro', + 'Slider maximum value': + 'Valor máximo do potenciómetro', + + // list watchers + 'length: ': + 'lonxitude: ', + + // coments + 'add comment here...': + 'Engade aquí un comentario...', + + // drow downs + // directions + '(90) right': + '(90) dereita', + '(-90) left': + '(-90) esquerda', + '(0) up': + '(0) arriba', + '(180) down': + '(180) abaixo', + + // collision detection + 'mouse-pointer': + 'punteiro do rato', + 'edge': + 'bordo', + 'pen trails': + 'riscos do lapis', + + // costumes + 'Turtle': + 'Tartaruga', + 'Empty': + 'baleiro', + + // graphical effects + 'color': + 'cor', + 'fisheye': + 'ollo de peixe', + 'whirl': + 'remuíño', + 'pixelate': + 'pixelar', + 'mosaic': + 'mosaico', + 'saturation': + 'saturación', + 'brightness': + 'brillo', + 'ghost': + 'pantasma', + 'negative': + 'negativo', + 'comic': + 'ondeado', + 'confetti': + 'confetti', + + // keys + 'space': + 'espazo', + 'up arrow': + 'frecha arriba', + 'down arrow': + 'frecha abaixo', + 'right arrow': + 'frecha dereita', + 'left arrow': + 'frecha esquerda', + 'any key': + 'calquera tecla', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'Nova...', + + // math functions + 'abs': + 'abs (valor absoluto)', + 'ceiling': + 'arredondar por riba', + 'floor': + 'arredondar por abaixo', + 'sqrt': + 'sqrt (raíz cadrada)', + 'sin': + 'sin (seno)', + 'cos': + 'cos (coseno)', + 'tan': + 'tan (tanxente)', + 'asin': + 'asin (arco-seno)', + 'acos': + 'acos (arco-coseno)', + 'atan': + 'atan (arco-tanxente)', + 'ln': + 'ln (logaritmo neperiano)', + 'e^': + 'e^ (exponencial)', + + // delimiters + 'letter': + 'letra', + 'whitespace': + 'espazo en branco', + 'line': + 'liña', + 'tab': + 'tabulador', + 'cr': + 'retorno de carro', + + // data types + 'number': + 'número', + 'text': + 'texto', + 'Boolean': + 'booleano', + 'list': + 'lista', + 'command': + 'comando', + 'reporter': + 'función', + 'predicate': + 'predicado', + 'sprite': + 'obxecto', + + // list indices + 'last': + 'o último elemento', + 'any': + 'un elemento ao chou', + + // attributes + 'neighbors': + 'os veciños', + 'self': + 'eu mesmo', + 'other sprites': + 'outros obxectos', + 'parts': + 'as partes', + 'anchor': + 'a áncora', + 'parent': + 'o proxenitor', + 'children': + 'os descendentes', + 'clones': + 'os clons', + 'other clones': + 'outros clons', + 'dangling?': + 'estás colgado?', + 'rotation x': + 'coordenada x de rotación', + 'rotation y': + 'coordenada y de rotación', + 'center x': + 'coordenada x do centro', + 'center y': + 'coordenada y do centro', + 'name': + 'nome', + 'stage': + 'escenario', + +// em falta no ficheiro lang-de.js + + 'delete %shd': + 'eliminar %shd', + 'Retina display support': + 'Soporte para pantalla de retina', + 'uncheck for lower resolution,\nsaves computing resources': + 'Desmarcar para menor resolución;\nmellora os recursos computacionais.', + 'check for higher resolution,\nuses more computing resources': + 'Marcar para maior resolución;\ngasta máis recursos computacionais.', + 'First-Class Sprites': + 'Obxectos de primeira clase', + 'uncheck to disable support\nfor first-class sprites': + 'Desmarcar para desactivar o soporte\nde obxectos de primeira clase.', + 'check to enable support\n for first-class sprite': + 'Marcar para activar o soporte\nde obxectos de primeira clase.', + 'Live coding support': + 'Soporte de programación ao vivo', + 'EXPERIMENTAL! check to enable\n live custom control structures': + 'EXPERIMENTAL! Marcar para activar estruturas\nde control personalizadas ao vivo.', + 'EXPERIMENTAL! uncheck to disable live\ncustom control structures': + 'EXPERIMENTAL! Desmarcar para desactivar estruturas\nde control personalizadas ao vivo.', + 'Persist linked sublist IDs': + 'Persistir ID de sublistas ligadas', + 'check to enable\nsaving linked sublist identities': + 'Marcar para activar o\nalmazenamento das identidades de sublistas ligadas.', + 'uncheck to disable\nsaving linked sublist identities': + 'Desmarcar para desactivar o\nalmazenamento das identidades de sublistas ligadas.', + 'grow': + 'aumentar', + 'shrink': + 'reducir', + 'flip ↔': + 'inverter ↔', + 'flip ↕': + 'inverter ↕', + 'Export all scripts as pic...': + 'Exportar todos os obxectos como fotografia…', + 'show a picture of all scripts\nand block definitions': + 'Mostra unha imaxe con todos\nos obxectos e defincións de bloques', + 'current %dates': + '%dates actuais', + 'year': + 'ano', + 'month': + 'mes', + 'date': + 'día', + 'day of week': + 'día da semana', + 'hour': + 'hora', + 'minute': + 'minuto', + 'second': + 'segundo', + 'time in milliseconds': + 'tempo (en milisegundos)', + 'find blocks...': + 'buscar bloques…', + 'costume name': + 'nome da vestimenta', + 'Open': + 'Abrir', + 'Share': + 'Compartir', + 'Snap!Cloud': + 'Snap!Nube', + 'Cloud': + 'Nube', + 'could not connect to:': + 'Non foi posible conectar con:', + 'Service:': + 'Servizo:', + 'login': + 'autenticación', + 'ERROR: INVALID PASSWORD': + 'ERRO: CONTRASINAL INVÁLIDO', + 'Browser': + 'Navegador', + 'Sign up': + 'Rexistar nova conta', + 'Signup': + 'Rexisto de nova conta', + 'Sign in': + 'Entrar', + 'Logout': + 'Saír', + 'Change Password...': + 'Cambiar contrasinal…', + 'Change Password': + 'Cambiar contrasinal', + 'Account created.': + 'Conta creada.', + 'An e-mail with your password\nhas been sent to the address provided': + 'Foi enviada unha mensaxe ao seu\nenderezo contendo o seu contrasinal.', + 'now connected.': + 'entrou.', + 'disconnected.': + 'saíu.', + 'Reset password': + 'Recuperar contrasinal', + 'Reset Password...': + 'Recuperar contrasinal…', + 'User name:': + 'Nome de usuario/a:', + 'Password:': + 'contrasinal:', + 'Old password:': + 'contrasinal actual:', + 'New password:': + 'Novo contrasinal:', + 'Repeat new password:': + 'Repita o contrasinal:', + 'Birth date:': + 'Data de nacemento:', + 'January': + 'Xaneiro', + 'February': + 'Febreiro', + 'March': + 'Marzo', + 'April': + 'Abril', + 'May': + 'Maio', + 'June': + 'Juño', + 'July': + 'Jullo', + 'August': + 'Agosto', + 'September': + 'Setembro', + 'October': + 'Outubro', + 'November': + 'Novembro', + 'December': + 'Decembro', + 'year:': + 'ano:', + ' or before': + ' ou antes', + 'E-mail address:': + 'Enderezo de correo electrónico:', + 'E-mail address of parent or guardian:': + 'Enderezo do titor ou titora:', + 'Terms of Service...': + 'Termos do Servizo…', + 'Privacy...': + 'Privacidade…', + 'I have read and agree\nto the Terms of Service': + 'Lin e declaro concordar\ncos Termos do Servizo', + 'stay signed in on this computer\nuntil logging out': + 'manterme autenticado neste\ncomputador ata que saia', + 'please fill out\nthis field': + 'Por favor encha\neste campo.', + 'User name must be four\ncharacters or longer': + 'O nome de usuario/a ten que ter\npolo menos catro caracteres.', + 'please provide a valid\nemail address': + 'Por favor indique un enderezo\nde correo electrónico válido.', + 'password must be six\ncharacters or longer': + 'O contrasinal ten que ter\npolo menos seis caracteres.', + 'passwords do\nnot match': + 'os contrasinais\nnon corresponden.', + 'please agree to\nthe TOS': + 'Por favor concorde cos\nTermos do Servizo.', + 'Examples': + 'Exemplos', + 'You are not logged in': + 'Aínda non se autenticou', + 'Updating\nproject list...': + 'Actualizando a\nlista de proxectos…', + 'Opening project...': + 'Abrindo o proxecto…', + 'Fetching project\nfrom the cloud...': + 'Obtendo o proxecto\nda nube…', + 'Saving project\nto the cloud...': + 'Gardando o proxecto\nna nube…', + 'Sprite Nesting': + 'Obxectos compostos', + 'uncheck to disable\nsprite composition': + 'Desmarcar para desactivar\na composición de obxectos.', + 'Codification support': + 'Soporte da produción de código', + 'check for block\nto text mapping features': + 'Desmarcar para funcionalidades\nde mapeamento entre bloques e texto.', + 'saved.': + 'gardado.', + 'options...': + 'opcións…', + 'read-only': + 'só lectura', + 'Input Slot Options': + 'Opcións de rañura de Entrada', + 'Enter one option per line.Optionally use "=" as key/value delimiter\ne.g.\n the answer=42': + 'Introduza unha opción por liña. Opcionalmente, use "=" como separador\nentre chave e valor, e.g.\n a resposta=42', + 'paint a new sprite': + 'Pintar un novo obxecto.', + 'Paint a new costume': + 'Pintar unha nova vestimenta.', + 'add a new Turtle sprite': + 'Engadir um novo obxecto.', + 'check for alternative\nGUI design': + 'Marcar para un deseño alternativo\nda interface gráfica co usuario/a.', + 'Rasterize SVGs': + 'Transformar SVG em mapas de bits', + 'check to rasterize\nSVGs on import': + 'Marcar para transformar os arquivos SVG\nen mapas de bits durante a importación.', + 'comment pic...': + 'fotografia do comentario…', + 'open a new window\nwith a picture of this comment': + 'Abrir unha nova xanela cunha\nfotografia deste comentario.', + 'undo': + 'desfacer', + 'Brush size': + 'Espesura do pincel', + 'Constrain proportions of shapes?\n(you can also hold shift)': + 'Preservar proposcións das formas?\n(tamén pode presionar shift)', + 'Eraser tool': + 'Borrador', + 'Paintbrush tool\n(free draw)': + 'Pincel\n(deseño libre)', + 'Line tool\n(shift: vertical/horizontal)': + 'Segmento de recta\n(shift: vertical/horizontal)', + 'Stroked Rectangle\n(shift: square)': + 'Rectángulo\n(shift: cadrado)', + 'Filled Rectangle\n(shift: square)': + 'Rectángulo preenchido\n(shift: cadrado)', + 'Stroked Ellipse\n(shift: circle)': + 'Elipse\n(shift: circunferencia)', + 'Filled Ellipse\n(shift: circle)': + 'Elipse preenchida\n(shift: círculo)', + 'Fill a region': + 'Balde de tinta', + 'Set the rotation center': + 'Estabelecer centro de rotación', + 'Pipette tool\n(pick a color anywhere)': + 'Pipeta\n(recoller unha cor en calquera sitio)', + 'Paint Editor': + 'Editor de Pintura', + 'square': + 'cadrado', + 'pointRight': + 'triángulo para a dereita', + 'gears': + 'roda dentada', + 'file': + 'ficheiro', + 'fullScreen': + 'pantalla completa', + 'normalScreen': + 'pantalla normal', + 'smallStage': + 'escenario pequeno', + 'normalStage': + 'escenario normal', + 'turtle': + 'tartaruga', + 'turtleOutline': + 'contorno de tartaruga', + 'pause': + 'pausa', + 'flag': + 'bandeira', + 'octagon': + 'octógono', + 'cloud': + 'nube', + 'cloudOutline': + 'contorno de nube', + 'cloudGradient': + 'nube con gradiente', + 'turnRight': + 'xirar á dereita', + 'turnLeft': + 'xirar á esquerda', + 'storage': + 'almacenaxe', + 'poster': + 'póster', + 'flash': + 'lóstrego', + 'brush': + 'pincel', + 'rectangle': + 'rectángulo', + 'rectangleSolid': + 'rectángulo preenchido', + 'circle': + 'circunferencia', + 'circleSolid': + 'círculo', + 'crosshairs': + 'mira', + 'paintbucket': + 'balde de tinta', + 'eraser': + 'borrador', + 'pipette': + 'pipeta', + 'speechBubble': + 'balón de fala', + 'speechBubbleOutline': + 'contorno de balón de fala', + 'arrowUp': + 'frecha arriba', + 'arrowUpOutline': + 'contorno de frecha arriba', + 'arrowLeft': + 'frecha esquerda', + 'arrowLeftOutline': + 'contorno de frecha esquerda', + 'arrowDown': + 'frecha abaixo', + 'arrowDownOutline': + 'contorno de frecha abaixo', + 'arrowRight': + 'frecha direita', + 'arrowRightOutline': + 'contorno de frecha direita', + 'robot': + 'robot', + 'turn pen trails into new costume...': + 'transformar trazos do lapis em nova vestimenta…', + 'turn all pen trails and stamps\ninto a new costume for the\ncurrently selected sprite': + 'Transforma todos os trazos do lapis\ne selos nunha nova vestimenta\n' + + 'do obxecto seleccionado neste momento', + 'pen': + 'lapis', + 'tip': + 'punta', + 'middle': + 'medio', + 'last changed': + 'cambiado por última vez en', + 'Are you sure you want to publish': + 'Queres publicar?', + 'Are you sure you want to unpublish': + 'Qreres deixar de publicar?', + 'Share Project': + 'Compartir o proxecto', + 'Unshare Project': + 'Deixar de compartir o proxecto', + 'sharing\nproject...': + 'compartindo\nproxecto…', + 'unsharing\nproject...': + 'deixando de compartir\nproxecto…', + 'shared.': + 'compartindo.', + 'unshared.': + 'deixado de compartir.', + 'Unshare': + 'Non compartir', + 'password has been changed.': + 'o seu contrasinal foi alterado.', + 'SVG costumes are\nnot yet fully supported\nin every browser': + 'vestimentas SVG aínda non\nson totalmente soportadas\nen todos os navegadores', + 'Save Project': + 'Gardar proxecto', + 'script pic with result...': + 'fotografia do programa incluíndo resultado…', + 'open a new window\nwith a picture of both\nthis script and its result': + 'Abrir unha nova xanela cunha\nfotografia tanto deste programa\ncomo do seu resultado.', + 'JavaScript function ( %mult%s ) { %code }': + 'función JavaScript ( %mult%s ) { %code }', + 'Select categories of additional blocks to add to this project.': + 'Seleccionar categorías de bloques adicionais a engadir a este proxecto.', + 'Import sound': + 'Importar son', + 'Select a sound from the media library': + 'Seleccionar un son da biblioteca de media.', + 'Import': + 'Importar', + 'Select a costume from the media library': + 'Seleccionar unha vestimenta da biblioteca de media.', + 'edit rotation point only...': + 'editar só o ponto de rotación…', + 'Export Project As...': + 'Exportar Proxecto Como…', + 'a variable of name \'': + 'Non existe unha variable «', + '\'\ndoes not exist in this context': + '»\nneste contexto', + '(temporary)': + '(temporal)', + 'expecting': + 'esperando', + 'input(s), but getting': + 'argumento(s), pero pasaron', + 'parent...': + 'proxenitor…', + 'current parent': + 'proxenitor actual', + 'Dragging threshold...': + 'Limiar de arrastre…', + 'Cache Inputs': + 'Memorizar entradas', + 'uncheck to stop caching\ninputs (for debugging the evaluator)': + 'Desmarcfar para parar de memorizar\nentradas (para depurar o avaliador).', + 'check to cache inputs\nboosts recursion': + 'Marcar para memorizar as entradas\n(acelera recursividade).', + 'Project URLs': + 'URL de proxecto', + 'check to enable\nproject data in URLs': + 'Marcar para activar datos\ndo proxecto nos URL.', + 'uncheck to disable\nproject data in URLs': + 'Desmarcar para desactivar\ndatos do proxecto nos URL.', + 'export project media only...': + 'Exportar só os media do proxecto…', + 'export project without media...': + 'Exportar proxecto sen os media…', + 'export project as cloud data...': + 'Exportar proxecto como datos da nube…', + 'open shared project from cloud...': + 'Abrir proxecto compartido a partir da nube…', + 'url...': + 'URL…', + 'Export summary with drop-shadows...': + 'Exportar resumo con sombreamento…', + 'open a new browser browser window\nwith a summary of this project\nwith drop-shadows on all pictures.\nnot supported by all browsers': + 'Abrir unha nova xanela no navegador\ncontendo un resumo deste proxecto\n' + + 'con sombreamento en todas as imaxes\n(non soportado en todos os navegadores)', + 'specify the distance the hand has to move\nbefore it picks up an object': + 'Especificar a distancia que a man tem que se\nmover antes de agarrar algún obxecto', + 'block variables...': + 'adicionar variables de bloque…', + 'remove block variables...': + 'eliminar variables de bloque…', + 'block variables': + 'con variables de bloque', + 'experimental -\nunder construction': + 'Experimental – en construción', + 'Table view': + 'Vista de táboa', + 'open in another dialog...': + 'abrir noutra caixa de diálogo…', + 'check for multi-column\nlist view support': + 'Marcar para soporte de\nvistas multicolumna de listas.', + 'uncheck to disable\nmulti-column list views': + 'Desmarcar para desactivar\nvistas multicolumna de listas.', + 'check for higher contrast\ntable views': + 'Marcar para vistas de\ntáboa con maior contraste.', + 'uncheck for less contrast\nmulti-column list views': + 'Desmarcar para vistas multicolumna\nde listas con menor contraste.', + '(in a new window)': + '(nunha nova xanela)', + 'save project data as XML\nto your downloads folder': + 'Gardar datos do proxecto como XML\nna súa carpeta de descargas.', + + // produção de código + 'map %cmdRing to %codeKind %code': + 'mapear %cmdRing no %codeKind %code', + 'map String to code %code': + 'mapear texto no código %code', + 'map %codeListPart of %codeListKind to code %code': + 'mapear %codeListPart de %codeListKind no código %code', + 'code of %cmdRing': + 'o código de %cmdRing', + 'delimiter': + 'delimitador', + 'collection': + 'colección', + 'variables': + 'variables', + 'parameters': + 'parámetros', + 'code': + 'código', + 'header': + 'encabezamento', + 'header mapping...': + 'mapeamento para encabezamento…', + 'code mapping...': + 'mapeamento para código…', + 'Code mapping': + 'Mapeamento para código', + 'Header mapping': + 'Mapeamento para encabezamento', + 'Enter code that corresponds to the block\'s definition. Use the formal parameter\nnames as shown and to reference the definition body\'s generated text code.': + 'Introduza o código correspondente á definición do bloque. Use os nomes dos parámetros\n' + + 'tal como mostrados e use para referenciar o código xerado da definición do corpo', + 'Enter code that corresponds to the block\'s definition. Choose your own\nformal parameter names (ignoring the ones shown).': + 'Introduza o código correspondente á definición do bloque. Escolla os seus proprios\n' + + 'nomes para os parámetros (ignorando os nomes mostrados).', + 'Enter code that corresponds to the block\'s operation (usually a single\nfunction invocation). Use <#n> to reference actual arguments as shown.': + 'Introduza o código que corresponda á operación do bloque (normalmente unha simple\n' + + 'invocación de rutina). Use <#n> para referenciar os argumentos tal como mostrado', + 'uncheck to disable\nkeyboard editing support': + 'Desmarcar para desactivar\na edición usando o teclado.', + 'check to enable\nkeyboard editing support': + 'Marcar para activar\na edición usando o teclado.', + 'Inheritance support': + 'Soporte para herdanza', + 'uncheck to disable\nsprite inheritance features': + 'Desmarcar para desactivar\nfuncionalidades de herdanza de obxectos.', + 'check for sprite\ninheritance features': + 'Marcar para activar\nfuncionalidades de herdanza de obxectos.' +}; + diff --git a/lang-hr.js b/lang-hr.js new file mode 100755 index 00000000..c343b759 --- /dev/null +++ b/lang-hr.js @@ -0,0 +1,1301 @@ +/* + + lang-hr.js + + Croatian translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2014 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.hr = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information 4.0.2.IMA NOVA VERZIJA + 'language_name': + 'Hrvatski', // the name as it should appear in the language menu + 'language_translator': + '\u017Deljko Hrvoj', // your name for the Translators tab + 'translator_e-mail': + 'zeljko.hrvoj@zg.t-com.hr', // optional + 'last_changed': + '2015-09-15', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'bez imena', + 'development mode': + 'razvojni na\u010Din', + + // categories: + 'Motion': + 'Kretanje', + 'Looks': + 'Izgled', + 'Sound': + 'Zvuk', + 'Pen': + 'Olovka', + 'Control': + 'Upravljanje', + 'Sensing': + 'Osjetila', + 'Operators': + 'Operatori', + 'Variables': + 'Varijable', + 'Lists': + 'Popisi', + 'Other': + 'Ostalo', + + // editor: + 'draggable': + 'povla\u010Div', + + // tabs: + 'Scripts': + 'Skripte', + 'Costumes': + 'Kostimi', + 'Sounds': + 'Zvukovi', + + // names: + 'Sprite': + 'Objekt', + 'Stage': + 'Scena', + + // rotation styles: + 'don\'t rotate': + 'ne rotiraj', + 'can rotate': + 'mo\u017Ee se rotirati', + 'only face left/right': + 'gledaj samo lijevo-desno', + + // new sprite button: + 'add a new sprite': + 'dodaj novi objekt', + + // tab help + 'costumes tab help': + 'Slike uvozi\u0161 povla\u010Denjem s jedne druge\n' + + 'web stranice ili ra\u010Dunala', + 'import a sound from your computer\nby dragging it into here': + 'Zvuk uvozi\u0161 tako, da ga povu\u010De\u0161 ovdje', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Scena je izabrana, ali je\njo\u0161 bez blokova ' + + 'kretanja', + + 'move %n steps': + 'pomakni se %n koraka', + 'turn %clockwise %n degrees': + 'okreni se %clockwise %n stupnjeva', + 'turn %counterclockwise %n degrees': + 'okreni se %counterclockwise %n stupnjeva', + 'point in direction %dir': + 'okreni se u smjeru %dir', + 'point towards %dst': + 'okreni se prema %dst', + 'go to x: %n y: %n': + 'kreni prema x: %n y: %n', + 'go to %dst': + 'kreni prema %dst', + 'glide %n secs to x: %n y: %n': + 'kli\u017Ei %n s do x: %n y: %n', + 'change x by %n': + 'promijeni x za %n', + 'set x to %n': + 'postavi x na %n', + 'change y by %n': + 'promijeni y za %n', + 'set y to %n': + 'postavi y na %n', + 'if on edge, bounce': + 'kad bude\u0161 na rubu, odbij se', + 'x position': + 'polo\u017Eaj x', + 'y position': + 'polo\u017Eaj y', + 'direction': + 'smjer', + + // looks: + 'switch to costume %cst': + 'prebaci na kostim %cst', + 'next costume': + 'idu\u0107i kostim', + 'costume #': + 'kostim br.', + 'say %s for %n secs': + 'reci %s tokom %n s', + 'say %s': + 'reci %s', + 'think %s for %n secs': + 'razmi\u0161ljaj %s tokom %n s', + 'think %s': + 'razmi\u0161ljaj %s', + 'Hello!': + 'Pozdrav!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + 'promijeni efekt %eff za %n', + 'set %eff effect to %n': + 'postavi efekt %eff na %n', + 'clear graphic effects': + 'isklju\u010Di grafi\u010Dke efekte', + 'change size by %n': + 'promijeni veli\u010Dinu za %n', + 'set size to %n %': + 'postavi veli\u010Dinu na %n %', + 'size': + 'veli\u010Dina', + 'show': + 'pogledaj', + 'hide': + 'sakrij', + 'go to front': + 'prebaci u prednji plan', + 'go back %n layers': + 'idi natrag %n slojeva', + + 'development mode \ndebugging primitives:': + 'razvojni na\u010Din \ndebagiranje osnovnih blokova', + 'console log %mult%s': + 'ispi\u0161i na konzolu %mult%s', + 'alert %mult%s': + 'upozorenje: %mult%s', + + // sound: + 'play sound %snd': + 'odsviraj zvuk %snd', + 'play sound %snd until done': + 'odsviraj zvuk %snd do kraja', + 'stop all sounds': + 'zaustavi sve zvukove', + 'rest for %n beats': + 'sviraj pauzu tokom %n udaraca', + 'play note %n for %n beats': + 'sviraj notu %n tokom %n udaraca', + 'change tempo by %n': + 'promijeni tempo za %n', + 'set tempo to %n bpm': + 'postavi tempo na %n udar./min.', + 'tempo': + 'Tempo', + + // pen: + 'clear': + 'obri\u0161i', + 'pen down': + 'olovku pritisni', + 'pen up': + 'olovku digni', + 'set pen color to %clr': + 'postavi boju olovke na %clr', + 'change pen color by %n': + 'promijeni boju olovke za %n', + 'set pen color to %n': + 'postavi boju olovke na %n', + 'change pen shade by %n': + 'promijeni sjenu olovke za %n', + 'set pen shade to %n': + 'postavi sjenu olovke na %n', + 'change pen size by %n': + 'promijeni veli\u010Dinu olovke za %n', + 'set pen size to %n': + 'postavi veli\u010Dinu olovke na %n', + 'stamp': + 'pe\u010Dat', + + // control: + 'when %greenflag clicked': + 'kad kliknem na %greenflag', + 'when %keyHat key pressed': + 'kad pritisnem tipku %keyHat', + 'when I am %interaction': + 'kad me %interaction', + 'clicked': + 'klikne\u0161', + 'pressed': + 'pritisne\u0161', + 'dropped': + 'ispusti\u0161', + 'mouse-entered': + 'mi\u0161 posjeti', + 'mouse-departed': + 'mi\u0161 napusti', + 'when I receive %msgHat': + 'kad \u010Dujem %msgHat', + 'broadcast %msg': + 'razglasi %msg', + 'broadcast %msg and wait': + 'razglasi %msg i \u010Dekaj odg.', + 'Message name': + 'Ime poruke', + 'message': + 'poruka', + 'any message': + 'bilo koja poruka', + 'wait %n secs': + '\u010Dekaj %n s', + 'wait until %b': + '\u010Dekaj dok je %b', + 'forever %c': + 'zauvijek %c', + 'repeat %n %c': + 'ponavljaj %n %c', + 'repeat until %b %c': + 'ponavljaj dok ne bude %b %c', + 'if %b %c': + 'ako %b %c', + 'if %b %c else %c': + 'ako %b %c ina\u010De %c', + 'report %s': + 'izvje\u0161\u0107e %s', + 'stop %stopChoices': + 'zaustavi %stopChoices', + 'all': + 'sve', + 'this script': + 'ovu skriptu', + 'this block': + 'ovaj blok', + 'stop %stopOthersChoices': + 'zaustavi %stopOthersChoices', + 'all but this script': + 'sve osim ove skripte', + 'other scripts in sprite': + 'ostale skripte objekta', + 'pause all %pause': + 'pauziraj sve %pause', + 'run %cmdRing %inputs': + 'pokreni %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'startaj %cmdRing %inputs', + 'call %repRing %inputs': + 'pozovi %repRing %inputs', + 'run %cmdRing w/continuation': + 'pokreni %cmdRing s nastavkom', + 'call %cmdRing w/continuation': + 'pozovi %cmdRing s nastavkom', + 'warp %c': + 'warp %c', + 'when I start as a clone': + 'kad startam kao klon', + 'create a clone of %cln': + 'stvori klona od %cln', + 'myself': + 'mene', + 'delete this clone': + 'obri\u0161i ovog klona', + + // sensing: + 'touching %col ?': + 'dodiruje %col ?', + 'touching %clr ?': + 'dodiruje %clr ?', + 'color %clr is touching %clr ?': + 'boja %clr dodiruje %clr ?', + 'ask %s and wait': + 'pitaj %s i \u010Dekaj', + 'what\'s your name?': + 'kako ti je ime?', + 'answer': + 'odgovor', + 'mouse x': + 'x polo\u017Eaj mi\u0161a', + 'mouse y': + 'y polo\u017Eaj mi\u0161a', + 'mouse down?': + 'gumb mi\u0161a pritisnut?', + 'key %key pressed?': + 'tipka %key pritisnuta?', + 'distance to %dst': + 'udaljenost do %dst', + 'reset timer': + 'resetiraj timer', + 'timer': + 'timer', + '%att of %spr': + '%att od %spr', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'turbo na\u010Din?', + 'set turbo mode to %b': + 'postavi turbo na\u010Din na %b', + + 'filtered for %clr': + 'filtrirano za %clr', + 'stack size': + 'veli\u010Dina stoga', + 'frames': + 'sli\u010Dice', + + // operators: + '%n mod %n': + '%n modul %n', + 'round %n': + 'round %n', + '%fun of %n': + '%fun od %n', + 'pick random %n to %n': + 'slu\u010Dajni broj od %n do %n', + '%b and %b': + '%b i %b', + '%b or %b': + '%b ili %b', + 'not %b': + 'ne %b', + 'true': + 'istina', + 'false': + 'la\u017E', + 'join %words': + 'spoji %words', + 'split %s by %delim': + 'razdvoji %s kod %delim', + 'hello': + 'pozdrav', + 'world': + 'svijet', + 'letter %n of %s': + 'slovo %n od %s', + 'length of %s': + 'duljina od %s', + 'unicode of %s': + 'unicode vrijednost od %s', + 'unicode %n as letter': + 'unicode %n kao znak', + 'is %s a %typ ?': + 'da li je %s tipa %typ ?', + 'is %s identical to %s ?': + 'da li je %s isti kao %s ?', + + 'type of %s': + 'tip od %s', + + // variables: + 'Make a variable': + 'Napravi varijablu', + 'Variable name': + 'Ime varijable', + 'Script variable name': + 'Ime skriptne varijable', + 'Delete a variable': + 'Obri\u0161i varijablu', + + 'set %var to %s': + 'postavi %var na %s', + 'change %var by %n': + 'promijeni varijablu %var za %n', + 'show variable %var': + 'prika\u017Ei varijablu %var', + 'hide variable %var': + 'sakrij varijablu %var', + 'script variables %scriptVars': + 'skriptne varijable %scriptVars', + + // lists: + 'list %exp': + 'popis %exp', + '%s in front of %l': + '%s ispred %l', + 'item %idx of %l': + 'element %idx od %l', + 'all but first of %l': + 'svi osim prvog od %l', + 'length of %l': + 'duljina od %l', + '%l contains %s': + '%l sadr\u017Ei %s', + 'thing': + 'stvar', + 'add %s to %l': + 'dodaj %s na %l', + 'delete %ida of %l': + 'obri\u0161i %ida od %l', + 'insert %s at %idx of %l': + 'ubaci %s na mjesto %idx od %l', + 'replace item %idx of %l with %s': + 'zamijeni element %idx od %l sa %s', + + // other + 'Make a block': + 'Napravi blok', + + // menus + // snap menu + 'About...': + 'O programu...', + 'Reference manual': + 'Korisni\u010Dki priru\u010Dnik', + 'Snap! website': + 'Snap! web stranica', + 'Download source': + 'Skini izvorni kod', + 'Switch back to user mode': + 'Prebaci natrag na korisni\u0161ki na\u010Din', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'onemogu\u0107i deep-Morphic\nkontekstne menije\ni koristi user-friendly menije', + 'Switch to dev mode': + 'Prebaci na razvojni na\u010Din', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'omogu\u0107i Morphic\nkontekstne menije\ni inspektore,\nnisu ba\u0161 user-friendly', + + // project menu + 'Project notes...': + 'Napomene o projektu...', + 'New': + 'Novi', + 'Open...': + 'Otvori...', + 'Save': + 'Spremi', + 'Save to disk': + 'Spremi na disk', + 'store this project\nin the downloads folder\n(in supporting browsers)': + 'spremi ovaj projekt u Download mapu' + + '(nije podr\u017Eano ba\u0161 kod svih preglednika)', + 'Save As...': + 'Spremi kao...', + 'Import...': + 'Uvezi...', + 'file menu import hint': + 'uvoz izvezenog projekta tj.\nbiblioteke s ' + + 'blokovima, ' + + 'kostimima i/ili zvukovima', + 'Export project as plain text...': + 'Izvezi projekt kao obi\u010Dni tekst...', + 'Export project...': + 'Izvoz projekta...', + 'show project data as XML\nin a new browser window': + 'prikaz projekta u XML obliku\nu novom prozoru preglednika', + 'Export blocks...': + 'Izvoz blokova', + 'show global custom block definitions as XML\nin a new browser window': + 'prikaz globalnih definicija korisni\u010Dkih blokova u XML obliku\nu novom prozoru preglednika', + 'Import tools': + 'Uvezi alate', + 'load the official library of\npowerful blocks': + 'u\u010Ditaj slu\u017Ebenu biblioteku\ns naprednim blokovima', + 'Libraries...': + 'Biblioteke...', + 'Import library': + 'Uvezi bibloteku', + + // cloud menu + 'Login...': + 'Prijava...', + 'Signup...': + 'Registracija ra\u010Duna...', + + // settings menu + 'Language...': + 'Jezik...', + 'Zoom blocks...': + 'Zumiraj blokove...', + 'Stage size...': + 'Veli\u010Dina scene...', + 'Stage size': + 'Veli\u010Dina scene', + 'Stage width': + '\u0160irina scene', + 'Stage height': + 'Visina scene', + 'Default': + 'Default', + 'Blurred shadows': + 'Zamagljene sjene', + 'uncheck to use solid drop\nshadows and highlights': + 'odzna\u010Di za \u010Dvrste\nsjene i osvjetljenja', + 'check to use blurred drop\nshadows and highlights': + 'ozna\u010Di za zamagljene\nsjene i osvjetljenja', + 'Zebra coloring': + 'Zebra boje', + 'check to enable alternating\ncolors for nested blocks': + 'ozna\u010Di za nijansiranje\nboja ugnje\u017E\u0111enih blokova', + 'uncheck to disable alternating\ncolors for nested block': + 'odzna\u010Di da ne nijansiram\nboje ugnje\u017E\u0111enih blokova', + 'Dynamic input labels': + 'Dinami\u010Dke oznake parametara', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'odzna\u010Di za isklju\u010Diti\ndinami\u010Dke oznake', + 'check to enable dynamic\nlabels for variadic inputs': + 'ozna\u010Di za omogu\u0107iti \ndinami\u010Dke oznake', + 'Prefer empty slot drops': + 'Preferiraj spu\u0161tanje u prazne utore', + 'settings menu prefer empty slots hint': + 'uklju\u010Di da se blokovi radije\nspu\u0161taju na slobodna mjesta pri ' + + 'postavljanju', + 'uncheck to allow dropped\nreporters to kick out others': + 'odzna\u010Di za to da ispu\u0161tene\nvrijednosti izbacuju druge', + 'Long form input dialog': + 'Du\u017Ei dijalog parametara', + 'Plain prototype labels': + 'Ozna\u010Davanje blokova', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'odzna\u010Di za uvijek prikazati (+) simbol\nu oznakama novih blokova', + 'check to hide (+) symbols\nin block prototype labels': + 'ozna\u010Di za sakriti (+) simbol\nu oznakama novih blokova', + 'check to always show slot\ntypes in the input dialog': + 'ozna\u010Di da bi uvijek pokazao\nsve opcije u dijalogu\nparametara', + 'uncheck to use the input\ndialog in short form': + 'odzna\u010Di za kratke dijaloge parametara', + 'Virtual keyboard': + 'Virtualna tipkovnica', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'odzna\u010Di da se\nne koristi virtualna\ntipkovnica za mobilne ure\u0111aje', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'ozna\u010Di da se\nmo\u017Ee koristiti virtualna\ntipkovnica za mobilne ure\u0111aje', + 'Input sliders': + 'Kliza\u010Di za parametre', + 'uncheck to disable\ninput sliders for\nentry fields': + 'odzna\u010Di da isklju\u010Di\u0161\nkliza\u010De vrijednosti\nkod parametara', + 'check to enable\ninput sliders for\nentry fields': + 'ozna\u010Di da uklju\u010Di\u0161\nkliza\u010De vrijednosti\n kod parametara', + 'Clicking sound': + 'Zvuk klikanja', + 'uncheck to turn\nblock clicking\nsound off': + 'odzna\u010Di da\nisklju\u010Di\u0161 zvuk\nklikanja bloka', + 'check to turn\nblock clicking\nsound on': + 'ozna\u010Di da\nuklju\u010Di\u0161 zvuk\nklikanja bloka', + 'Animations': + 'Animacije', + 'uncheck to disable\nIDE animations': + 'odzna\u010Di da onemogu\u0107i\u0161 IDE-\nanimacije', + 'Turbo mode': + 'Turbo na\u010Din', + 'check to prioritize\nscript execution': + 'ozna\u010Di da da\u0161 prioritet izvr\u0161avanju skripti', + 'uncheck to run scripts\nat normal speed': + 'odzna\u010Di za normalni prioritet izvr\u0161avanja skripti', + 'check to enable\nIDE animations': + 'ozna\u010Di da se omogu\u0107i\u0161 IDE-\nanimacije', + 'Flat design': + 'Flat design', + 'Keyboard Editing': + 'Ure\u0111ivanje tipkovnicom', + 'Thread safe scripts': + 'Skripte - vi\u0161estrukost', + 'uncheck to allow\nscript reentrance': + 'odzna\u010Di da dopusti\u0161\nvi\u0161ekratni poziv skripti', + 'check to disallow\nscript reentrance': + 'ozna\u010Di da zabrani\u0161\nvi\u0161ekratni poziv skripti', + 'Prefer smooth animations': + 'Preferiraj glatke animacije', + 'uncheck for greater speed\nat variable frame rates': + 'odzna\u010Di za ve\u0107u brzinu kod\npromjenljive frekvencije osvje\u017Eavanja', + 'check for smooth, predictable\nanimations across computers': + 'ozna\u010Di za glatke, predvidive animacije na raznim ra\u010Dunalima', + 'Flat line ends': + 'Ravni zavr\u0161eci linija', + 'check for flat ends of lines': + 'ozna\u010Di za ravne zavr\u0161etke linija', + 'uncheck for round ends of lines': + 'odzna\u010Di za zaobljene zavr\u0161etke linija', + 'Inheritance support': + 'Podr\u017Ei naslje\u0111ivanje', + + // inputs + 'with inputs': + 's parametrima', + 'input names:': + 'imena parametara:', + 'Input Names:': + 'Imena parametara:', + 'input list:': + 'popis parametara:', + + // context menus: + 'help': + 'Pomo\u0107', + + // palette: + 'hide primitives': + 'sakrij osnovne blokove', + 'show primitives': + 'poka\u017Ei osnovne blokove', + + // blocks: + 'help...': + 'pomo\u0107...', + 'relabel...': + 'promijeni tip...', + 'duplicate': + 'dupliciraj', + 'make a copy\nand pick it up': + 'napravi kopiju\ni pokupi', + 'only duplicate this block': + 'dupliciraj samo taj blok', + 'delete': + 'izbri\u0161i', + 'script pic...': + 'slikaj skriptu...', + 'open a new window\nwith a picture of this script': + 'otvori novi prozor\nsa slikom te skripte', + 'ringify': + 'opkru\u017Ei', + 'unringify': + 'odstrani obru\u010D', + + // custom blocks: + 'delete block definition...': + 'obri\u0161i definiciju bloka', + 'edit...': + 'uredi...', + + // sprites: + 'edit': + 'uredi', + 'move': + 'pomakni', + 'detach from': + 'odlijepi od', + 'detach all parts': + 'odlijepi sve dijelove', + 'export...': + 'izvezi...', + + // stage: + 'show all': + 'poka\u017Ei sve', + 'pic...': + 'slikaj...', + 'open a new window\nwith a picture of the stage': + 'otvori novi prozor\nsa slikom scene', + + // scripting area + 'clean up': + 'poslo\u017Ei', + 'arrange scripts\nvertically': + 'poslo\u017Ei skripte\nokomito', + 'add comment': + 'dodaj komentar', + 'undrop': + 'poni\u0161ti ispu\u0161tanje', + 'undo the last\nblock drop\nin this pane': + 'poni\u0161ti zadnje ispu\u0161tanje\nbloka u tom okviru', + 'scripts pic...': + 'slikaj skriptu...', + 'open a new window\nwith a picture of all scripts': + 'otvori novi prozor\nsa slikom svih skripti', + 'make a block...': + 'napravi blok...', + + // costumes + 'rename': + 'preimenuj', + 'export': + 'izvezi', + 'rename costume': + 'preimenuj kostim', + + // sounds + 'Play sound': + 'Sviraj zvuk', + 'Stop sound': + 'Zaustavi zvuk', + 'Stop': + 'Zaustavi', + 'Play': + 'Sviraj', + 'rename sound': + 'preimenuj zvuk', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'Ok', + 'Cancel': + 'Odustani', + 'Yes': + 'Da', + 'No': + 'Ne', + + // help + 'Help': + 'Pomo\u0107', + + // zoom blocks + 'Zoom blocks': + 'Zumiraj blok', + 'build': + 'napravi', + 'your own': + 'svoje vlastite', + 'blocks': + 'blokove', + 'normal (1x)': + 'normal (1x)', + 'demo (1.2x)': + 'demo (1.2x)', + 'presentation (1.4x)': + 'prezentacija (1.4x)', + 'big (2x)': + 'velik (2x)', + 'huge (4x)': + 'ogroman (4x)', + 'giant (8x)': + 'gigantski (8x)', + 'monstrous (10x)': + 'monstruozni (10x)', + + // Project Manager + 'Untitled': + 'Bez imena', + 'Open Project': + 'Otvori projekt', + '(empty)': + '(prazno)', + 'Saved!': + 'Spremljeno!', + 'Delete Project': + 'Izbrisati projekt', + 'Are you sure you want to delete': + 'Jesi siguran da \u017Eeli\u0161 izbrisati?', + 'rename...': + 'preimenuj...', + + // costume editor + 'Costume Editor': + 'Ure\u0111iva\u010D kostima', + 'click or drag crosshairs to move the rotation center': + 'Klikni ili povuci kri\u017Ei\u0107 za promjenu centra rotacije', + + // project notes + 'Project Notes': + 'Napomene o projektu', + + // new project + 'New Project': + 'Novi projekt', + 'Replace the current project with a new one?': + 'Zamijeniti trenutni projekt s novim?', + + // save project + 'Save Project As...': + 'Spremi projekt kao...', + + // export blocks + 'Export blocks': + 'Izvezi blokove', + 'Import blocks': + 'Uvezi blokove', + 'this project doesn\'t have any\ncustom global blocks yet': + 'ovaj projekt nema jo\u0161 nijedan korisni\u010Dki globalni blok', + 'select': + 'izaberi', + 'none': + 'nijedan', + + // variable dialog + 'for all sprites': + 'za sve objekte', + 'for this sprite only': + 'samo za taj objekt', + + // block dialog + 'Change block': + 'Promijeni blok', + 'Command': + 'Naredba', + 'Reporter': + 'Vrijednost', + 'Predicate': + 'Tvrdnja', + + // block editor + 'Block Editor': + 'Ure\u0111iva\u010D blokova', + 'Apply': + 'Primijeni', + + // block deletion dialog + 'Delete Custom Block': + 'Obri\u0161i korisni\u010Dki blok', + 'block deletion dialog text': + 'Da li da obri\u0161em taj blok\n' + + 'sa svim primjerima?', + + // input dialog + 'Create input name': + 'Kreiraj ime parametra', + 'Edit input name': + 'Uredi ime parametra', + 'Edit label fragment': + 'Uredi oznaku', + 'Title text': + 'Tekst naslova', + 'Input name': + 'Ime parametra', + 'Delete': + 'Obri\u0161i', + 'Object': + 'Objekt', + 'Number': + 'Broj', + 'Text': + 'Tekst', + 'List': + 'Popis', + 'Any type': + 'Bilo koji tip', + 'Boolean (T/F)': + 'Logi\u010Dki (T/F)', + 'Command\n(inline)': + 'Naredba\n(u liniji)', + 'Command\n(C-shape)': + 'Naredba\n(C-oblika)', + 'Any\n(unevaluated)': + 'Bilo koji\n(neizra\u010Dunat)', + 'Boolean\n(unevaluated)': + 'Logi\u010Dki\n(neizra\u010Dunat)', + 'Single input.': + 'Jedan parametar.', + 'Default Value:': + 'Default vrijednost:', + 'Multiple inputs (value is list of inputs)': + 'Vi\u0161e parametara (vrijednost je popis parametara)', + 'Upvar - make internal variable visible to caller': + 'Interna varijabla vidljiva pozivaocu', + + // About Snap + 'About Snap': + 'O Snap-u', + 'Back...': + 'Natrag...', + 'License...': + 'Licenca...', + 'Modules...': + 'Moduli...', + 'Credits...': + 'Suradnici...', + 'Translators...': + 'Prevoditelji', + 'License': + 'Licenca', + 'current module versions:': + 'Verzije u\u010Ditanih modula:', + 'Contributors': + 'Doprinosioci', + 'Translations': + 'Prijevodi', + + // variable watchers + 'normal': + 'normalno', + 'large': + 'veliko', + 'slider': + 'kliza\u010D', + 'slider min...': + 'kliza\u010D min...', + 'slider max...': + 'kliza\u010D max...', + 'import...': + 'uvezi...', + 'Slider minimum value': + 'Minimalna vrijednost kliza\u010Da', + 'Slider maximum value': + 'Maksimalna vrijednost kliza\u010Da', + + // list watchers + 'length: ': + 'duljina: ', + + // coments + 'add comment here...': + 'ovdje dodaj komentar...', + + // drow downs + // directions + '(90) right': + '(90) desno', + '(-90) left': + '(-90) lijevo', + '(0) up': + '(0) gore', + '(180) down': + '(180) dolje', + + // collision detection + 'mouse-pointer': + 'strelica mi\u0161a', + 'edge': + 'rub', + 'pen trails': + 'tragovi olovke', + + // costumes + 'Turtle': + 'Kostim zero', + 'Empty': + 'Prazno', + + // graphical effects + 'brightness': + 'svjetlina', + 'ghost': + 'prozirnost', + 'negative': + 'negativ', + 'comic': + 'komi\u010Dno', + 'confetti': + '\u0161areno', + + // keys + 'space': + 'razmaknica', + 'up arrow': + 'strelica gore', + 'down arrow': + 'strelica dolje', + 'right arrow': + 'strelica desno', + 'left arrow': + 'strelica lijevo', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'novo...', + + // math functions + 'abs': + 'abs', + 'floor': + 'floor', + 'sqrt': + 'sqrt', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // delimiters + 'letter': + 'slovo', + 'whitespace': + 'razmak', + 'line': + 'linija', + 'tab': + 'tabulator', + 'cr': + 'novi red', + + // data types + 'number': + 'brojka', + 'text': + 'tekst', + 'Boolean': + 'logi\u010Dki', + 'list': + 'popis', + 'command': + 'naredba', + 'reporter': + 'vrijednost', + 'predicate': + 'tvrdnja', + + // list indices + 'last': + 'zadnji', + 'any': + 'bilo koji' +}; diff --git a/lang-id.js b/lang-id.js new file mode 100644 index 00000000..6b0dd76b --- /dev/null +++ b/lang-id.js @@ -0,0 +1,1360 @@ +/* + + lang-id.js + + German translation for SNAP! + + written by Jens Mönig + + Copyright (C) 2016 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.id = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Bahasa Indonesia', // the name as it should appear in the language menu + 'language_translator': + 'Alexander Raphael Liu', // your name for the Translators tab + 'translator_e-mail': + 'raphaxander@gmail.com', // optional + 'last_changed': + '2016-5-2', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'tak bernama', + 'development mode': + 'mode percobaan', + + // categories: + 'Motion': + 'Gerakan', + 'Looks': + 'Penampilan', + 'Sound': + 'Suara', + 'Pen': + 'Pena', + 'Control': + 'Kontrol', + 'Sensing': + 'Sensor', + 'Operators': + 'Operator', + 'Variables': + 'Variabel', + 'Lists': + 'Daftar', + 'Other': + 'Lain-lain', + + // editor: + 'draggable': + 'bisa di drag', + + // tabs: + 'Scripts': + 'Skrip', + 'Costumes': + 'Kostum', + 'Sounds': + 'Suara', + + // names: + 'Sprite': + 'Karakter', + 'Stage': + 'Panggung', + + // rotation styles: + 'don\'t rotate': + 'jangan berputar', + 'can rotate': + 'bisa berputar', + 'only face left/right': + 'hanya boleh mengahadap kiri kanan', + + // new sprite button: + 'add a new sprite': + 'tambah sprite baru', + + // tab help + 'costumes tab help': + 'impor gambar dari site atau \n' + + 'sebiah file dengan cara men-drag file nya', + 'import a sound from your computer\nby dragging it into here': + 'impor sebuah suara dari komputer mu dengan men-drag file nya kesini', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Panggung terpilih: tidak ada primitif (balok)\n' + + 'gerak', + + 'move %n steps': + 'maju %n langkah', + 'turn %clockwise %n degrees': + 'berputar %clockwise %n derajad', + 'turn %counterclockwise %n degrees': + 'berputar %counterclockwise %n derajad', + 'point in direction %dir': + 'tunjuk ke arah %dir', + 'point towards %dst': + 'tunjuk ke arah %dst', + 'go to x: %n y: %n': + 'pergi ke x: %n y: %n', + 'go to %dst': + 'pergi ke %dst', + 'glide %n secs to x: %n y: %n': + 'meluncur %n dtk. ke x: %n y: %n', + 'change x by %n': + 'ubah x sebanyak %n', + 'set x to %n': + 'atur x ke %n', + 'change y by %n': + 'ubah y sebanyak %n', + 'set y to %n': + 'set y ke %n', + 'if on edge, bounce': + 'jika ada di ujung, melambung', + 'x position': + 'posisi x', + 'y position': + 'posisi y', + 'direction': + 'arah', + + // looks: + 'switch to costume %cst': + 'ganti ke kostum %cst', + 'next costume': + 'kostum selanjutnya', + 'costume #': + 'nomor kostum', + 'say %s for %n secs': + 'katakan %s untuk %n dtk.', + 'say %s': + 'katakan %s', + 'think %s for %n secs': + 'pikirkan %s untuk %n dtk.', + 'think %s': + 'pikirkan %s', + 'Hello!': + 'Halo!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + 'ubah efek %eff sebanyak %n', + 'set %eff effect to %n': + 'atur efek %eff ke %n', + 'clear graphic effects': + 'hapus efek grafik', + 'change size by %n': + 'ubah ukuran sebanyak %n', + 'set size to %n %': + 'atur ukuran ke %n %', + 'size': + 'ukuran', + 'show': + 'tampilkan', + 'hide': + 'sembunyikan', + 'go to front': + 'ke depan', + 'go back %n layers': + 'ke belakang %n lapisan', + + 'development mode \ndebugging primitives:': + 'primitif debugging \nmode percobaan', + 'console log %mult%s': + 'catat di konsol %mult%s', + 'alert %mult%s': + 'tampilkan popup: %mult%s', + + // sound: + 'play sound %snd': + 'mainkan suara %snd', + 'play sound %snd until done': + 'mainkan %snd sampai selesai', + 'stop all sounds': + 'hentikan semua suara', + 'rest for %n beats': + 'istriahat untuk %n ketukan', + 'play note %n for %n beats': + 'mainkan not %n selama %n ketukan', + 'change tempo by %n': + 'ubah tempo sebanyak %n', + 'set tempo to %n bpm': + 'atur tempo ke %n ketukan per menit', + 'tempo': + 'ketukan', + + // pen: + 'clear': + 'bersihkan layar', + 'pen down': + 'turunkan pena', + 'pen up': + 'naikan pena', + 'set pen color to %clr': + 'atur warna pena ke %clr', + 'change pen color by %n': + 'ubah warna pena sebanyak %n', + 'set pen color to %n': + 'atur warna pena ke %n', + 'change pen shade by %n': + 'ubah kegelapan pena sebanyak %n', + 'set pen shade to %n': + 'atur kegelapan pena ke %n', + 'change pen size by %n': + 'ubah ukuran pena sebesar %n', + 'set pen size to %n': + 'atur ukuran pena ke %n', + 'stamp': + 'stempel', + 'fill': + 'isi dengan cat', + + // control: + 'when %greenflag clicked': + 'Ketika %greenflag diklik', + 'when %keyHat key pressed': + 'Ketika %keyHat ditekan', + 'when I am %interaction': + 'Ketika %interaction', + 'clicked': + 'aku diklik', + 'pressed': + 'aku ditekan', + 'dropped': + 'aku dijatuhkan', + 'mouse-entered': + 'aku dimasuki tetikus/mouse', + 'mouse-departed': + 'tetikus/mouse keluar', + 'when %b': + 'Ketika %b', + 'when I receive %msgHat': + 'Ketika aku menerima %msgHat', + 'broadcast %msg': + 'beritakan %msg', + 'broadcast %msg and wait': + 'beritakan %msg dan tunggu', + 'Message name': + 'Nama pesan', + 'message': + 'Pesan', + 'any message': + 'pesan apapun', + 'wait %n secs': + 'tungu %n dtk.', + 'wait until %b': + 'tunggu sampai %b', + 'forever %c': + 'selamanya lakukan: %c', + 'repeat %n %c': + 'ulangi %n kali %c', + 'repeat until %b %c': + 'ulangi sampai %b %c', + 'if %b %c': + 'jika %b %c', + 'if %b %c else %c': + 'jika %b %c jika tidak %c', + 'report %s': + 'laporkan %s', + 'stop %stopChoices': + 'hentikan %stopChoices', + 'all': + 'semuanya', + 'this script': + 'skrip ini', + 'this block': + 'blok ini', + 'stop %stopOthersChoices': + 'hentikan %stopOthersChoices', + 'all but this script': + 'semua selain skrip ini', + 'other scripts in sprite': + 'skrip lain di karakter ini', + 'pause all %pause': + 'paus semua %pause', + 'run %cmdRing %inputs': + 'jalankan %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'luncurkan %cmdRing %inputs', + 'call %repRing %inputs': + 'panggil %repRing %inputs', + 'run %cmdRing w/continuation': + 'jalankan %cmdRing dengan kontinuasi', + 'call %cmdRing w/continuation': + 'panggil %cmdRing dengan kontinuasi', + 'warp %c': + 'bungkus %c', + 'when I start as a clone': + 'ketika aku mulai sebagai klon', + 'create a clone of %cln': + 'buat klon baru dari %cln', + 'myself': + 'diriku', + 'delete this clone': + 'hapus klon ini', + + // sensing: + 'touching %col ?': + 'menyentuh %col ?', + 'touching %clr ?': + 'menyentuh %clr ?', + 'color %clr is touching %clr ?': + 'warna %clr menyentuh %clr ?', + 'ask %s and wait': + 'tanya %s dan tunggu', + 'what\'s your name?': + 'Siapa namamu?', + 'answer': + 'jawaban', + 'mouse x': + 'posisi x tetikus/mouse', + 'mouse y': + 'posisi y tetikus/mouse', + 'mouse down?': + 'tetikus/mouse diklik?', + 'key %key pressed?': + 'kunci %key ditekan?', + 'distance to %dst': + 'jarak ke %dst', + 'reset timer': + 'atur ulang timer', + 'timer': + 'timer', + '%att of %spr': + '%att dari %spr', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'mode turbo nyala?', + 'set turbo mode to %b': + 'atur mode turbo ke %b', + + 'filtered for %clr': + 'disaring untuk %clr', + 'stack size': + 'ukuran tumpukan:', + 'frames': + 'jumlah frame:', + + // operators: + '%n mod %n': + '%n modulo %n', + 'round %n': + 'bulatkan %n', + '%fun of %n': + '%fun dari %n', + 'pick random %n to %n': + 'pilih angka acak dari %n ke %n', + '%b and %b': + '%b dan %b', + '%b or %b': + '%b atau %b', + 'not %b': + 'bukan %b', + 'true': + 'benar', + 'false': + 'salah', + 'join %words': + 'gabungkan %words', + 'split %s by %delim': + 'potong %s di setiap %delim', + 'hello': + 'halo', + 'world': + 'dunia', + 'letter %n of %s': + 'huruf %n dari %s', + 'length of %s': + 'panjang dari %s', + 'unicode of %s': + 'nilai unicode dari %s', + 'unicode %n as letter': + 'Unicode %n sebagai huruf', + 'is %s a %typ ?': + 'apakah %s sebuah %typ ?', + 'is %s identical to %s ?': + 'apakah %s identik dengan %s ?', + + 'type of %s': + 'tipe dari %s', + + // variables: + 'Make a variable': + 'Buat variabel', + 'Variable name': + 'Nama variabel', + 'Script variable name': + 'Skrip nama variabel', + 'Delete a variable': + 'Hapus variabel', + + 'set %var to %s': + 'atur %var ke %s', + 'change %var by %n': + 'ubah %var sebanyak %n', + 'show variable %var': + 'tampilkan variabel %var', + 'hide variable %var': + 'sembunyikan Variable %var', + 'script variables %scriptVars': + 'skrip variabel %scriptVars', + + // lists: + 'list %exp': + 'daftar %exp', + '%s in front of %l': + '%s di depan %l', + 'item %idx of %l': + 'barang %idx dari %l', + 'all but first of %l': + 'semua kecuali barang pertama dari %l', + 'length of %l': + 'panjang dari %l', + '%l contains %s': + '%l mempunyai %s', + 'thing': + 'barang', + 'add %s to %l': + 'tambahkan %s ke %l', + 'delete %ida of %l': + 'hapus %ida dari %l', + 'insert %s at %idx of %l': + 'tambahkan %s di %idx dari %l', + 'replace item %idx of %l with %s': + 'ganti barang %idx di %l dengan %s', + + // other + 'Make a block': + 'buat block baru', + + // menus + // snap menu + 'About...': + 'Tentang Snap!...', + 'Reference manual': + 'Panduan', + 'Snap! website': + 'Website Snap!', + 'Download source': + 'Download kodenya', + 'Switch back to user mode': + 'Kembali ke mode pengunna', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'matikan konteks menu \ndeep-morphic dan \ntunjukan konteks \nmenu yang ramah', + 'Switch to dev mode': + 'ganti ke mode percobaan', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'nyalahkan Morphic \ncontext menu\ndan inspektor, \ntidak ramah!', + + // project menu + 'Project notes...': + 'Catatan Projek...', + 'New': + 'Baru', + 'Open...': + 'Buka', + 'Save': + 'Simpan', + 'Save to disk': + 'Simpan ke komputer', + 'store this project\nin the downloads folder\n(in supporting browsers)': + 'simpan projek ini\ndi folder downloads\n' + + '(hanya untuk browser yang mendukung!)', + 'Save As...': + 'Simpan sebagai', + 'Import...': + 'Impor', + 'file menu import hint': + 'impor sebuah projek yang sudah diekspor atau\n ' + + 'pustaka blok,\n' + + 'kostum atau suara', + 'Export project as plain text...': + 'expor projek sebagai file .txt', + 'Export project...': + 'Expor projek', + 'show project data as XML\nin a new browser window': + 'tunjukan data projek sebagai XML\ndi jendela browser', + 'Export blocks...': + 'Expor balok...', + 'show global custom block definitions as XML\nin a new browser window': + 'tunjukan definisi balok kostum global\nsebagai xml di jendela baru', + 'Unused blocks...': + 'Balok yang tidak dipakai', + 'find unused global custom blocks\nand remove their definitions': + 'cari balok kostum global yang tidak dipakai\ndan hapus definisi mereka', + 'Remove unused blocks': + 'Hapus balok yang tidak dipakai', + 'there are currently no unused\nglobal custom blocks in this project': + 'sekarang tidak ada balok kostum global\nyang tidak dipakai', + 'unused block(s) removed': + 'blok yang tidak dipakai terhapus', + 'Export summary...': + 'Expor ringkasan...', + 'open a new browser browser window\n with a summary of this project': + 'buka jendela browser baru\ndengan ringkasan dari projek ini', + 'Contents': + 'Konten', + 'Kind of': + 'Seperti', + 'Part of': + 'Bagian dari', + 'Parts': + 'Bagian', + 'Blocks': + 'Balok', + 'For all Sprites': + 'Untuk semua karakter', + 'Import tools': + 'Impor peralatan', + 'load the official library of\npowerful blocks': + 'impor modul resmi dari\nblok hebat', + 'Libraries...': + 'Pustaka...', + 'Import library': + 'Import modul/pustaka', + + // cloud menu + 'Login...': + 'Masuk...', + 'Signup...': + 'Daftar', + + // settings menu + 'Language...': + 'Bahasa...', + 'Zoom blocks...': + 'Perbesar balok', + 'Stage size...': + 'Ukuran panggung', + 'Stage size': + 'Ukuran panggung', + 'Stage width': + 'Lebar panggung', + 'Stage height': + 'Tinggi panggung', + 'Default': + 'Normal', + 'Blurred shadows': + 'Bayangan blur', + 'uncheck to use solid drop\nshadows and highlights': + 'jangan centang untuk mengunakan\nbayangan dan cahaya saat jatuh', + 'check to use blurred drop\nshadows and highlights': + 'centang untuk mengunakan bayangan \ndan cahaya blur saat jatuh', + 'Zebra coloring': + 'Warna zebra', + 'check to enable alternating\ncolors for nested blocks': + 'centang untuk menyalahkan warna\nberganti di blok bersarang', + 'uncheck to disable alternating\ncolors for nested block': + 'jangan centang untu mematikan\nwarna berganti di balok bersarang', + 'Dynamic input labels': + 'Label input yang dinamik', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'jangan centang untuk mematika\nlabel dinamik untuk input variadik', + 'check to enable dynamic\nlabels for variadic inputs': + 'centang untuk menyalahkan label\ndinamik untu input variadik', + 'Prefer empty slot drops': + 'Memilih jatuh slot kosong', + 'settings menu prefer empty slots hint': + 'jangan centang untuk mengizinkan reporter yang jatuh menendang' + + 'yang lain', + 'uncheck to allow dropped\nreporters to kick out others': + 'jangan centang untuk mengizinkan reporter yang jatuh menendang' + + 'yang lain', + 'Long form input dialog': + 'Form input panjang', + 'Plain prototype labels': + 'Label prototipe/purwarupa polos', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'jangan centang untuk selalu menunjukan (+)\ndi label balok prototype', + 'check to hide (+) symbols\nin block prototype labels': + 'centang untuk menyembunykan (+)\ndi label balok prototype', + 'check to always show slot\ntypes in the input dialog': + 'centang untuk selalu menunjukan slot\ntipe di input dialog', + 'uncheck to use the input\ndialog in short form': + 'jangan centang untuk menggunakan input\ndialog dalam bentuk pendek', + 'Virtual keyboard': + 'Kibor virtual', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'jangan centang untuk mematikan\nkibor virtual untuk\n' + + 'alat mobile', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'centang untuk meyalahkan\nkibor virtual untuk\n' + + 'alat mobile', + 'Input sliders': + 'Slider input', + 'uncheck to disable\ninput sliders for\nentry fields': + 'jangan centang untuk mematikan\nslider input untuk\nbagian entry', + 'check to enable\ninput sliders for\nentry fields': + 'centang untuk menyalahkan\nslider input intuk\nbagian entry', + 'Clicking sound': + 'Suara klik', + 'uncheck to turn\nblock clicking\nsound off': + 'jangan centang untuk mematiakn\nsuara klik', + 'check to turn\nblock clicking\nsound on': + 'centang untuk menyalahkan\nsuara klik', + 'Animations': + 'Animasi', + 'uncheck to disable\nIDE animations': + 'jangan centang untuk mematikan\nanimasi IDE', + 'Turbo mode': + 'Mode turbo', + 'check to prioritize\nscript execution': + 'centang untuk mementingkan\neksekusi skrip', + 'uncheck to run scripts\nat normal speed': + 'jangan centang untuk menjalankan\nskrip pada kecepatan normal', + 'check to enable\nIDE animations': + 'centang untuk menyalahkan \nanimasi IDE', + 'Flat design': + 'Desain datar', + 'Keyboard Editing': + 'Editing melalui kibor', + 'Table support': + 'Dukungan tabel', + 'Table lines': + 'Garis tabel', + 'Thread safe scripts': + 'Skrip aman untuk thread', + 'uncheck to allow\nscript reentrance': + 'jangan centang, untuk\nmengizinkan skrip masuk ulang', + 'check to disallow\nscript reentrance': + 'klik untuk menghindari\nskrip masuk ulang', + 'Prefer smooth animations': + 'Memilih animasi lembut', + 'uncheck for greater speed\nat variable frame rates': + 'jangan centang untuk kecepatan lebih\ndan frame rate dinamis', + 'check for smooth, predictable\nanimations across computers': + 'centang untuk animasi lembut, bisa diramalkan\ndi komputer', + 'Flat line ends': + 'Garis ujung rata', + 'check for flat ends of lines': + 'centang untuk ujung rata dari\ngaris pena', + 'uncheck for round ends of lines': + 'matikan untuk ujung bulat dari\ngaris pena', + 'Inheritance support': + 'Dukungan inheritance', + + // inputs + 'with inputs': + 'dengan input', + 'input names:': + 'Nama input:', + 'Input Names:': + 'Nama input:', + 'input list:': + 'Dafta input:', + + // context menus: + 'help': + 'Tolong', + + // palette: + 'hide primitives': + 'sembunyikan primitif', + 'show primitives': + 'tampilkan primitif', + + // blocks: + 'help...': + 'bantuan...', + 'relabel...': + 'label ulang...', + 'duplicate': + 'gandakan', + 'make a copy\nand pick it up': + 'buat kopi\ndat ambil', + 'only duplicate this block': + 'hanya gandakan blok ini', + 'delete': + 'hapus', + 'script pic...': + 'gambar skrip...', + 'open a new window\nwith a picture of this script': + 'buka jendela baru\ndengan gambar dari skrip ini', + 'ringify': + 'cincinkan', + 'unringify': + 'hapus cincin', + 'transient': + 'sementara', + 'uncheck to save contents\nin the project': + 'jangan centang untuk\nmenymimpan konten di dalam\nprojek', + 'check to prevent contents\nfrom being saved': + 'centang untuk mencegah konten\ndisimpan', + + // custom blocks: + 'delete block definition...': + 'hapus definisi blok', + 'edit...': + 'edit...', + + // sprites: + 'edit': + 'edit', + 'move': + 'bergerak', + 'detach from': + 'lepaskan dari', + 'detach all parts': + 'lepaskan semua bagian', + 'export...': + 'expor...', + + // stage: + 'show all': + 'Tunjukan semua', + 'pic...': + 'expor gambar...', + 'open a new window\nwith a picture of the stage': + 'buka jendela baru\ndengan gambar dari panggung', + + // scripting area + 'clean up': + 'rapikan', + 'arrange scripts\nvertically': + 'urutkan skripnya\nsecara vertikal', + 'add comment': + 'tambahkan komen', + 'undrop': + 'undo jatuhkan', + 'undo the last\nblock drop\nin this pane': + 'undo mendrag\ndan jatuhkan\nblok terakhir\ndi panel ini', + 'scripts pic...': + 'Gambar skrip', + 'open a new window\nwith a picture of all scripts': + 'buka jendela baru\ndengan gambar semua skrip', + 'make a block...': + 'Buat blok baru.', + + // costumes + 'rename': + 'namakan ulang', + 'export': + 'expor', + 'rename costume': + 'namakan ulang kostum', + + // sounds + 'Play sound': + 'Mainkan suara', + 'Stop sound': + 'Hentikan suara', + 'Stop': + 'Berhenti', + 'Play': + 'mainkan bunyi', + 'rename sound': + 'namakan ulang bunyi', + + // lists and tables + 'list view...': + 'tampilan daftar', + 'table view...': + 'tampilan tabel', + 'open in dialog...': + 'buka di dialog...', + 'reset columns': + 'atur ulang kolum', + 'items': + 'barang', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'oke', + 'Cancel': + 'Batalkan', + 'Yes': + 'Ya', + 'No': + 'tidak', + + // help + 'Help': + 'Bantuan', + + // zoom blocks + 'Zoom blocks': + 'balok zoom', + 'build': + 'bangun', + 'your own': + 'punyamu', + 'blocks': + 'balok', + 'normal (1x)': + 'normal (1x)', + 'demo (1.2x)': + 'demo (1.2x)', + 'presentation (1.4x)': + 'presentasi (1.4x)', + 'big (2x)': + 'gro\u00df (2x)', + 'huge (4x)': + 'besar sekali (4x)', + 'giant (8x)': + 'raksasa (8x)', + 'monstrous (10x)': + 'sangat besar (10x)', + + // Project Manager + 'Untitled': + 'Tak bernama', + 'Open Project': + 'Buka projek', + '(empty)': + '(kosong)', + 'Saved!': + 'Tersimpan!', + 'Delete Project': + 'Hapus projek', + 'Are you sure you want to delete': + 'Apakah kamu yakin mau menghapus?', + 'rename...': + 'Namakan ulang...', + + // costume editor + 'Costume Editor': + 'Editor kostum', + 'click or drag crosshairs to move the rotation center': + 'Kilk atau drag crosshair nya untuk megerakan pusat rotasi', + + // project notes + 'Project Notes': + 'Catatan projek', + + // new project + 'New Project': + 'Projek baru', + 'Replace the current project with a new one?': + 'Ganti projek yang sudah ada denagn yang baru?', + + // save project + 'Save Project As...': + 'Simpan projek sebagai', + + // export blocks + 'Export blocks': + 'Expor balok', + 'Import blocks': + 'Impor balok', + 'this project doesn\'t have any\ncustom global blocks yet': + 'projek ini sepertinya tidak \npunya blok global buatan sendiri', + 'select': + 'pilih', + 'none': + 'tidak ada', + + // variable dialog + 'for all sprites': + 'untuk semua sprite', + 'for this sprite only': + 'hanya untuk sprite ini', + + // block dialog + 'Change block': + 'Ganti blok', + 'Command': + 'Perintah', + 'Reporter': + 'Pelapor', + 'Predicate': + 'Predikat', + + // block editor + 'Block Editor': + 'Editor blok', + 'Apply': + 'Aplikasikan', + + // block deletion dialog + 'Delete Custom Block': + 'Hapus blok', + 'block deletion dialog text': + 'Apakah anda yaking anda mau menghapus\n' + + 'blok ini dan instans-nya?', + + // input dialog + 'Create input name': + 'Buat nama input', + 'Edit input name': + 'Edit nama input', + 'Edit label fragment': + 'Edit bagian label', + 'Title text': + 'Teks judul', + 'Input name': + 'Nama input', + 'Delete': + 'Hapus', + 'Object': + 'Objek', + 'Number': + 'Angka', + 'Text': + 'Teks', + 'List': + 'Daftar', + 'Any type': + 'Tipe apapun', + 'Boolean (T/F)': + 'Boolean (B/S)', + 'Command\n(inline)': + 'Perintah', + 'Command\n(C-shape)': + 'Perintah\n(bentuk-C)', + 'Any\n(unevaluated)': + 'Apapun\n(tidak dievaluasi)', + 'Boolean\n(unevaluated)': + 'Boolean\n(tidak dievaluasi)', + 'Single input.': + 'Input tunggal.', + 'Default Value:': + 'Nilai default:', + 'Multiple inputs (value is list of inputs)': + 'Input multipel (nilai adalah daftar input)', + 'Upvar - make internal variable visible to caller': + 'Upvar - membuat var internal mirip dengan pemanggil', + + // About Snap + 'About Snap': + 'Tentang Snap', + 'Back...': + 'Kembali...', + 'License...': + 'Lisensi...', + 'Modules...': + 'Modul...', + 'Credits...': + 'Kredit...', + 'Translators...': + 'Penerjemah', + 'License': + 'Lisensi', + 'current module versions:': + 'Versi komponen sekarang', + 'Contributors': + 'Kontributor', + 'Translations': + 'Terjemahan', + + // variable watchers + 'normal': + 'normal', + 'large': + 'besar', + 'slider': + 'slider', + 'slider min...': + 'Minimum slider...', + 'slider max...': + 'Maksimum slider...', + 'import...': + 'Impor...', + 'Slider minimum value': + 'Nilai minimum slider:', + 'Slider maximum value': + 'Nilai maksimum slider:', + + // list watchers + 'length: ': + 'panjang: ', + + // coments + 'add comment here...': + 'tambahkan komen disini...', + + // drow downs + // directions + '(90) right': + '(90) kanan', + '(-90) left': + '(-90) kiri', + '(0) up': + '(0) atas', + '(180) down': + '(180) bawah', + + // collision detection + 'mouse-pointer': + 'penunjuk tetikus/mouse', + 'edge': + 'ujung', + 'pen trails': + 'jejak pena', + + // costumes + 'Turtle': + 'kura-kura', + 'Empty': + 'kosong', + + // graphical effects + 'brightness': + 'kecerahan', + 'ghost': + 'keburaman', + 'negative': + 'negatif', + 'comic': + 'komik', + 'confetti': + 'konfetti', + + // keys + 'space': + 'spasi', + 'up arrow': + 'panah bawah', + 'down arrow': + 'panah atas', + 'right arrow': + 'panah kanan', + 'left arrow': + 'panah kiri', + 'any key': + 'tombol apapun', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'Neu...', + + // math functions + 'abs': + 'nilai absolut', + 'ceiling': + 'bulatkan keatas', + 'floor': + 'bulatkan kebawah', + 'sqrt': + 'akar', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // delimiters + 'letter': + 'huruf', + 'whitespace': + 'spasi', + 'line': + 'garis', + 'tab': + 'indentasi', + 'cr': + 'cr', + + // data types + 'number': + 'Zahl', + 'text': + 'Text', + 'Boolean': + 'Boole', + 'list': + 'Liste', + 'command': + 'perintah', + 'reporter': + 'pelapor', + 'predicate': + 'predikat', + + // list indices + 'last': + 'terakhir', + 'any': + 'apapun' +}; diff --git a/lang-it.js b/lang-it.js index 85600a07..84d85435 100644 --- a/lang-it.js +++ b/lang-it.js @@ -181,11 +181,11 @@ SnapTranslator.dict.it = { 'language_name': 'Italiano', // the name as it should appear in the language menu 'language_translator': - 'Stefano Federici, Alberto Firpo', // your name for the Translators tab + 'Stefano Federici, Alberto Firpo, Massimo Ghisalberti', // your name for the Translators tab 'translator_e-mail': - 's_federici@yahoo.com, albertofirpo12@gmail.com', // optional + 's_federici@yahoo.com, albertofirpo12@gmail.com, zairik@gmail.com', // optional 'last_changed': - '2015-01-12', // this, too, will appear in the Translators tab + '2016-10-31', // this, too, will appear in the Translators tab // GUI // control bar: @@ -410,12 +410,28 @@ SnapTranslator.dict.it = { 'porta dimensione penna a %n', 'stamp': 'timbra', + 'fill': + 'riempi', // control: + 'when %b': + 'quando %b', 'when %greenflag clicked': 'quando si clicca su %greenflag', 'when %keyHat key pressed': 'quando si preme il tasto %keyHat', + 'when I am %interaction': + 'quando sono %interaction', + 'clicked': + 'cliccato', + 'pressed': + 'premuto', + 'dropped': + 'lasciato', + 'mouse-entered': + 'il mouse entra', + 'mouse-departed': + 'il mouse esce', 'when I am clicked': 'quando vengo cliccato', 'when I receive %msgHat': @@ -448,7 +464,12 @@ SnapTranslator.dict.it = { 'risultato %s', 'stop block': 'ferma il blocco', - 'all': + 'stop %stopOthersChoices': + 'ferma %stopOthersChoices', + 'stop %stopChoices': + 'ferma %stopChoices', + + 'all': 'tutti', 'this script': 'questo script', @@ -514,12 +535,34 @@ SnapTranslator.dict.it = { 'cronometro', '%att of %spr': '%att di %spr', + 'my %get': + 'attributo %get', 'http:// %s': 'leggi pagina web http:// %s', 'turbo mode?': 'modalit\u00E0 turbo attiva', 'set turbo mode to %b': 'porta modalit\u00E0 turbo a %b', + + 'current %dates': + '%dates attuale', + + 'year': + 'anno', + 'month': + 'mese', + 'date': + 'giorno', + 'hour': + 'ora', + 'minute': + 'minuto', + 'second': + 'secondo', + 'time in milliseconds': + 'ora in millisecondi', + 'day of week': + 'giorno della settimana', 'filtered for %clr': 'selezionati per colore %clr', @@ -567,9 +610,10 @@ SnapTranslator.dict.it = { '%s \u00E8 di tipo %typ', 'is %s identical to %s ?': '%s \u00E8 identico a %s ?', - 'type of %s': 'tipo di %s', + 'JavaScript function ( %mult%s ) { %code }': + 'funzione JavaScript ( %mult%s ) { %code }', // variables: 'Make a variable': @@ -639,6 +683,11 @@ SnapTranslator.dict.it = { 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': 'Abilita i menu contestuali\ndi Morphic e l\'inspector,\n non user-friendly', + 'Export summary...': + 'Esporta sommario...', + 'open a new browser browser window\n with a summary of this project': + 'apre una nuova finestra del browser\ncon un sommario del progetto', + // project menu 'Project notes...': 'Note di Progetto...', @@ -766,7 +815,11 @@ SnapTranslator.dict.it = { 'check to enable\nIDE animations': 'abilitare per nconsentire\nanimazioni dell\u0027IDE', 'Thread safe scripts': - 'Script thread safe', + 'Script sicuri per i thread', + 'check to disallow\nscript reentrance': + 'attivare per disabilitare\nla \'rientranza\' degli script', + 'uncheck to allow\nscript reentrance': + 'disattivare per abilitare\nla \'rientranza\' degli script', 'uncheck to allow\nscript reentrancy': 'disabilitare per permettere agli script di rientrare', 'check to disallow\nscript reentrancy': @@ -777,14 +830,55 @@ SnapTranslator.dict.it = { 'disabilitare per massima velocità\na framerate variabile', 'check for smooth, predictable\nanimations across computers': 'abilitare per avere animazioni\nfluide su tutti i computer', - 'Flat line ends': + 'Flat line ends': 'fine linea piana', 'check for flat ends of lines': 'abilitare per fine linea netti', 'uncheck for round ends of lines': 'disabilitare per fine linea arrotondati', - - + 'Inheritance support': + 'Supporto ereditarietà degli sprite', + 'check for sprite\ninheritance features': + 'attivare per\n la ereditarietà degli sprite', + 'uncheck to disable\nsprite inheritance features': + 'disattivare per rimuovere\n la ereditarietà degli sprite', + 'Codification support': + 'Supporto per il codice nei blocchi', + 'check for block\nto text mapping features': + 'attivare per il supporto\n al codice nei blocchi', + 'uncheck to disable\nblock to text mapping features': + 'disattivare per disabilitare\n il supporto al codice nei blocchi', + 'Flat design': + 'Aspetto piatto interfaccia', + 'check for alternative\nGUI design': + 'attivare per una interfaccia alternativa', + 'uncheck for default\nGUI design': + 'disattivare per la interfaccia normale', + 'Keyboard Editing': + 'Modifica della tastiera', + 'check to enable\nkeyboard editing support': + 'attivare per la modifica della tastiera', + 'uncheck to disable\nkeyboard editing support': + 'disattivare per la modifica della tastiera', + 'Table support': + 'Supporto per le tabelle', + 'Table lines': + 'Tabelle con linee', + 'Visible stepping': + 'Evidenzia esecuzione', + 'uncheck to turn off\nvisible stepping': + 'Deseleziona per disattivare la\nevidenziazione dell\'esecuzione', + 'check to turn on\n visible stepping (slow)': + 'Seleziona per attivare la\nevidenziazione dell\'esecuzione', + 'check for multi-column\nlist view support': + 'attiva la vista multicolonna', + 'uncheck to disable\nmulti-column list views': + 'disattiva la vista multicolonna', + 'check for higher contrast\ntable views': + 'attivare per un maggior contrasto', + 'uncheck for less contrast\nmulti-column list views': + 'disattivare per un minor contrasto', + // inputs 'with inputs': 'con argomenti', @@ -1287,5 +1381,87 @@ SnapTranslator.dict.it = { 'last': 'ultimo', 'any': - 'qualunque' + 'qualunque', + + // attributes + 'neighbors': + 'vicinato', + 'self': + 'me stesso', + 'other sprites': + 'altri sprite', + 'parts': + 'parti', + 'anchor': + 'ancora', + 'parent': + 'genitore', + 'children': + 'figli', + 'clones': + 'cloni', + 'other clones': + 'altri cloni', + 'dangling?': + 'pendente?', + 'rotation x': + 'rotazione x', + 'rotation y': + 'rotazione y', + 'center x': + 'centro x', + 'center y': + 'centro y', + 'name': + 'nome', + 'stage': + 'stage', + + // Paint.js + 'Paint editor': + 'Editor grafico', + 'undo': + 'annulla', + 'Paintbrush tool\n(free draw)': + 'Pennello (disegno libero)', + 'Stroked Rectangle\n(shift: square)': + 'Rettangolo\n(shift: quadrato)', + 'Stroked Ellipse\n(shift: circle)': + 'Ellisse\n(shift: cerchio)', + 'Eraser tool': + 'Gomma per cancellare', + 'Set the rotation center': + 'imposta centro di rotazione', + 'Line tool\n(shift: vertical/horizontal)': + 'Linea\n(shift: verticale/orizzontale)', + 'Filled Rectangle\n(shift: square)': + 'Rettangolo pieno\n(shift: quadrato)', + 'Filled Ellipse\n(shift: circle)': + 'Ellisse piena\n(shift: cerchio)', + 'Fill a region': + 'Riempi un\'area', + 'Pipette tool\n(pick a color anywhere)': + 'contagocce\n(preleva un colore dovunque)', + 'grow': + 'ingrandisci', + 'shrink': + 'rimpicciolisci', + 'flip \u2194': + 'capovolgi ↔ ', + 'flip \u2195': + 'capovolgi ↕', + 'Brush size': + 'dimensione del pennello', + 'Constrain proportions of shapes?\n(you can also hold shift)': + 'Costringi le proporzioni della figura?\n(puoi tenere premuto il tasto: shift)', + + // thread.js + 'a variable of name \'': + 'una variabile di nome \'', + '\'\ndoes not exist in this context': + '\'\nnon esiste in questo contesto', + 'expecting': + 'aspettando', + 'input(s), but getting': + 'entra(s), ma arriva' }; diff --git a/lang-nl.js b/lang-nl.js index 630e49fb..7eef046a 100644 --- a/lang-nl.js +++ b/lang-nl.js @@ -179,7 +179,7 @@ SnapTranslator.dict.nl = { 'translator_e-mail': 'sjoerddirk@fromScratchEd.nl, frank.sierens@telenet.be', // optional 'last_changed': - '2013-08-12', // this, too, will appear in the Translators tab + '2015-12-15', // this, too, will appear in the Translators tab // GUI // control bar: @@ -990,7 +990,7 @@ SnapTranslator.dict.nl = { 'Command\n(C-shape)': 'Opdracht\n(C-vorm)', 'Any\n(unevaluated)': - 'Alle\n(onge\u00EBvalueerd)', + 'Willekeurig\n(onge\u00EBvalueerd)', 'Boolean\n(unevaluated)': 'Booleaans\n(onge\u00EBvalueerd)', 'Single input.': @@ -1211,6 +1211,6 @@ SnapTranslator.dict.nl = { 'last': 'laatste', 'any': - 'alle' + 'willekeurig' }; diff --git a/lang-pl.js b/lang-pl.js index c0c4c49f..9e948488 100644 --- a/lang-pl.js +++ b/lang-pl.js @@ -461,12 +461,20 @@ SnapTranslator.dict.pl = { 'je\u017Celi %b to %c w przeciwnym razie %c', 'report %s': 'zwr\u00F3\u0107 %s', - 'stop block': - 'zatrzymaj ten blok', - 'stop script': - 'zatrzymaj ten skrypt', - 'stop all %stop': - 'zatrzymaj wszystko %stop', + 'stop %stopChoices': + 'zatrzymaj %stopChoices', + 'all': + 'wszystko', + 'this script': + 'ten skrypt', + 'this block': + 'ten blok', + 'stop %stopOthersChoices': + 'zatrzymaj %stopOthersChoices', + 'all but this script': + 'wszystko oprócz tego skryptu', + 'other scripts in sprite': + 'inne skrypty tego duszka', 'pause all %pause': 'pauzuj wszystko %pause', 'run %cmdRing %inputs': diff --git a/lang-pt.js b/lang-pt.js index cd743099..f8da0fa0 100755 --- a/lang-pt.js +++ b/lang-pt.js @@ -6,7 +6,7 @@ translated by Manuel Menezes de Sequeira - Copyright (C) 2012 by Manuel Menezes de Sequeira + Copyright (C) 2016 by Manuel Menezes de Sequeira This file is part of Snap!. @@ -185,7 +185,7 @@ SnapTranslator.dict.pt = { 'translator_e-mail': 'mmsequeira@gmail.com', 'last_changed': - '2015-08-02', + '2016-10-30', // GUI // control bar: @@ -411,6 +411,8 @@ SnapTranslator.dict.pt = { 'altera a espessura da tua caneta para %n', 'stamp': 'carimba-te', + 'fill': + 'enche o palco', // controlo: 'when %greenflag clicked': @@ -429,6 +431,8 @@ SnapTranslator.dict.pt = { 'entrar em ti', 'mouse-departed': 'sair de ti', + 'when %b': + 'Quando %b', 'when I receive %msgHat': 'Quando receberes a mensagem %msgHat', 'broadcast %msg': @@ -523,6 +527,8 @@ SnapTranslator.dict.pt = { 'o valor do cronómetro', '%att of %spr': '%att de %spr', + 'my %get': + '%get', 'http:// %s': 'o recurso http:// %s', 'turbo mode?': @@ -680,6 +686,33 @@ SnapTranslator.dict.pt = { 'Exportar blocos deste projecto…', 'show global custom block definitions as XML\nin a new browser window': 'Mostrar as definições de blocos\npersonalizados globais no formato\nXML numa nova janela do navegador.', + 'Unused blocks...': + 'Blocos não usados…', + 'find unused global custom blocks\nand remove their definitions': + 'Procurar os blocos personalizados globais\nnão usados e remover as suas definições', + 'Remove unused blocks': + 'Remover blocos não usados', + 'there are currently no unused\nglobal custom blocks in this project': + 'de momento não há blocos personalizados\nglobais não usados neste projecto', + 'unused block(s) removed': + 'blocos não usados removidos', + 'Export summary...': + 'Exportar resumo…', + 'open a new browser browser window\n with a summary of this project': + 'Abrir uma nova janela no navegador\ncontendo um resumo deste projecto', + + 'Contents': + 'Índice', + 'Kind of': + 'Do tipo de', + 'Part of': + 'Uma parte de', + 'Parts': + 'Partes', + 'Blocks': + 'Blocos', + 'For all Sprites': + 'Para todos os Actores', 'Import tools': 'Importar as ferramentas oficiais para este projecto', 'load the official library of\npowerful blocks': @@ -780,6 +813,16 @@ SnapTranslator.dict.pt = { 'Desssinalar para executar os guiões\nà velocidade normal.', 'check to enable\nIDE animations': 'Assinalar para activar\nas animações do AID', + 'Flat design': + 'Design plano', + 'Keyboard Editing': + 'Edição usando o teclado', + 'Table support': + 'Suporte de tabelas', + 'Table lines': + 'Tabelas com linhas', + 'Visible stepping': + 'Traçado passo a passo visível', 'Thread safe scripts': 'Guiões seguros face a threads', 'uncheck to allow\nscript reentrance': @@ -798,6 +841,8 @@ SnapTranslator.dict.pt = { 'Assinalar para que os extremos das linhas\ndesenhadas pela caneta sejam planos.', 'uncheck for round ends of lines': 'Desassinalar para que os extremos das linhas\ndesenhadas pela caneta sejam redondos.', + 'Inheritance support': + 'Suporte para herança', // entradas 'with inputs': @@ -840,6 +885,12 @@ SnapTranslator.dict.pt = { 'adicionar anel', 'unringify': 'remover anel', + 'transient': + 'transiente', + 'uncheck to save contents\nin the project': + 'Desassinalar para guardar\no conteúdo no projecto', + 'check to prevent contents\nfrom being saved': + 'Assinalar para não guardar\no conteúdo no projecto', // blocos personalizados: 'delete block definition...': @@ -905,6 +956,18 @@ SnapTranslator.dict.pt = { 'rename sound': 'Qual o novo nome do som?', + // lists and tables + 'list view...': + 'vista de lista…', + 'table view...': + 'vista de tabela…', + 'open in dialog...': + 'abrir em caixa de diálogo…', + 'reset columns': + 'reiniciar colunas', + 'items': + 'itens', + // caixas de diálogo // botões 'OK': @@ -1139,6 +1202,18 @@ SnapTranslator.dict.pt = { 'vazio', // efeitos gráficos + 'color': + 'cor', + 'fisheye': + 'olho-de-peixe', + 'whirl': + 'remoinho', + 'pixelate': + 'pixelização', + 'mosaic': + 'mosaico', + 'saturation': + 'saturação', 'brightness': 'brilho', 'ghost': @@ -1148,7 +1223,7 @@ SnapTranslator.dict.pt = { 'comic': 'ondeado', 'confetti': - 'cor', + 'confetes', // teclas 'space': @@ -1161,6 +1236,8 @@ SnapTranslator.dict.pt = { 'seta para a direita', 'left arrow': 'seta para a esquerda', + 'any key': + 'qualquer', 'a': 'a', 'b': @@ -1241,6 +1318,8 @@ SnapTranslator.dict.pt = { // funções matemáticas 'abs': 'o valor absoluto', + 'ceiling': + 'o arredondamento para cima', 'floor': 'o arredondamento para baixo', 'sqrt': @@ -1289,6 +1368,8 @@ SnapTranslator.dict.pt = { 'repórter', 'predicate': 'predicado', + 'sprite': + 'actor', // índices de listas 'last': @@ -1296,7 +1377,67 @@ SnapTranslator.dict.pt = { 'any': 'um item ao acaso', + // attributes + 'neighbors': + 'os vizinhos', + 'self': + 'tu próprio', + 'other sprites': + 'os outros actores', + 'parts': + 'as partes', + 'anchor': + 'a âncora', + 'parent': + 'o progenitor', + 'children': + 'os descendentes', + 'clones': + 'os clones', + 'other clones': + 'os outros clones', + 'dangling?': + 'estás pendurado', + 'rotation x': + 'a coordenada x de rotação', + 'rotation y': + 'a coordenada y de rotação', + 'center x': + 'a coordenada x do centro', + 'center y': + 'a coordenada y do centro', + 'name': + 'o nome', + 'stage': + 'o palco', + // em falta no ficheiro lang-de.js + 'delete %shd': + 'remove %shd', + 'Retina display support': + 'Suporte para o ecrã retina', + 'uncheck for lower resolution,\nsaves computing resources': + 'Desassinalar para menor resolução;\npoupa recursos computacionais.', + 'check for higher resolution,\nuses more computing resources': + 'Assinalar para maior resolução;\ngasta mais recursos computacionais.', + 'First-Class Sprites': + 'Actores de primeira classe', + 'uncheck to disable support\nfor first-class sprites': + 'Desassinalar para desactivar o suporte\nde actores de primeira classe.', + 'check to enable support\n for first-class sprite': + 'Assinalar para activar o suporte\nde actores de primeira classe.', + 'Live coding support': + 'Suporte de programação ao vivo', + 'EXPERIMENTAL! check to enable\n live custom control structures': + 'EXPERIMENTAL! Assinalar para activar estruturas\nde controlo personalizadas ao vivo.', + 'EXPERIMENTAL! uncheck to disable live\ncustom control structures': + 'EXPERIMENTAL! Desassinalar para desactivar estruturas\nde controlo personalizadas ao vivo.', + 'Persist linked sublist IDs': + 'Persistir ID de sublistas ligadas', + 'check to enable\nsaving linked sublist identities': + 'Assinalar para activar o\narmazenamento das identidades de sublistas ligadas.', + 'uncheck to disable\nsaving linked sublist identities': + 'Desassinalar para desactivar o\narmazenamento das identidades de sublistas ligadas.', 'grow': 'aumentar', 'shrink': @@ -1312,21 +1453,21 @@ SnapTranslator.dict.pt = { 'current %dates': '%dates corrente', 'year': - 'ano', + 'o ano', 'month': - 'mês', + 'o mês', 'date': - 'dia', + 'o dia', 'day of week': - 'dia da semana', + 'o dia da semana', 'hour': - 'hora', + 'a hora', 'minute': - 'minuto', + 'o minuto', 'second': - 'segundo', + 'o segundo', 'time in milliseconds': - 'tempo (em milisegundos)', + 'o tempo (em milisegundos)', 'find blocks...': 'procurar blocos…', 'costume name': @@ -1473,8 +1614,6 @@ SnapTranslator.dict.pt = { 'Pintar um novo traje.', 'add a new Turtle sprite': 'Adicionar um novo actor.', - 'Flat design': - 'Design plano', 'check for alternative\nGUI design': 'Assinalar para um design alternativo\nda interface gráfica com o utilizador.', 'Rasterize SVGs': @@ -1531,8 +1670,6 @@ SnapTranslator.dict.pt = { 'palco normal', 'turtle': 'tartaruga', - 'stage': - 'palco', 'turtleOutline': 'contorno de tartaruga', 'pause': @@ -1664,6 +1801,65 @@ SnapTranslator.dict.pt = { 'esperavam-se', 'input(s), but getting': 'argumento(s), mas foram passados', + 'parent...': + 'progenitor…', + 'current parent': + 'progenitor actual', + 'Dragging threshold...': + 'Limiar de arrastamento…', + 'Cache Inputs': + 'Memorizar entradas', + 'uncheck to stop caching\ninputs (for debugging the evaluator)': + 'Desassinalar para parar de memorizar\nentradas (para depurar o avaliador).', + 'check to cache inputs\nboosts recursion': + 'Assinalar para memorizar as entradas\n(acelera recursividade).', + 'Project URLs': + 'URL de projecto', + 'check to enable\nproject data in URLs': + 'Assinalar para activar dados\ndo projecto nos URL.', + 'uncheck to disable\nproject data in URLs': + 'Desassinalar para desactivar\ndados do projecto nos URL.', + 'export project media only...': + 'Exportar apenas os média do projecto…', + 'export project without media...': + 'Exportar projecto sem os média…', + 'export project as cloud data...': + 'Exportar projecto como dados da nuvem…', + 'open shared project from cloud...': + 'Abrir projecto partilhado a partir da nuvem…', + 'url...': + 'URL…', + 'Export summary with drop-shadows...': + 'Exportar resumo com sombreamento…', + 'open a new browser browser window\nwith a summary of this project\nwith drop-shadows on all pictures.\nnot supported by all browsers': + 'Abrir uma nova janela no navegador\ncontendo um resumo deste projecto\n' + + 'com sombreamento em todas as imagens\n(não suportado em todos os navegadores)', + 'specify the distance the hand has to move\nbefore it picks up an object': + 'Especificar a distância que mão tem de se\nmover antes de agarrar algum objecto', + 'block variables...': + 'adicionar variáveis de bloco…', + 'remove block variables...': + 'remover variáveis de bloco…', + 'block variables': + 'com variáveis de bloco', + 'experimental -\nunder construction': + 'Experimental – em construção', + 'Table view': + 'Vista de tabela', + 'open in another dialog...': + 'abrir noutra caixa de diálogo…', + 'check for multi-column\nlist view support': + 'Assinalar para suporte de\nvistas multicoluna de listas.', + 'uncheck to disable\nmulti-column list views': + 'Desassinalar para desactivar\nvistas multicoluna de listas.', + 'check for higher contrast\ntable views': + 'Assinalar para vistas de\ntabela com maior contraste.', + 'uncheck for less contrast\nmulti-column list views': + 'Desassinalar para vistas multicoluna\nde listas com menor contraste.', + '(in a new window)': + '(numa nova janela)', + 'save project data as XML\nto your downloads folder': + 'Guardar dados do projecto como XML\nna sua pasta de descarregamentos.', // produção de código 'map %cmdRing to %codeKind %code': @@ -1703,8 +1899,6 @@ SnapTranslator.dict.pt = { 'Enter code that corresponds to the block\'s operation (usually a single\nfunction invocation). Use <#n> to reference actual arguments as shown.': 'Introduza o código que corresponda à operação do bloco (normalmente uma simples\n' + 'invocação de rotina). Use <#n> para referenciar os argumentos tal como mostrado', - 'Keyboard Editing': - 'Edição usando o teclado', 'uncheck to disable\nkeyboard editing support': 'Desassinalar para desactivar\na edição usando o teclado.', 'check to enable\nkeyboard editing support': diff --git a/lang-ro.js b/lang-ro.js new file mode 100644 index 00000000..47bf82a9 --- /dev/null +++ b/lang-ro.js @@ -0,0 +1,1322 @@ +/* + + lang-ro.js + + Romanian translation for SNAP! + + written by Jens Mönig & Cristian Macarascu + + Copyright (C) 2015 by Jens Mönig & Cristian Macarascu + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.ro = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Romanian', // the name as it should appear in the language menu + 'language_translator': + 'Cristian Macarascu', // your name for the Translators tab + 'translator_e-mail': + '', // optional + 'last_changed': + '2015-10-24', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'fara nume', + 'development mode': + 'modul dezvoltare', + + // categories: + 'Motion': + 'Miscare', + 'Looks': + 'Infatisare', + 'Sound': + 'Sunet', + 'Pen': + 'Scriere', + 'Control': + 'Control', + 'Sensing': + 'Interactiune', + 'Operators': + 'Operatori', + 'Variables': + 'Variabile', + 'Lists': + 'Liste', + 'Other': + 'Altele', + + // editor: + 'draggable': + 'poate fi mutat', + + // tabs: + 'Scripts': + 'Scripturi', + 'Costumes': + 'Costume', + 'Sounds': + 'Sunete', + + // names: + 'Sprite': + 'Animatie', + 'Stage': + 'Scena', + + // rotation styles: + 'don\'t rotate': + 'fara rotire', + 'can rotate': + 'rotire libera', + 'only face left/right': + 'doar stanga/dreapta', + + // new sprite button: + 'add a new sprite': + 'adauga o noua animatie', + + // tab help + 'costumes tab help': + 'ajutor pentru costume', + 'import a sound from your computer\nby dragging it into here': + 'adauga un sunet tragandu-l aici', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + 'Scena selectata:\nfara primitive de miscare', + + 'move %n steps': + 'inainteaza %n pasi', + 'turn %clockwise %n degrees': + 'roteste %clockwise %n grade', + 'turn %counterclockwise %n degrees': + 'roteste %counterclockwise %n grade', + 'point in direction %dir': + 'arata spre directia %dir', + 'point towards %dst': + 'arata spre %dst', + 'go to x: %n y: %n': + 'mergi la x: %n y: %n', + 'go to %dst': + 'mergi la %dst', + 'glide %n secs to x: %n y: %n': + 'mergi in %n secunde la x: %n y: %n', + 'change x by %n': + 'modifica x cu %n', + 'set x to %n': + 'schimba x in %n', + 'change y by %n': + 'modifica y cu %n', + 'set y to %n': + 'schimba y in %n', + 'if on edge, bounce': + 'daca esti pe margine, sari', + 'x position': + 'pozitia x', + 'y position': + 'pozitia y', + 'direction': + 'directia', + + // looks: + 'switch to costume %cst': + 'schimba-te in costumul %cst', + 'next costume': + 'urmatorul costum', + 'costume #': + 'costumul nr', + 'say %s for %n secs': + 'spune %s pentru %n secunde', + 'say %s': + 'spune %s', + 'think %s for %n secs': + 'gandeste %s pentru %n secunde', + 'think %s': + 'gandeste %s', + 'Hello!': + 'Salut!', + 'Hmm...': + 'Hmmm...', + 'change %eff effect by %n': + 'modifica efectul %eff cu %n', + 'set %eff effect to %n': + 'schimba efectul %eff in %n', + 'clear graphic effects': + 'anuleaza efectele grafice', + 'change size by %n': + 'modifica marimea cu %n', + 'set size to %n %': + 'schimba marimea la %n %', + 'size': + 'marime', + 'show': + 'afiseaza', + 'hide': + 'ascunde', + 'go to front': + 'adu in fata', + 'go back %n layers': + 'muta spre spate %n niveluri', + + 'development mode \ndebugging primitives:': + 'Modul dezvoltare \nprimitive de debug:', + 'console log %mult%s': + 'jurnal consola: %mult%', + 'alert %mult%s': + 'anunta %mult%', + + // sound: + 'play sound %snd': + 'scoate sunetul %snd', + 'play sound %snd until done': + 'scoate sunetul %snd pana termini', + 'stop all sounds': + 'opreste toate sunetele', + 'rest for %n beats': + 'pauza pentru %n masuri', + 'play note %n for %n beats': + 'canta nota %n pentru %n masuri', + 'change tempo by %n': + 'modifica tempoul cu %n', + 'set tempo to %n bpm': + 'schimba tempoul in %n bpm', + 'tempo': + 'tempo', + + // pen: + 'clear': + 'sterge', + 'pen down': + 'stiloul jos', + 'pen up': + 'stiloul sus', + 'set pen color to %clr': + 'schimba culoarea stiloului in %clr', + 'change pen color by %n': + 'modifica culoarea stiloului cu %n', + 'set pen color to %n': + 'schimba culoarea stiloului in %n', + 'change pen shade by %n': + 'modifica umbra stiloului cu %n', + 'set pen shade to %n': + 'schimba umbra stiloului in %n', + 'change pen size by %n': + 'modifica grosimea penitei cu %n', + 'set pen size to %n': + 'schimba grosimea penitei in %n', + 'stamp': + 'stampila', + + // control: + 'when %greenflag clicked': + 'cand se apasa %greenflag', + 'when %keyHat key pressed': + 'cand se apasa tasta %keyHat', + 'when I am %interaction': + 'cand sunt %interaction', + 'clicked': + 'apasat', + 'pressed': + 'tinut apasat', + 'dropped': + 'eliberat', + 'mouse-entered': + 'in contact cu mouse-ul', + 'mouse-departed': + 'indepratat de langa mouse', + 'when I receive %msgHat': + 'cand primesc %msgHat', + 'broadcast %msg': + 'trimite mesajul %msg tuturor', + 'broadcast %msg and wait': + 'trimite mesajul %msg tuturor si asteapta', + 'Message name': + 'Mesaj', + 'message': + 'mesaj', + 'any message': + 'orice mesaj', + 'wait %n secs': + 'asteapta %n secunde', + 'wait until %b': + 'asteapta pana cand %b', + 'forever %c': + 'la infinit %c', + 'repeat %n %c': + 'repeta de %n ori %c', + 'repeat until %b %c': + 'repeta pana cand %b %c', + 'if %b %c': + 'daca %b fa %c', + 'if %b %c else %c': + 'daca %b fa %c altfel fa %c', + 'report %s': + 'anunta %s', + 'stop %stopChoices': + 'opreste %stopChoices', + 'all': + 'toate', + 'this script': + 'acest script', + 'this block': + 'acest bloc', + 'stop %stopOthersChoices': + 'opreste %stopOthersChoices', + 'all but this script': + 'toate scripturile, mai putin pe acesta', + 'other scripts in sprite': + 'toate scripturile din animatie', + 'pause all %pause': + 'pune pauza pentru %pause', + 'run %cmdRing %inputs': + 'ruleaza %cmdRing cu %inputs', + 'launch %cmdRing %inputs': + 'porneste %cmdRing cu %inputs', + 'call %repRing %inputs': + 'apeleaza %repRing cu %inputs', + 'run %cmdRing w/continuation': + 'ruleaza %cmdRing cu Continuation', + 'call %cmdRing w/continuation': + 'apeleaza %cmdRing cu Continuation', + 'warp %c': + 'warp %c', + 'when I start as a clone': + 'cand sunt pornit ca o clona', + 'create a clone of %cln': + 'creaza o clona a %cln', + 'myself': + 'eu insumi', + 'delete this clone': + 'sterge aceasta clona', + + // sensing: + 'touching %col ?': + 'atinge %col ?', + 'touching %clr ?': + 'atinge %clr ?', + 'color %clr is touching %clr ?': + 'culoarea %clr atinge %clr ?', + 'ask %s and wait': + 'intreaba %s si asteapta', + 'what\'s your name?': + 'care e numele tau?', + 'answer': + 'raspuns', + 'mouse x': + 'pozitia x a mouseului', + 'mouse y': + 'pozitia y a mouseului', + 'mouse down?': + 'este mouseul apasat?', + 'key %key pressed?': + 'este tasta %key apasata?', + 'distance to %dst': + 'distanta pana la %dst', + 'reset timer': + 'restarteaza cronometrul', + 'timer': + 'cronometrul', + '%att of %spr': + '%att al %spr', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'modul turbo?', + 'set turbo mode to %b': + 'seteaza modul turbo la %b', + + 'filtered for %clr': + 'filtrat pentru %clr', + 'stack size': + 'marimea stivei', + 'frames': + 'cadre', + + // operators: + '%n mod %n': + '%n modulo %n', + 'round %n': + '%n rotunjit', + '%fun of %n': + '%fun din %n', + 'pick random %n to %n': + 'alege aleator de la %n la %n', + '%b and %b': + '%b si %b', + '%b or %b': + '%b sau %b', + 'not %b': + 'not %b', + 'true': + 'adevarat', + 'false': + 'fals', + 'join %words': + 'lipeste %words', + 'split %s by %delim': + 'desparte %s folosind %delim', + 'hello': + 'salut', + 'world': + 'lume', + 'letter %n of %s': + 'litera %n din %s', + 'length of %s': + 'lungimea lui %s', + 'unicode of %s': + 'codul Unicode al %s', + 'unicode %n as letter': + 'codul Unicode %n ca litera', + 'is %s a %typ ?': + 'este %s un/o %typ ?', + 'is %s identical to %s ?': + 'este %s identic cu %s ?', + + 'type of %s': + 'tipul lui %s', + + // variables: + 'Make a variable': + 'creaza o variabila', + 'Variable name': + 'numele variabilei', + 'Script variable name': + 'variabila script ', + 'Delete a variable': + 'sterge o variabila', + + 'set %var to %s': + 'schimba %var in %s', + 'change %var by %n': + 'modifica %var cu %n', + 'show variable %var': + 'afiseaza variabila %var', + 'hide variable %var': + 'ascunde variabila %var', + 'script variables %scriptVars': + 'variabilele script %scriptVars', + + // lists: + 'list %exp': + 'lista %exp', + '%s in front of %l': + '%s in fata %l', + 'item %idx of %l': + 'elementul %idx din %l', + 'all but first of %l': + 'toate, mai putin primul din %l', + 'length of %l': + 'lungimea %l', + '%l contains %s': + '%l contine %s', + 'thing': + 'lucru', + 'add %s to %l': + 'adauga %s la %l', + 'delete %ida of %l': + 'sterge %ida din %l', + 'insert %s at %idx of %l': + 'adauga %s la pozitia %idx in %l', + 'replace item %idx of %l with %s': + 'inlocuieste elementul %idx din %l cu %s', + + // other + 'Make a block': + 'creaza bloc', + + // menus + // snap menu + 'About...': + 'Despre Snap!...', + 'Reference manual': + 'Manual utilizator', + 'Snap! website': + 'Siteul Snap!', + 'Download source': + 'Descarca codul sursa', + 'Switch back to user mode': + 'Treci in modul utilizator', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'Afiseaza meniuri simplificate in locul celor morfice', + 'Switch to dev mode': + 'Treci in modul dezvoltator', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'Afiseaza meniuri morfice in locul celor simplificate', + + // project menu + 'Project notes...': + 'Note de proiect...', + 'New': + 'Nou', + 'Open...': + 'Deschide...', + 'Save': + 'Salveaza', + 'Save to disk': + 'Salveaza pe disc', + 'store this project\nin the downloads folder\n(in supporting browsers)': + 'Salveaza proiectul\nin meniul Descarcari\n(functie de browser)', + 'Save As...': + 'Salveaza sub numele...', + 'Import...': + 'Importa...', + 'file menu import hint': + 'indiciu import meniul fisiere', + 'Export project as plain text...': + 'Exporta proiectul ca text...', + 'Export project...': + 'Exporta proiectul...', + 'show project data as XML\nin a new browser window': + 'afiseaza continut proiect ca XML\nin fereastra noua de browser', + 'Export blocks...': + 'Exporta blocurile...', + 'show global custom block definitions as XML\nin a new browser window': + ' afiseaza definitiile blocurilor ca XML\n intr-o fereastra noua de browser', + 'Unused blocks...': + 'Blocuri nefolosite...', + 'find unused global custom blocks\nand remove their definitions': + 'cauta blocuri utilizator nefolosite\nsi sterge-le', + 'Remove unused blocks': + 'Sterge blocurile nefolosite', + 'there are currently no unused\nglobal custom blocks in this project': + 'nu exista blocuri utilizator nefolosite\nin acest proiect', + 'unused block(s) removed': + 'blocuri nefolosite eliminate', + 'Export summary...': + 'Exporta sumarul...', + 'open a new browser browser window\n with a summary of this project': + 'Deschide o noua fereastra de browser\ncu sumarul acestui proiect', + 'Contents': + 'Continut', + 'Kind of': + 'De tipul', + 'Part of': + 'Parte din', + 'Parts': + 'Parti', + 'Blocks': + 'Blocuri', + 'For all Sprites': + 'Pentru toate animatiile', + 'Import tools': + 'Importa unelte', + 'load the official library of\npowerful blocks': + 'incarca biblioteca oficiala\nde blocuri importante', + 'Libraries...': + 'Biblioteci...', + 'Import library': + 'Importa biblioteca', + + // cloud menu + 'Login...': + 'Autentificare...', + 'Signup...': + 'Creaza-ti cont...', + + // settings menu + 'Language...': + 'Selecteaza limba...', + 'Zoom blocks...': + 'Marimeblocuri...', + 'Stage size...': + 'Marime scena...', + 'Stage size': + 'Marime scena', + 'Stage width': + 'Latime scena', + 'Stage height': + 'Inaltime scena', + 'Default': + 'Implicit', + 'Blurred shadows': + 'Umbre neclare', + 'uncheck to use solid drop\nshadows and highlights': + 'debifeaza pentru a utiliza\numbre clare si evidentieri', + 'check to use blurred drop\nshadows and highlights': + 'bifeaza pentru a utiliza\numbre neclare si evidentieri', + 'Zebra coloring': + 'Culoare zebra', + 'check to enable alternating\ncolors for nested blocks': + 'bifeaza pentru a folosi culori\nalternative in blocurile imbricate', + 'uncheck to disable alternating\ncolors for nested block': + 'debifeaza pentru a folosi culori\nobisnuite in blocurile imbricate', + 'Dynamic input labels': + 'Etichete intrare dinamice', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'debifeaza pentru a renunta la\netichete dinamice pentru intrari variabile', + 'check to enable dynamic\nlabels for variadic inputs': + 'difeaza pentru a folosi etichete\n dinamice pentru intrari variabile', + 'Prefer empty slot drops': + 'Foloseste slot drops goale', + 'settings menu prefer empty slots hint': + 'indicii de slot goale in meniul setari', + 'uncheck to allow dropped\nreporters to kick out others': + 'debifeaza pentru a folosi\nreporteri pentru a elimina altii', + 'Long form input dialog': + 'Dialoguri lungi', + 'Plain prototype labels': + 'Etichete simple', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'debifeaza pentru a folosi (+) \nin blocurile eticheta', + 'check to hide (+) symbols\nin block prototype labels': + 'bifeaza pentru a ascunde (+) \nin blocurile eticheta', + 'check to always show slot\ntypes in the input dialog': + 'bifeaza pentru a afisa tipuri slot\N in dialoguri de intrare', + 'uncheck to use the input\ndialog in short form': + 'debifeaza pentru a afisa dialoguri\n de intrare in forma scurta', + 'Virtual keyboard': + 'Tastatura pe ecran', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'debifeaza pentru a ascunde\ntastatura de pe ecranul\nechipamentelor mobile', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'bifeaza pentru a afisa\ntastatura pe ecranul\nechipamentelor mobile', + 'Input sliders': + 'Slidere pentru intrare', + 'uncheck to disable\ninput sliders for\nentry fields': + 'debifeaza pentru a ascunde\nsliderele campurilor de intrare', + 'check to enable\ninput sliders for\nentry fields': + 'bifeaza pentru a afisa\nslidere in campurile de intrare', + 'Clicking sound': + 'Sunet la apasarea tastelor', + 'uncheck to turn\nblock clicking\nsound off': + 'debifeaza pentru a opri\nsunete la schimbarea blocurilor', + 'check to turn\nblock clicking\nsound on': + 'bifeaza pentru sunete\n la schimbare blocuri', + 'Animations': + 'Animatii', + 'uncheck to disable\nIDE animations': + 'debifeaza pentru a dezactiva\nanimatiile IDE', + 'Turbo mode': + 'Modul turbo', + 'check to prioritize\nscript execution': + 'bifeaza pentru a da prioritate\nexecutiei scripului', + 'uncheck to run scripts\nat normal speed': + 'debifeaza pentru a rula scripul\nla viteza normala', + 'check to enable\nIDE animations': + 'bifeaza pentru a activa\nanimatiile IDE', + 'Flat design': + 'Design cu colturi', + 'Keyboard Editing': + 'Editare tastatura', + 'Thread safe scripts': + 'Scripturi thread-safe', + 'uncheck to allow\nscript reentrance': + 'debifeaza pentru a permite\nreentranta in scripturi', + 'check to disallow\nscript reentrance': + 'bifeaza pentru a interzice\nreentranta in scripturi', + 'Prefer smooth animations': + 'Animatii fluide', + 'uncheck for greater speed\nat variable frame rates': + 'debifeaza pentru viteza mai mare\nin detrimentul afisarii pe ecran', + 'check for smooth, predictable\nanimations across computers': + 'bifeaza pentru afisare fluida pe ecran\n in detrimentul vitezei', + 'Flat line ends': + 'Sfarsit de linii cu colturi', + 'check for flat ends of lines': + 'bifeaza pentru sfarsit\nde linii cu colturi', + 'uncheck for round ends of lines': + 'debifeaza pentru sfarsit\nde linii rotunjite', + 'Inheritance support': + 'Suport pentru mostenire', + + // inputs + 'with inputs': + 'cu intrari', + 'input names:': + 'numele intrarii:', + 'Input Names:': + 'Numele intrarii:', + 'input list:': + 'lista intrare:', + + // context menus: + 'help': + 'ajutor', + + // palette: + 'hide primitives': + 'Ascunde primitivele', + 'show primitives': + 'Afiseaza primitivele', + + // blocks: + 'help...': + 'ajutor...', + 'relabel...': + 'redenumeste...', + 'duplicate': + 'duplica', + 'make a copy\nand pick it up': + 'fa o copie si selecteaz-o', + 'only duplicate this block': + 'duplica doar acest bloc', + 'delete': + 'sterge', + 'script pic...': + 'imagine script...', + 'open a new window\nwith a picture of this script': + 'deschide fereastra noua\ncu imaginea acestui script', + 'ringify': + 'ringify', + 'unringify': + 'unringify', + + // custom blocks: + 'delete block definition...': + 'sterge definitia blocului....', + 'edit...': + 'modifica...', + + // sprites: + 'edit': + 'modifica', + 'move': + 'muta', + 'detach from': + 'desparte', + 'detach all parts': + 'desparte toate bucatile', + 'export...': + 'exporta...', + + // stage: + 'show all': + 'afiseaza tot', + 'pic...': + 'imagine...', + 'open a new window\nwith a picture of the stage': + 'deschide o imagine a scenei\nintr-o fereastra noua', + + // scripting area + 'clean up': + 'curata', + 'arrange scripts\nvertically': + 'Afiseaza scripturile\nvertical', + 'add comment': + 'adauga comentariu', + 'undrop': + 'undrop', + 'undo the last\nblock drop\nin this pane': + 'renunta la ultimul drop\nde block in aceasta fereastra', + 'scripts pic...': + 'imagine scripturi...', + 'open a new window\nwith a picture of all scripts': + 'afiseaza imaginea scripturilor\nintr-o noua fereastra', + 'make a block...': + 'creaza bloc...', + + // costumes + 'rename': + 'redenumeste', + 'export': + 'exporta', + 'rename costume': + 'redenumeste costum', + + // sounds + 'Play sound': + 'Ruleaza sunet', + 'Stop sound': + 'Opreste sunet', + 'Stop': + 'Opreste', + 'Play': + 'Ruleaza', + 'rename sound': + 'redenumeste sunet', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'OK', + 'Cancel': + 'Renunta', + 'Yes': + 'Da', + 'No': + 'Nu', + + // help + 'Help': + 'Ajutor', + + // zoom blocks + 'Zoom blocks': + 'Nivel zoom blocuri', + 'build': + 'build', + 'your own': + 'al tau', + 'blocks': + 'blocuri', + 'normal (1x)': + 'normal (1x)', + 'demo (1.2x)': + 'demonstratie (1.2x)', + 'presentation (1.4x)': + 'prezentare (1.4x)', + 'big (2x)': + 'mare (2x)', + 'huge (4x)': + 'enorm (4x)', + 'giant (8x)': + 'gigant (8x)', + 'monstrous (10x)': + 'monstruos (10x)', + + // Project Manager + 'Untitled': + 'fara nume', + 'Open Project': + 'Deschide proiect', + '(empty)': + '(gol)', + 'Saved!': + 'Salvat!', + 'Delete Project': + 'Sterge proiect', + 'Are you sure you want to delete': + 'Esti sigur ca vrei sa stergi?', + 'rename...': + 'redenumeste...', + + // costume editor + 'Costume Editor': + 'Modificare costume', + 'click or drag crosshairs to move the rotation center': + 'apasa mouseul sau trage de cursor pentru a muta centrul de rotatie', + + // project notes + 'Project Notes': + 'Note de proiect', + + // new project + 'New Project': + 'Proiect nou', + 'Replace the current project with a new one?': + 'Inlocuieste proiectul cu un altul?', + + // save project + 'Save Project As...': + 'Salveaza proiectul sub alt nume...', + + // export blocks + 'Export blocks': + 'Exporta blocurile', + 'Import blocks': + 'Importa blocurile', + 'this project doesn\'t have any\ncustom global blocks yet': + 'acest proiect nu are\nblocuri globale', + 'select': + 'selecteaza', + 'none': + 'nici un/o', + + // variable dialog + 'for all sprites': + 'pentru toate animatiile', + 'for this sprite only': + 'doar pentru aceasta animatie', + + // block dialog + 'Change block': + 'Schimba blocul', + 'Command': + 'Comanda', + 'Reporter': + 'Reporter', + 'Predicate': + 'Predicat', + + // block editor + 'Block Editor': + 'Editor de blocuri', + 'Apply': + 'Aplica actiunea', + + // block deletion dialog + 'Delete Custom Block': + 'Sterge blocul utilizator', + 'block deletion dialog text': + 'textul pentru stergerea unui bloc', + + // input dialog + 'Create input name': + 'Creaza nume', + 'Edit input name': + 'Schimba nume', + 'Edit label fragment': + 'Schimba eticheta', + 'Title text': + 'Text titlu', + 'Input name': + 'Nume', + 'Delete': + 'Sterge', + 'Object': + 'Obiect', + 'Number': + 'Nume', + 'Text': + 'Text', + 'List': + 'Lista', + 'Any type': + 'Orice tip', + 'Boolean (T/F)': + 'Boolean (Adevarat/Fals)', + 'Command\n(inline)': + 'Comanda\n(pe acelasi rand)', + 'Command\n(C-shape)': + 'Comanda\n(in forma de C)', + 'Any\n(unevaluated)': + 'Orice\n(necalculat)', + 'Boolean\n(unevaluated)': + 'Boolean\n(necalculat)', + 'Single input.': + 'Intrare.', + 'Default Value:': + 'Valoare implicita:', + 'Multiple inputs (value is list of inputs)': + 'Intrari multiple (dintr-o lista)', + 'Upvar - make internal variable visible to caller': + 'Fa variabilele locale vizibile blocului chemator', + + // About Snap + 'About Snap': + 'Despre Snap', + 'Back...': + 'Inapoi...', + 'License...': + 'Licenta...', + 'Modules...': + 'Componente...', + 'Credits...': + 'Multumiri...', + 'Translators...': + 'Traducatori', + 'License': + 'Licenta', + 'current module versions:': + 'versiuni componente:', + 'Contributors': + 'Contribuitori', + 'Translations': + 'Traduceri', + + // variable watchers + 'normal': + 'normal', + 'large': + 'mare', + 'slider': + 'slider', + 'slider min...': + 'slider minim...', + 'slider max...': + 'slider maxim...', + 'import...': + 'importa...', + 'Slider minimum value': + 'Valoare minima slidere', + 'Slider maximum value': + 'Valoare maxima slidere', + + // list watchers + 'length: ': + 'lungime: ', + + // coments + 'add comment here...': + 'adauga comentariu...', + + // drow downs + // directions + '(90) right': + '(90) dreapta', + '(-90) left': + '(-90) stanga', + '(0) up': + '(0) sus', + '(180) down': + '(180) jos', + + // collision detection + 'mouse-pointer': + 'cursor mouse', + 'edge': + 'margine', + 'pen trails': + 'urme stilou', + + // costumes + 'Turtle': + 'Broasca', + 'Empty': + 'Gol', + + // graphical effects + 'brightness': + 'luminozitate', + 'ghost': + 'umbra', + 'negative': + 'negativ', + 'comic': + 'glumet', + 'confetti': + 'contetti', + + // keys + 'space': + 'spatiu', + 'up arrow': + 'sageata sus', + 'down arrow': + 'sageata jos', + 'right arrow': + 'sageata dreapta', + 'left arrow': + 'sageata stanga', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + 'nou...', + + // math functions + 'abs': + 'val absoluta', + 'ceiling': + 'rotunjire in sus', + 'floor': + 'rotunjire in jos', + 'sqrt': + 'radical', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // delimiters + 'letter': + 'litera', + 'whitespace': + 'spatiu', + 'line': + 'linie', + 'tab': + 'tab', + 'cr': + 'enter', + + // data types + 'number': + 'numar', + 'text': + 'test', + 'Boolean': + 'boolean', + 'list': + 'lista', + 'command': + 'comanda', + 'reporter': + 'reporter', + 'predicate': + 'predicat', + + // list indices + 'last': + 'ultimul', + 'any': + 'oricare' +}; diff --git a/lang-ru.js b/lang-ru.js index 5932bb44..2c28e233 100644 --- a/lang-ru.js +++ b/lang-ru.js @@ -178,11 +178,11 @@ SnapTranslator.dict.ru = { 'language_name': 'Русский', // the name as it should appear in the language menu 'language_translator': - 'Svetlana Ptashnaya', // your name for the Translators tab + 'Svetlana Ptashnaya, Проскурнёв Артём', // your name for the Translators tab 'translator_e-mail': - 'svetlanap@berkeley.edu', // optional + 'svetlanap@berkeley.edu, tema@school830.ru', // optional 'last_changed': - '2014-09-29', // this, too, will appear in the Translators tab + '2016-06-21', // this, too, will appear in the Translators tab // GUI // control bar: @@ -237,7 +237,7 @@ SnapTranslator.dict.ru = { 'can rotate': 'вращаемый', 'only face left/right': - 'вращаемый только на лево и направо', + 'зеркальное отображение лево-право при вращении', // new sprite button: 'add a new sprite': @@ -406,14 +406,28 @@ SnapTranslator.dict.ru = { 'установить размер пера %n', 'stamp': 'оттиск', + 'fill': + 'заливка', // control: 'when %greenflag clicked': 'когда щелкнуть на %greenflag', 'when %keyHat key pressed': 'когда нажать %keyHat клавишу', - 'when I am clicked': - 'когда щелкнуть на меня', + 'when I am %interaction': + 'когда меня %interaction', + 'clicked': + 'кликнут', + 'pressed': + 'нажмут', + 'dropped': + 'бросят', + 'mouse-entered': + 'заденет курсор', + 'mouse-departed': + 'покинет курсор', + 'when %b': + 'когда %b', 'when I receive %msgHat': 'когда я получу %msgHat', 'broadcast %msg': @@ -422,6 +436,10 @@ SnapTranslator.dict.ru = { 'переслать %msg всем и ждать', 'Message name': 'Название сообщения', + 'message': + 'сообщение', + 'any message': + 'любое сообщение', 'wait %n secs': 'ждать %n сек.', 'wait until %b': @@ -438,12 +456,20 @@ SnapTranslator.dict.ru = { 'если %b %c иначе %c', 'report %s': 'результат %s', - 'stop block': - 'стоп блок', - 'stop script': - 'стоп скрипт', - 'stop all %stop': - 'стоп все %stop', + 'stop %stopChoices': + 'стоп %stopChoices', + 'all': + 'все', + 'this script': + 'этот скрипт', + 'this block': + 'этот блок', + 'stop %stopOthersChoices': + 'стоп %stopOthersChoices', + 'all but this script': + 'всех, кроме меня', + 'other scripts in sprite': + 'все другие мои скрипты', 'run %cmdRing %inputs': 'выполнять %cmdRing %inputs', 'launch %cmdRing %inputs': @@ -455,19 +481,29 @@ SnapTranslator.dict.ru = { 'call %cmdRing w/continuation': 'вызвать %cmdRing с продолжением', 'warp %c': - 'warp %c', + 'сразу %c', + 'when I start as a clone': + 'когда я создан как клон', + 'create a clone of %cln': + 'клонировать %cln', + 'myself': + 'меня', + 'delete this clone': + 'удалить клона', + 'pause all %pause': + 'пауза для всех %pause', // sensing: 'touching %col ?': - 'касаеться %col ?', + 'касается %col ?', 'touching %clr ?': - 'касаеться %clr ?', + 'касается %clr ?', 'color %clr is touching %clr ?': 'цвет %clr касаеться %clr ?', 'ask %s and wait': 'спросить %s и ждать', 'what\'s your name?': - 'как вас зовут?', + 'Как Вас зовут?', 'answer': 'ответ', 'mouse x': @@ -484,8 +520,16 @@ SnapTranslator.dict.ru = { 'переустановить таймер', 'timer': 'таймер', + '%att of %spr': + '%att у %spr', + 'my %get': + 'атрибут %get', 'http:// %s': 'http:// %s', + 'turbo mode?': + 'режим турбо?', + 'set turbo mode to %b': + 'установить турбо-режим %b', 'filtered for %clr': 'отфильтровано для %clr', @@ -516,7 +560,7 @@ SnapTranslator.dict.ru = { 'join %words': 'объединить %words', 'hello': - 'привет', + 'Привет', 'world': 'мир', 'letter %n of %s': @@ -531,6 +575,8 @@ SnapTranslator.dict.ru = { '%s это %typ ?', 'is %s identical to %s ?': '%s тождественно %s ?', + 'split %s by %delim': + 'разделить %s по %delim', 'type of %s': 'тип %s', @@ -618,6 +664,8 @@ SnapTranslator.dict.ru = { 'Экспорт проект как текстовый файл...', 'Export project...': 'Экспорт проект...', + 'Export summary...': + 'Экспортируемая информация...', 'show project data as XML\nin a new browser window': 'представить проектные данные как XML\nв новом окне браузера', 'Export blocks...': @@ -625,13 +673,27 @@ SnapTranslator.dict.ru = { 'show global custom block definitions as XML\nin a new browser window': 'представить определения глобальных пользовательских блоков как XML\nв новом окне браузера', 'Import tools': - 'Импорт сервисные ср-ва', + 'Импортировать сервисные ср-ва', + 'Backgrounds...': + 'Фоны...', + 'Libraries...': + 'Библиотеки...', 'load the official library of\npowerful blocks': 'загрузить служебную библиотеку блоков', // settings menu 'Language...': 'Язык...', + 'Zoom blocks...': + 'Увеличение блоков кода...', + 'Stage size...': + 'Размер сцены...', + 'Stage size': + 'Размер сцены', + 'Stage width': + 'Ширина сцены', + 'Stage height': + 'Высота сцены', 'Blurred shadows': 'Контрастность тени', 'uncheck to use solid drop\nshadows and highlights': @@ -1097,6 +1159,18 @@ SnapTranslator.dict.ru = { 'e^': 'e^', + // delimiters + 'letter': + 'буквам', + 'whitespace': + 'пробелам', + 'line': + 'строкам', + 'tab': + 'табуляторам', + 'cr': + 'концам строк', + // data types 'number': 'число', @@ -1112,6 +1186,8 @@ SnapTranslator.dict.ru = { 'генератор значений', 'predicate': 'предикат', + 'sprite': + 'спрайт', // list indices 'last': @@ -1121,5 +1197,61 @@ SnapTranslator.dict.ru = { 'now connected': 'вы вошли в систему', 'undo': - 'отменить' + 'отменить', + + // attributes + 'neighbors': + 'соседи', + 'self': + 'я', + 'other sprites': + 'другие спрайты', + 'parts': + 'части', + 'anchor': + 'якорь', + 'parent': + 'родитель', + 'children': + 'потомок', + 'clones': + 'клоны', + 'other clones': + 'другие клоны', + 'dangling?': + 'висячий?', + 'rotation x': + 'смещение по x', + 'rotation y': + 'смещение по y', + 'center x': + 'x центра спрайта', + 'center y': + 'y центра спрайта', + 'name': + 'имя', + 'stage': + 'сцена', + //Переводы найденых в программе, но не в файле перевода + 'current %dates': + 'сейчас %dates', + 'year': + 'год', + 'month': + 'месяц', + 'date': + 'день', + 'day of week': + 'день недели', + 'hour': + 'часов', + 'minute': + 'минут', + 'second': + 'секунд', + 'time in milliseconds': + 'время в миллисекундах', + 'costume name': + 'имя костюма' + }; diff --git a/lang-si.js b/lang-si.js index 8985286a..31c63712 100644 --- a/lang-si.js +++ b/lang-si.js @@ -183,11 +183,11 @@ SnapTranslator.dict.si = { 'language_name': 'Sloven\u0161\u010Dina', // the name as it should appear in the language menu 'language_translator': - 'Sasa Divjak', // your name for the Translators tab + 'Sasa Divjak, Gorazd Breskvar', // your name for the Translators tab 'translator_e-mail': 'sasa.divjak@fri.uni-lj.si', // optional 'last_changed': - '2013-01-07', // this, too, will appear in the Translators tab + '2016-04-22', // this, too, will appear in the Translators tab // GUI // control bar: @@ -378,6 +378,16 @@ SnapTranslator.dict.si = { 'predvajaj zvok %snd do konca', 'stop all sounds': 'ustavi vse zvoke', + 'rest for %n beats': + 'po\u010Divaj %n udarcev', + 'play note %n for %n beats': + 'predvajaj noto %n za %n udarcev', + 'change tempo by %n': + 'spremeni tempo za %n', + 'set tempo to %n bpm': + 'nastavi tempo na %n udarcev na minuto.', + 'tempo': + 'tempo', // pen: 'clear': @@ -408,8 +418,20 @@ SnapTranslator.dict.si = { 'ko kliknemo na %greenflag', 'when %keyHat key pressed': 'ko pritisnemo na tipko %keyHat ', - 'when I am clicked': - 'ko je klik name', + 'when I am %interaction': + 'Ko je %interaction', + 'clicked': + 'mi\u0161ka kliknjena', + 'pressed': + 'gumb mi\u0161ke pritisnjen', + 'dropped': + 'konec vle\u010Denja', + 'mouse-entered': + 'mi\u0161ka se dotika', + 'mouse-departed': + 'mi\u0161ka se ne dotika ve\u010D', + 'when %b': + 'Ko je %b', 'when I receive %msgHat': 'ko sprejmem %msgHat', 'broadcast %msg': @@ -418,6 +440,10 @@ SnapTranslator.dict.si = { 'po\u0161lji vsem %msg in po\u010Dakaj', 'Message name': 'Obvestilo', + 'message': + 'sporo\u010Dilo', + 'any message': + 'poljudno sporo\u010Dilo', 'wait %n secs': '\u010Dakaj %n sekund.', 'wait until %b': @@ -434,12 +460,20 @@ SnapTranslator.dict.si = { '\u010De %b %c sicer %c', 'report %s': 'sporo\u010Di %s', - 'stop block': - 'ustavi ta blok', - 'stop script': - 'ustavi ta program', - 'stop all %stop': - 'ustavi vse %stop', + 'stop %stopChoices': + 'ustavi %stopChoices', + 'this script': + 'to skripto', + 'this block': + 'ta blok', + 'stop %stopOthersChoices': + 'ustavi %stopOthersChoices', + 'all but this script': + 'vse razen te skripte', + 'other scripts in sprite': + 'ostale skripte tega objekta', + 'pause all %pause': + 'pavziraj vse %pause', 'run %cmdRing %inputs': 'izvajaj %cmdRing %inputs', 'launch %cmdRing %inputs': @@ -452,6 +486,14 @@ SnapTranslator.dict.si = { 'pokli\u010Di %cmdRing z nadaljevanjem', 'warp %c': 'Warp %c', + 'when I start as a clone': + 'ko za\u010Dnem kot klon', + 'create a clone of %cln': + 'kloniraj %cln', + 'myself': + 'sebe', + 'delete this clone': + 'izbri\u0161i ta klon', // sensing: 'touching %col ?': @@ -482,6 +524,30 @@ SnapTranslator.dict.si = { '\u0161toparica', 'http:// %s': 'http:// %s', + 'turbo mode?': + 'hitri na\u010Din?', + 'set turbo mode to %b': + 'nastavi hitri na\u010Din na %b', + + 'current %dates': + 'trenutni %dates', + 'year': + 'leto', + 'month': + 'mesec', + 'date': + 'dan', + 'day of week': + 'dan v tednu', + 'hour': + 'ura', + 'minute': + 'minuta', + 'second': + 'sekunda', + 'time in milliseconds': + '\u010Das v tiso\u010Dinkah sekunde', + 'filtered for %clr': 'filtriran za %clr', @@ -511,6 +577,8 @@ SnapTranslator.dict.si = { 'ni res', 'join %words': 'pove\u017Ei %words', + 'split %s by %delim': + 'razdeli %s z %delim', 'hello': 'Halo', 'world': @@ -525,6 +593,8 @@ SnapTranslator.dict.si = { 'Unicode %n kot \u010Drka', 'is %s a %typ ?': 'je %s tipa %typ ?', + 'is %s identical to %s ?': + 'je %s identi\u010Den %s ?', 'type of %s': 'Tip od %s', @@ -580,6 +650,8 @@ SnapTranslator.dict.si = { // snap menu 'About...': 'Nekaj o Snap!...', + 'Reference manual': + 'Uporabni\u0161ka navodila', 'Snap! website': 'Spletna stran Snap!', 'Download source': @@ -602,6 +674,11 @@ SnapTranslator.dict.si = { 'Odpri...', 'Save': 'Shrani', + 'Save to disk': + 'Shrani na disk', + 'store this project\nin the downloads folder\n(in supporting browsers)': + 'shrani v mapo Prenosi\n' + + '(ni na voljo v vseh brkljalnika)', 'Save As...': 'Shrani kot...', 'Import...': @@ -620,10 +697,50 @@ SnapTranslator.dict.si = { 'Izvozi bloke', 'show global custom block definitions as XML\nin a new browser window': 'Prikaz definicij globalnih lastnih blokov kot XML\nv novem oknu brkljalnika', + 'Unused blocks...': + 'Neuporabljeni bloki...', + 'find unused global custom blocks\nand remove their definitions': + 'najdi in odstrani uporabni\u0161ke neuporabljene globalne bloke', + 'Remove unused blocks': + 'Odstrani neuporabljene bloke', + 'there are currently no unused\nglobal custom blocks in this project': + 'trenutno ni neuporabljenih globalnih blokov v tem projektu', + 'unused block(s) removed': + 'neuporabljeni bloki so bili odstranjeni', + 'Export summary...': + 'Povzetek izvoza...', + 'Import tools': + 'Uvozi orodja', + 'load the official library of\npowerful blocks': + 'uvozi uradni modul z naprednimi bloki', + 'Libraries...': + 'Knji\u017Enice...', + 'Import library': + 'Nalo\u017Ei knji\u017Enico', + + // cloud menu + 'Login...': + 'Prijava...', + 'Signup...': + 'Registracija...', + 'Reset Password...': + 'Pozabljeno geslo...', // settings menu 'Language...': 'Jezik...', + 'Zoom blocks...': + 'Pove\u010Daj bloke...', + 'Stage size...': + 'Velikost scene...', + 'Stage size': + 'Velikost scene', + 'Stage width': + '\u0160irina scene', + 'Stage height': + 'Vi\u0161ina scene', + 'Default': + 'Normalno', 'Blurred shadows': 'Mehke sence', 'uncheck to use solid drop\nshadows and highlights': @@ -637,9 +754,9 @@ SnapTranslator.dict.si = { 'uncheck to disable alternating\ncolors for nested block': 'izklopi izmenjujo\u010De barve gnezdenih blokov', 'Prefer empty slot drops': - 'Imejmo raje prazne reže', + 'Imejmo raje prazne re\u017Ee', 'settings menu prefer empty slots hint': - 'vklop raje namiga za prazne reže' + 'vklop raje namiga za prazne re\u017Ee' + 'zu bevorzugen', 'uncheck to allow dropped\nreporters to kick out others': 'razkljukaj za to, da reporterji odrinejo druge', @@ -667,12 +784,46 @@ SnapTranslator.dict.si = { 'razkljukaj za deaktiviranje akusti\u010Dnega klikanja', 'check to turn\nblock clicking\nsound on': 'odkljukaj za vklop akusti\u010Dnega klikanja', + 'Animations': + 'Animacije', + 'uncheck to disable\nIDE animations': + 'razkljukaj za izklop IDE animacij', + 'Turbo mode': + 'Hitri na\u010Din', + 'check to prioritize\nscript execution': + 'odkljukaj za ve\u010Djo prioriteto izvajanja skript', + 'uncheck to run scripts\nat normal speed': + 'razkljukaj za normalno hitrost izvajanja skript', + 'check to enable\nIDE animations': + 'odkljukaj za IDE animacije', + 'Flat design': + 'Svetli izgled', + 'Keyboard Editing': + 'Urejanje s tipkovnico', + 'Table support': + 'Podpora za tabele', + 'Table lines': + '\u010Crte med celicami v tabeli', 'Thread safe scripts': 'Varnost niti', - 'uncheck to allow\nscript reentrancy': + 'uncheck to allow\nscript reentrance': 'razkljukaj za dopu\u0161\u010Danje ve\u010Dkraten vstop skript (reentrancy)', - 'check to disallow\nscript reentrancy': + 'check to disallow\nscript reentrance': 'odkljukaj za onemogo\u010Danje ve\u010Dkratnega vstopa skript', + 'Prefer smooth animations': + 'Gladka animacija', + 'uncheck for greater speed\nat variable frame rates': + 'razkljukaj za hitrej\u0161e animacije s spremenljivo hitrostjo osve\u017Eevanja', + 'check for smooth, predictable\nanimations across computers': + 'odkljukaj za bolj predvidljivo hitrost animacij med razli\u010Dnimi ra\u010Dunalniki', + 'Flat line ends': + 'Ravni zaklju\u010Dki \u010Drt', + 'check for flat ends of lines': + 'odkljukaj za ravne zaklju\u010Dke \u010Drt', + 'uncheck for round ends of lines': + 'razkljukaj za zaobljene zaklju\u010Dke \u010Drt', + 'Inheritance support': + 'Podpora za dedovanje', // inputs 'with inputs': @@ -681,18 +832,30 @@ SnapTranslator.dict.si = { 'imena vhodov:', 'Input Names:': 'imena vhodov:', + 'input list:': + 'vhodni seznam:', // context menus: 'help': - 'Pomo\u010D', + 'Pomo\u010D...', + + // palette: + 'hide primitives': + 'skrij osnovne bloke', + 'show primitives': + 'poka\u017Ei osnovne bloke', // blocks: 'help...': - 'Pomo\u010D...', + 'pomo\u010D...', + 'relabel...': + 'spremeni tip...', 'duplicate': 'podvoji', 'make a copy\nand pick it up': 'kopiraj', + 'only duplicate this block': + 'podvoji ta blok', 'delete': 'bri\u0161i', 'script pic...': @@ -703,6 +866,8 @@ SnapTranslator.dict.si = { 'Obkro\u017Ei', 'unringify': 'odstrani obro\u010D', + 'transient': + 'se ne shranjuje', // custom blocks: 'delete block definition...': @@ -713,9 +878,23 @@ SnapTranslator.dict.si = { // sprites: 'edit': 'uredi', + 'move': + 'premakni', + 'detach from': + 'odklopi', + 'detach all parts': + 'odklopi vse dele', 'export...': 'izvozi...', + // stage: + 'show all': + 'prila\u017Ei vse ', + 'pic...': + 'izvozi sliko...', + 'open a new window\nwith a picture of the stage': + 'odpri novo okno s sliko te scene', + // scripting area 'clean up': 'po\u010Disti', @@ -723,6 +902,14 @@ SnapTranslator.dict.si = { 'uredi skripte vertikalno', 'add comment': 'dodaj komentar', + 'undrop': + 'ponovno povle\u010Di', + 'undo the last\nblock drop\nin this pane': + 'prekli\u010Di dodajanje zadnjega bloka v tem okviru', + 'scripts pic...': + 'slika skript...', + 'open a new window\nwith a picture of all scripts': + 'odpri novo okno s sliko vseh skript', 'make a block...': 'Gradnja novega bloka...', @@ -731,6 +918,8 @@ SnapTranslator.dict.si = { 'preimenuj', 'export': 'izvozi', + 'rename costume': + 'preimenuj izgled', // sounds 'Play sound': @@ -741,11 +930,25 @@ SnapTranslator.dict.si = { 'Ustavi', 'Play': 'Predvajaj', + 'rename sound': + 'Preimenuj zvok', + + // lists and tables + 'list view...': + 'prika\u017Ei kot seznam...', + 'table view...': + 'prika\u017Ei kot tabelo', + 'open in dialog...': + 'odpri v novem oknu', + 'items': + 'elementi', // dialogs // buttons 'OK': 'V redu', + 'Ok': + 'V redu', 'Cancel': 'Prekli\u010Di', 'Yes': @@ -757,6 +960,46 @@ SnapTranslator.dict.si = { 'Help': 'Pomo\u010D', + // zoom blocks + 'Zoom blocks': + 'Pove\u010Daj blok', + 'build': + 'zgradi', + 'your own': + 'svoj', + 'blocks': + 'blok', + 'normal (1x)': + 'normalno (1x)', + 'demo (1.2x)': + 'demo (1.2x)', + 'presentation (1.4x)': + 'predstavitev(1.4x)', + 'big (2x)': + 'veliko (2x)', + 'huge (4x)': + 've\u010Dje(4x)', + 'giant (8x)': + 'ogromno (8x)', + 'monstrous (10x)': + 'najve\u010Dje(10x)', + + // Project Manager + 'Untitled': + 'Neimenovano', + 'Open Project': + 'Odpri projekt', + '(empty)': + '(prazno)', + 'Saved!': + 'Shranjeno!', + 'Delete Project': + 'Zbri\u0161i projekt', + 'Are you sure you want to delete': + 'Ste prepri\u010Dani da \u017Eelite izbrisati?', + 'rename...': + 'preimenuj...', + // costume editor 'Costume Editor': 'Urejevalnik oblek', @@ -773,10 +1016,6 @@ SnapTranslator.dict.si = { 'Replace the current project with a new one?': 'Zamenjam trenutni projekt z novim?', - // open project - 'Open Project': - 'Odpri projekt', - // save project 'Save Project As...': 'Shrani projekt kot...', @@ -784,6 +1023,8 @@ SnapTranslator.dict.si = { // export blocks 'Export blocks': 'Izvoz blokov', + 'Import blocks': + 'Uvoz blokov', 'this project doesn\'t have any\ncustom global blocks yet': 'ta projekt \u0161e nima lastnih globalnih blokov', 'select': @@ -827,6 +1068,8 @@ SnapTranslator.dict.si = { 'Tvori ime vhoda', 'Edit input name': 'Uredi ime vhoda', + 'Edit label fragment': + 'Uredi ime dela', 'Title text': 'Naslovno besedilo', 'Input name': @@ -895,6 +1138,8 @@ SnapTranslator.dict.si = { 'min vrednost...', 'slider max...': 'maks vrednost...', + 'import...': + 'uvozi...', 'Slider minimum value': 'Minimalna vrednost drsnika', 'Slider maximum value': @@ -930,11 +1175,16 @@ SnapTranslator.dict.si = { // costumes 'Turtle': 'Kazalec smeri', + 'Empty': + 'Prazno', // graphical effects + 'brightness': + 'svetlost', 'ghost': 'prosojnost', - + 'negative': + 'obratno', // keys 'space': 'presledek', @@ -946,6 +1196,8 @@ SnapTranslator.dict.si = { 'pu\u0161\u010Dica desno', 'left arrow': 'pu\u0161\u010Dica levo', + 'any key': + 'poljuden', 'a': 'a', 'b': @@ -1026,6 +1278,10 @@ SnapTranslator.dict.si = { // math functions 'abs': 'abs', + 'ceiling': + 'zaokro\u017Eevanje navzgor', + 'floor': + 'zaokro\u017Eevanje navzdol', 'sqrt': 'koren', 'sin': @@ -1045,6 +1301,16 @@ SnapTranslator.dict.si = { 'e^': 'e^', + // delimiters + 'letter': + '\u010Drke', + 'whitespace': + 'presledki', + 'line': + 'vrstica', + 'tab': + 'tab', + // data types 'number': '\u0161tevilo', diff --git a/lang-sv.js b/lang-sv.js index 382f2bd4..acba22f2 100644 --- a/lang-sv.js +++ b/lang-sv.js @@ -187,7 +187,7 @@ SnapTranslator.dict.sv = { 'translator_e-mail': 'eolsson@gmail.com', // optional 'last_changed': - '2014-11-01', // this, too, will appear in the Translators tab + '2016-06-09', // this, too, will appear in the Translators tab // GUI // control bar: @@ -250,7 +250,7 @@ SnapTranslator.dict.sv = { // tab help 'costumes tab help': - 'importera en bild fr\u00E5n en annan webbsida eller fr\u00E5n\nen fil på din dator genom att dra den hit', + 'importera en bild fr\u00E5n en annan webbsida eller fr\u00E5n\nen fil p\u00E5 din dator genom att dra den hit', 'import a sound from your computer\nby dragging it into here': 'importera en ljudfil fr\u00E5n din dator\ngenom att dra den hit', @@ -331,6 +331,8 @@ SnapTranslator.dict.sv = { 'n\u00E4sta kostym', 'costume #': 'kostym nr.', + 'costume name': + 'kostymnamn', 'say %s for %n secs': 's\u00E4g %s i %n sek', 'say %s': @@ -412,6 +414,8 @@ SnapTranslator.dict.sv = { 's\u00E4tt penntjocklek til %n', 'stamp': 'st\u00E4mpla', + 'fill': + 'fyll', // control: 'when %greenflag clicked': @@ -443,7 +447,7 @@ SnapTranslator.dict.sv = { 'if %b %c else %c': 'om %b %c d\u00E5 %c', 'report %s': - 'returnera %s', + 'rapportera %s', 'stop block': 'stoppa block', 'stop script': @@ -471,6 +475,25 @@ SnapTranslator.dict.sv = { 'mig sj\u00E4lv', 'delete this clone': 'radera klon', + 'when I am %interaction': + 'n\u00E4r jag %interaction', + 'when %b': + 'n\u00E4r %b', + 'clicked': + 'klickas p\u00E5', + 'pressed': + 'trycks ned', + 'dropped': + 'sl\u00E4pps ned', + 'mouse-entered': + 'f\u00E5r muspekaren \u00F6ver mig', + 'mouse-departed': + 'inte l\u00E4ngre har muspekaren \u00F6ver mig', + 'when I am clicked': + 'n\u00E4r jag klickas p\u00E5', + 'when I receive %msgHat': + 'n\u00E4r jag tar emot %msgHat', + 'warp %c': @@ -480,7 +503,7 @@ SnapTranslator.dict.sv = { 'touching %col ?': 'r\u00F6r %col ?', 'touching %clr ?': - 'r\u00F6r %clr ?', + 'r\u00F6r f\u00E4rgen %clr ?', 'color %clr is touching %clr ?': 'f\u00E4rgen %clr r\u00F6r %clr ?', 'ask %s and wait': @@ -518,14 +541,42 @@ SnapTranslator.dict.sv = { 'stack-storlek', 'frames': 'ramar', + + + '%att of %spr': + '%att av %spr', + 'my %get': + 'attribut %get', + + + 'current %dates': + '%dates just nu', + + 'year': + '\u00E5ret', + 'month': + 'm\u00E5naden', + 'date': + 'datum', + 'hour': + 'timmen', + 'minute': + 'minuten', + 'second': + 'sekunden', + 'time in milliseconds': + 'tiden i millisekunder', + 'day of week': + 'veckodagen', + // operators: '%n mod %n': '%n mod %n', 'round %n': 'avrunda %n', - '%fun av %n': - '%fun von %n', + '%fun of %n': + '%fun av %n', 'pick random %n to %n': 'slumptal fr\u00E5n %n till %n', '%b and %b': @@ -558,7 +609,7 @@ SnapTranslator.dict.sv = { '%s identisk med %s ?', 'type of %s': - 'type %s', + 'typ %s', // variables: 'Make a variable': @@ -638,7 +689,7 @@ SnapTranslator.dict.sv = { 'Import...': 'Importera...', 'file menu import hint': - 'l\u00E4ser in ett exporterat projekt,\nett bibliotek med block,\nen kostym, eller ett ljud', + 'l\u00E4ser in ett exporterat projekt,\nett bibliotek med block,\nen kostym, eller ett ljud', 'Export project as plain text ...': 'Exportera projektet som vanlig text...', 'Export project...': @@ -784,6 +835,10 @@ SnapTranslator.dict.sv = { 'redigera', 'export...': 'exportera...', + 'parent...': + 'f\u00F6r\u00E4lder...', + 'current parent': + 'nuvarande f\u00F6r\u00E4lder', // stage: 'show all': @@ -910,10 +965,10 @@ SnapTranslator.dict.sv = { // block deletion dialog 'Delete Custom Block': - 'Radera custom blokk', + 'Radera block', 'block deletion dialog text': - 'Skal denne blokken med alle dens instanser\n' + - 'bli slettet?', + 'Ska detta block med alla dess instanser\n' + + 'tas bort?', // input dialog 'Create input name': @@ -1141,6 +1196,10 @@ SnapTranslator.dict.sv = { 'ln', 'e^': 'e^', + 'floor': + 'golv', + 'ceiling': + 'tak', // data types 'number': @@ -1163,197 +1222,248 @@ SnapTranslator.dict.sv = { 'sista', 'any': 'vilken som helst', + + + // attributes + 'neighbors': + 'grannar', + 'self': + 'mig sj\u00E4lv', + 'other sprites': + 'andra sprites', + 'parts': + 'delar', + 'anchor': + 'ankare', + 'parent': + 'f\u00F6r\u00E4ldrar', + 'children': + 'barn', + 'clones': + 'kloner', + 'other clones': + 'andra kloner', + 'dangling?': + 'h\u00E4ngande?', + 'rotation x': + 'rotation x', + 'rotation y': + 'rotation y', + 'center x': + 'mittpunkt x', + 'center y': + 'mittpunkt y', + 'name': + 'namn', + 'stage': + 'scen', // missing labels from initial translation added below 'add a new sprite': - 'ny sprite', - 'when %keyHat key pressed': - 'n\u00E4r tangent %keyHat trycks ned', - 'when I receive %msgHat': - 'n\u00E4r jag tar emot %msgHat', - 'message': - 'meddelande', - 'any message': - 'n\u00E5got meddelande', - 'stop %stopChoices': - 'stoppa %stopChoices', - 'this script': - 'detta skript', - 'this block': - 'detta block', - 'stop %stopOthersChoices': - 'stoppa %stopOthersChoices', - 'all but this script': - 'alla f\u00F6rutom detta skript', - 'other scripts in sprite': - 'andra skript i denna sprite', - '%att of %spr': - '%att av %spr', - '%fun of %n': - '%fun av %n', - 'split %s by %delim': - 'dela %s med tecken %delim', - 'Script variable name': - 'Skriptvariabelnamn', - 'Reference manual': - 'Referensbok', - 'Export project as plain text...': - 'Exportera projektet som vanlig text...', - 'Import tools': - 'Importverktyg', - 'Signup...': - 'Registrera...', - 'Stage size...': - 'Scenstorlek...', - 'Stage size': - 'Scenstorlek', - 'Stage width': - 'Scenbredd', - 'Stage height': - 'Scenh\u00F6jd', - 'Default': - 'Standard', - 'Plain prototype labels': - 'Vanliga prototypetiketter', - 'uncheck to always show (+) symbols\nin block prototype labels': - 'avmarkera f\u00F6r att visa (+) symboler \n i blockprototypetiketter', - 'check to hide (+) symbols\nin block prototype labels': - 'kryssa f\u00F6r att visa (+) symboler \n i blockprototypetiketter', - 'check to prioritize\nscript execution': - 'kryssa f\u00F6r att prioritera \nskriptexekvering', - 'uncheck to run scripts\nat normal speed': - 'avmarkera f\u00F6r att k\u00F6ra \nskript vid normal hastighet', - 'uncheck to allow\nscript reentrance': - 'avmarkera f\u00F6r att till\u00E5ta \nskript att \u00E5tertilltr\u00E4da', - 'check to disallow\nscript reentrance': - 'kryssa f\u00F6r att f\u00F6rbjuda \nskript att \u00E5tertilltr\u00E4da', - 'Flat line ends': - 'Platta streckslut', - 'check for flat ends of lines': - 'kryssa f\u00F6r platta streckslut', - 'uncheck for round ends of lines': - 'avmarkera f\u00F6r avrundade streckslut', - 'hide primitives': - 'g\u00F6m primitiva', - 'show primitives': - 'visa primitiva', - 'help...': - 'hj\u00E4lp...', - 'move': - 'flytta', - 'detach from': - 'koppla bort', - 'detach all parts': - 'koppla bort alla delar', - 'pic...': - 'bild...', - 'open a new window\nwith a picture of the stage': - '\u00F6ppna ett nytt f\u00F6nster\nmed en bild av scenen', - 'undrop': - '\u00E5ngra sl\u00E4pp', - 'undo the last\nblock drop\nin this pane': - '\u00E5ngra sista \nblocksl\u00E4ppet i\ndetta omr\u00E5de', - 'scripts pic...': - 'skriptbild...', - 'open a new window\nwith a picture of all scripts': - '\u00F6ppna ett nytt f\u00F6nster\nmed en bild p\u00E5 alla skript', - 'Zoom blocks': - 'F\u00F6rstora blocken', - 'build': - 'bygg', - 'your own': - 'dina egna', - 'blocks': - 'block', - 'normal (1x)': - 'normal (1x)', - 'demo (1.2x)': - 'demo (1.2x)', - 'presentation (1.4x)': - 'presentation (1.4x)', - 'big (2x)': - 'stor (2x)', - 'huge (4x)': - 'j\u00E4ttestor (4x)', - 'giant (8x)': - 'enorm (8x)', - 'monstrous (10x)': - 'gigantisk (10x)', - 'Empty': - 'Tom', - 'brightness': - 'ljusstyrke', - 'negative': - 'negativ', - 'comic': - 'komisk', - 'confetti': - 'konfetti', - 'floor': - 'golv', - 'letter': - 'bokstav', - 'whitespace': - 'mellanslag', - 'line': - 'rad', - 'tab': - 'tab', - 'cr': - 'retur', - 'warp %c': - 'snabbspola %c', - 'Reset Password...': - 'Nollst\u00E4ll l\u00F6senord...', - 'Codification support': - 'St\u00F6d f\u00F6r textprogrammering', - 'Flat design': - 'Platt utseende', - 'check for block\nto text mapping features': - 'kryssa f\u00F6r att aktivera\nblock-till-text funktioner', - 'uncheck to disable\nblock to text mapping features': - 'avmarkera f\u00F6r att inaktivera\nblock-till-text funktioner', - 'check for alternative\nGUI design': - 'kryssa f\u00F6r att aktivera ett\nalternativt utseende', - 'uncheck for default\nGUI design': - 'avmarkera f\u00F6r att byta\ntill standardutseendet', - 'Select categories of additional blocks to add to this project.': - 'v\u00E4lj grupper av extrablock att l\u00E4gga till i projektet', - 'Select a costume from the media library': - 'v\u00E4lj en kostym fr\u00E5n mediabiblioteket', - 'Select a sound from the media library': - 'v\u00E4lj ett ljud fr\u00E5n mediabiblioteket', - 'Iteration, composition': - 'Upprepning, komposition', - 'List utilities': - 'Listverktyg', - 'Streams (lazy lists)': - 'Str\u00F6mmar (lata listor)', - 'Variadic reporters': - 'Variabla rapporterare', - 'Words, sentences': - 'Ord, meningar', - 'Paint a new costume': - 'Rita en ny kostym', - 'add a new Turtle sprite': - 'l\u00E4gg till en ny Sk\u00F6ldpadda-sprite', - 'paint a new sprite': - 'rita en ny sprite', - 'Paint Editor': - 'Rita', - 'undo': - '\u00E5ngra', - 'grow': - 'st\u00F6rre', - 'shrink': - 'mindre', - 'flip ↔': - 'v\u00E4nd ↔', - 'flip ↕': - 'v\u00E4nd ↕', - 'Brush size': - 'Pennstorlek', - 'Constrain proportions of shapes?\n(you can also hold shift)': - 'Beh\u00E5ll figurernas proportioner?\n(du kan ocks\u00E5 h\u00E5lla skift nedtryckt)' - + 'ny sprite', + 'when %keyHat key pressed': + 'n\u00E4r tangent %keyHat trycks ned', + 'when I receive %msgHat': + 'n\u00E4r jag tar emot %msgHat', + 'message': + 'meddelande', + 'any message': + 'n\u00E5got meddelande', + 'stop %stopChoices': + 'stoppa %stopChoices', + 'this script': + 'detta skript', + 'this block': + 'detta block', + 'stop %stopOthersChoices': + 'stoppa %stopOthersChoices', + 'all but this script': + 'alla f\u00F6rutom detta skript', + 'other scripts in sprite': + 'andra skript i denna sprite', + '%att of %spr': + '%att av %spr', + '%fun of %n': + '%fun av %n', + 'split %s by %delim': + 'dela %s med tecken %delim', + 'Script variable name': + 'Skriptvariabelnamn', + 'Reference manual': + 'Referensbok', + 'Export project as plain text...': + 'Exportera projektet som vanlig text...', + 'Import tools': + 'Importverktyg', + 'Signup...': + 'Registrera...', + 'Stage size...': + 'Scenstorlek...', + 'Stage size': + 'Scenstorlek', + 'Stage width': + 'Scenbredd', + 'Stage height': + 'Scenh\u00F6jd', + 'Default': + 'Standard', + 'Plain prototype labels': + 'Vanliga prototypetiketter', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'avmarkera f\u00F6r att visa (+) symboler \n i blockprototypetiketter', + 'check to hide (+) symbols\nin block prototype labels': + 'kryssa f\u00F6r att visa (+) symboler \n i blockprototypetiketter', + 'check to prioritize\nscript execution': + 'kryssa f\u00F6r att prioritera \nskriptexekvering', + 'uncheck to run scripts\nat normal speed': + 'avmarkera f\u00F6r att k\u00F6ra \nskript vid normal hastighet', + 'uncheck to allow\nscript reentrance': + 'avmarkera f\u00F6r att till\u00E5ta \nskript att \u00E5tertilltr\u00E4da', + 'check to disallow\nscript reentrance': + 'kryssa f\u00F6r att f\u00F6rbjuda \nskript att \u00E5tertilltr\u00E4da', + 'Flat line ends': + 'Platta streckslut', + 'check for flat ends of lines': + 'kryssa f\u00F6r platta streckslut', + 'uncheck for round ends of lines': + 'avmarkera f\u00F6r avrundade streckslut', + 'hide primitives': + 'g\u00F6m primitiva', + 'show primitives': + 'visa primitiva', + 'help...': + 'hj\u00E4lp...', + 'move': + 'flytta', + 'detach from': + 'koppla bort', + 'detach all parts': + 'koppla bort alla delar', + 'pic...': + 'bild...', + 'open a new window\nwith a picture of the stage': + '\u00F6ppna ett nytt f\u00F6nster\nmed en bild av scenen', + 'undrop': + '\u00E5ngra sl\u00E4pp', + 'undo the last\nblock drop\nin this pane': + '\u00E5ngra sista \nblocksl\u00E4ppet i\ndetta omr\u00E5de', + 'scripts pic...': + 'skriptbild...', + 'open a new window\nwith a picture of all scripts': + '\u00F6ppna ett nytt f\u00F6nster\nmed en bild p\u00E5 alla skript', + 'Zoom blocks': + 'F\u00F6rstora blocken', + 'build': + 'bygg', + 'your own': + 'dina egna', + 'blocks': + 'block', + 'normal (1x)': + 'normal (1x)', + 'demo (1.2x)': + 'demo (1.2x)', + 'presentation (1.4x)': + 'presentation (1.4x)', + 'big (2x)': + 'stor (2x)', + 'huge (4x)': + 'j\u00E4ttestor (4x)', + 'giant (8x)': + 'enorm (8x)', + 'monstrous (10x)': + 'gigantisk (10x)', + 'Empty': + 'Tom', + 'brightness': + 'ljusstyrka', + 'negative': + 'negativ', + 'comic': + 'komisk', + 'confetti': + 'konfetti', + 'letter': + 'bokstav', + 'whitespace': + 'mellanslag', + 'line': + 'rad', + 'tab': + 'tab', + 'cr': + 'retur', + 'warp %c': + 'snabbspola %c', + 'Reset Password...': + 'Nollst\u00E4ll l\u00F6senord...', + 'Codification support': + 'St\u00F6d f\u00F6r textprogrammering', + 'Flat design': + 'Platt utseende', + 'Keyboard Editing': + 'Tangentbordsredigering', + 'Table support': + 'Tabellstöd', + 'Inheritance support': + 'Arv', + 'uncheck to disable\nsprite inheritance features': + 'avmarkera f\u00F6r att inaktivera\nst\u00F6d f\u00F6r arv mellan sprites', + 'check for sprite\ninheritance features': + 'kryssa f\u00F6r att aktivera\nst\u00F6d f\u00F6r arv mellan sprites', + 'uncheck to disable\nmulti-column list views': + 'avmarkera f\u00F6r att inaktivera\nst\u00F6d f\u00F6r redigering av listor i flera kolumner', + 'check for multi-column\nlist view support': + 'kryssa f\u00F6r att aktivera\nst\u00F6d f\u00F6r redigering av listor i flera kolumner', + 'check to enable\nkeyboard editing support': + 'kryssa f\u00F6r att aktivera\ntangentbordsredigering', + 'uncheck to disable\nkeyboard editing support': + 'avmarkera f\u00F6r att inaktivera\ntangentbordsredigering', + 'check for block\nto text mapping features': + 'kryssa f\u00F6r att aktivera\nblock-till-text funktioner', + 'uncheck to disable\nblock to text mapping features': + 'avmarkera f\u00F6r att inaktivera\nblock-till-text funktioner', + 'check for alternative\nGUI design': + 'kryssa f\u00F6r att aktivera ett\nalternativt utseende', + 'uncheck for default\nGUI design': + 'avmarkera f\u00F6r att byta\ntill standardutseendet', + 'Select categories of additional blocks to add to this project.': + 'v\u00E4lj grupper av extrablock att l\u00E4gga till i projektet', + 'Select a costume from the media library': + 'v\u00E4lj en kostym fr\u00E5n mediabiblioteket', + 'Select a sound from the media library': + 'v\u00E4lj ett ljud fr\u00E5n mediabiblioteket', + 'Iteration, composition': + 'Upprepning, komposition', + 'List utilities': + 'Listverktyg', + 'Streams (lazy lists)': + 'Str\u00F6mmar (lata listor)', + 'Variadic reporters': + 'Variabla rapporterare', + 'Words, sentences': + 'Ord, meningar', + 'Paint a new costume': + 'Rita en ny kostym', + 'add a new Turtle sprite': + 'l\u00E4gg till en ny Sk\u00F6ldpadda-sprite', + 'paint a new sprite': + 'rita en ny sprite', + 'Paint Editor': + 'Rita', + 'undo': + '\u00E5ngra', + 'grow': + 'st\u00F6rre', + 'shrink': + 'mindre', + 'flip ↔': + 'v\u00E4nd ↔', + 'flip ↕': + 'v\u00E4nd ↕', + 'Brush size': + 'Pennstorlek', + 'Constrain proportions of shapes?\n(you can also hold shift)': + 'Beh\u00E5ll figurernas proportioner?\n(du kan ocks\u00E5 h\u00E5lla skift nedtryckt)' + }; diff --git a/lang-zh.js b/lang-zh.js index 71bff59b..d6bdb6a6 100644 --- a/lang-zh.js +++ b/lang-zh.js @@ -1,1214 +1,2068 @@ -/* - - lang-zh.js - - Simplified Chinese translation for SNAP! - SNAP 简体中文翻译版 - - written by Jens Mönig - - Copyright (C) 2012 by Jens Mönig - - This file is part of Snap!. - - Snap! is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - - - - Note to Translators: - -------------------- - At this stage of development, Snap! can be translated to any LTR language - maintaining the current order of inputs (formal parameters in blocks). - - Translating Snap! is easy: - - - 1. Download - - Download the sources and extract them into a local folder on your - computer: - - - - Use the German translation file (named 'lang-de.js') as template for your - own translations. Start with editing the original file, because that way - you will be able to immediately check the results in your browsers while - you're working on your translation (keep the local copy of snap.html open - in your web browser, and refresh it as you progress with your - translation). - - - 2. Edit - - Edit the translation file with a regular text editor, or with your - favorite JavaScript editor. - - In the first non-commented line (the one right below this - note) replace "de" with the two-letter ISO 639-1 code for your language, - e.g. - - fr - French => SnapTranslator.dict.fr = { - it - Italian => SnapTranslator.dict.it = { - pl - Polish => SnapTranslator.dict.pl = { - pt - Portuguese => SnapTranslator.dict.pt = { - es - Spanish => SnapTranslator.dict.es = { - el - Greek => => SnapTranslator.dict.el = { - - etc. (see ) - - - 3. Translate - - Then work through the dictionary, replacing the German strings against - your translations. The dictionary is a straight-forward JavaScript ad-hoc - object, for review purposes it should be formatted as follows: - - { - 'English string': - 'Translation string', - 'last key': - } 'last value' - - and you only edit the indented value strings. Note that each key-value - pair needs to be delimited by a comma, but that there shouldn't be a comma - after the last pair (again, just overwrite the template file and you'll be - fine). - - If something doesn't work, or if you're unsure about the formalities you - should check your file with - - - - This will inform you about any missed commas etc. - - - 4. Accented characters - - Depending on which text editor and which file encoding you use you can - directly enter special characters (e.g. Umlaut, accented characters) on - your keyboard. However, I've noticed that some browsers may not display - special characters correctly, even if other browsers do. So it's best to - check your results in several browsers. If you want to be on the safe - side, it's even better to escape these characters using Unicode. - - see: - - - 5. Block specs: - - At this time your translation of block specs will only work - correctly, if the order of formal parameters and their types - are unchanged. Placeholders for inputs (formal parameters) are - indicated by a preceding % prefix and followed by a type - abbreviation. - - For example: - - 'say %s for %n secs' - - can currently not be changed into - - 'say %n secs long %s' - - and still work as intended. - - Similarly - - 'point towards %dst' - - cannot be changed into - - 'point towards %cst' - - without breaking its functionality. - - - 6. Submit - - When you're done, rename the edited file by replacing the "de" part of the - filename with the two-letter ISO 639-1 code for your language, e.g. - - fr - French => lang-fr.js - it - Italian => lang-it.js - pl - Polish => lang-pl.js - pt - Portuguese => lang-pt.js - es - Spanish => lang-es.js - el - Greek => => lang-el.js - - and send it to me for inclusion in the official Snap! distribution. - Once your translation has been included, Your name will the shown in the - "Translators" tab in the "About Snap!" dialog box, and you will be able to - directly launch a translated version of Snap! in your browser by appending - - lang:xx - - to the URL, xx representing your translations two-letter code. - - - 7. Known issues - - In some browsers accents or ornaments located in typographic ascenders - above the cap height are currently (partially) cut-off. - - Enjoy! - -Jens -*/ - -/*global SnapTranslator*/ - -SnapTranslator.dict.zh = { - -/* - Special characters: (see ) - - Ä, ä \u00c4, \u00e4 - Ö, ö \u00d6, \u00f6 - Ü, ü \u00dc, \u00fc - ß \u00df -*/ - - // translations meta information - 'language_name': - '简体中文', // the name as it should appear in the language menu - 'language_translator': - '邓江华', // your name for the Translators tab - 'translator_e-mail': - 'djh@rhjxx.cn', // optional - 'last_changed': - '2013-3-25', // this, too, will appear in the Translators tab - - // GUI - // control bar: - 'untitled': - '无标题', - 'development mode': - '开发模式', - - // categories: - 'Motion': - '动作', - 'Looks': - '外观', - 'Sound': - '声音', - 'Pen': - '画笔', - 'Control': - '控制', - 'Sensing': - '侦测', - 'Operators': - '运算', - 'Variables': - '变量', - 'Lists': - '链表', - 'Other': - '其他', - - // editor: - 'draggable': - '可拖动', - - // tabs: - 'Scripts': - '脚本', - 'Costumes': - '造型', - 'Sounds': - '声音', - - // names: - 'Sprite': - '角色', - 'Stage': - '舞台', - - // rotation styles: - 'don\'t rotate': - '不能旋转', - 'can rotate': - '可以旋转', - 'only face left/right': - '只能水平翻转', - - // new sprite button: - 'add a new sprite': - '新建角色', - - // tab help - 'costumes tab help': - '造型选卡帮助\n要使用另外网站上的图片或计算机中的图像' - + '只需拖到图像到这里即可', - 'import a sound from your computer\nby dragging it into here': - '从计算机中导入声音文件\n只需拖动声音文件到这里即可', - - // primitive blocks: - - /* - Attention Translators: - ---------------------- - At this time your translation of block specs will only work - correctly, if the order of formal parameters and their types - are unchanged. Placeholders for inputs (formal parameters) are - indicated by a preceding % prefix and followed by a type - abbreviation. - - For example: - - 'say %s for %n secs' - - can currently not be changed into - - 'say %n secs long %s' - - and still work as intended. - - Similarly - - 'point towards %dst' - - cannot be changed into - - 'point towards %cst' - - without breaking its functionality. - */ - - // motion: - 'Stage selected:\nno motion primitives': - '舞台选择:\n没有动作程序语言', - - 'move %n steps': - '移动 %n 歩', - 'turn %clockwise %n degrees': - '旋转 %clockwise %n 度', - 'turn %counterclockwise %n degrees': - '旋转 %counterclockwise %n 度', - 'point in direction %dir': - '面向 %dir 度', - 'point towards %dst': - '面向 %dst ', - 'go to x: %n y: %n': - '移到 x: %n y: %n ', - 'go to %dst': - '移到 %dst ', - 'glide %n secs to x: %n y: %n': - '在 %n 秒内,平滑移动到 x: %n y: %n ', - 'change x by %n': - '将x坐标增加 %n ', - 'set x to %n': - '将x坐标设定为 %n ', - 'change y by %n': - '将y坐标增加 %n ', - 'set y to %n': - '将y坐标设定为 %n ', - 'if on edge, bounce': - '碰到边缘就反弹', - 'x position': - 'x坐标', - 'y position': - 'y坐标', - 'direction': - '方向', - - // looks: - 'switch to costume %cst': - '切换到造型 %cst ', - 'next costume': - '下一个造型', - 'costume #': - '造型编号', - 'say %s for %n secs': - '说 %s %n 秒', - 'say %s': - '说 %s ', - 'think %s for %n secs': - '思考 %s %n 秒', - 'think %s': - '思考 %s ', - 'Hello!': - '你好!', - 'Hmm...': - '嗯...', - 'change %eff effect by %n': - '将 %eff 特效增加 %n ', - 'set %eff effect to %n': - '将 %eff 特效设定为 %n ', - 'clear graphic effects': - '清除所有图形特效', - 'change size by %n': - '将角色的大小增加 %n ', - 'set size to %n %': - '将角色的大小设定为 %n ', - 'size': - '大小', - 'show': - '显示', - 'hide': - '隐藏', - 'go to front': - '移至最上层', - 'go back %n layers': - '下移 %n 层', - - 'development mode \ndebugging primitives:': - '开发模式\n调式程序语言:', - 'console log %mult%s': - '控制台日志 %mult%s', - 'alert %mult%s': - '警告: %mult%s', - - // sound: - 'play sound %snd': - '播放声音 %snd ', - 'play sound %snd until done': - '播放声音 %snd 直到播放完毕', - 'stop all sounds': - '停止所有声音', - 'rest for %n beats': - '停止 %n 秒', - 'play note %n for %n beats': - '弹奏 %n %n 拍', - 'change tempo by %n': - '将节奏加快 %n', - 'set tempo to %n bpm': - '将节奏设定为 %n', - 'tempo': - '节奏', - - // pen: - 'clear': - '清除所有画笔', - 'pen down': - '落笔', - 'pen up': - '抬笔', - 'set pen color to %clr': - '将画笔的颜色设定为 %clr ', - 'change pen color by %n': - '将画笔的颜色值增加 %n ', - 'set pen color to %n': - '将画笔的颜色值设定为 %n ', - 'change pen shade by %n': - '将画笔的色度增加 %n ', - 'set pen shade to %n': - '将画笔的色度设定为 %n ', - 'change pen size by %n': - '将画笔的大小增加 %n ', - 'set pen size to %n': - '将画笔的大小设定为 %n ', - 'stamp': - '图章', - - // control: - 'when %greenflag clicked': - '当 %greenflag 被点击', - 'when %keyHat key pressed': - '当按下 %keyHat', - 'when I am clicked': - '当角色被点击', - 'when I receive %msgHat': - '当接收到 %msgHat', - 'broadcast %msg': - '广播 %msg ', - 'broadcast %msg and wait': - '广播 %msg 并等待', - 'Message name': - '信息名称', - 'wait %n secs': - '等待 %n 秒', - 'wait until %b': - '直到 %b 前都等待阗', - 'forever %c': - '重复执行 %c', - 'repeat %n %c': - '重复执行 %n %c', - 'repeat until %b %c': - '重复执行直到 %b %c', - 'if %b %c': - '如果 %b %c', - 'if %b %c else %c': - '如果 %b %c 否则 %c', - 'report %s': - '报告 %s ', - 'stop block': - '停止程序块', - 'stop script': - '停止脚本', - 'stop all %stop': - '全部停止 %stop', - 'run %cmdRing %inputs': - '运行 %cmdRing %inputs ', - 'launch %cmdRing %inputs': - '启动 %cmdRing %inputs ', - 'call %repRing %inputs': - '调用 %repRing %inputs ', - 'run %cmdRing w/continuation': - '持续运行 %cmdRing ', - 'call %cmdRing w/continuation': - '持续调用 %cmdRing ', - 'warp %c': - '直接运行 %c', - 'when I start as a clone': - '当我开始克隆', - 'create a clone of %cln': - '新建一个克隆 %cln', - 'myself': - '自己', - 'delete this clone': - '删除这个克隆', - - // sensing: - 'touching %col ?': - '碰到 %col ', - 'touching %clr ?': - '碰到颜色 %clr ', - 'color %clr is touching %clr ?': - '颜色 %clr 碰到了颜色 %clr ?', - 'ask %s and wait': - '询问 %s 并等待', - 'what\'s your name?': - '你的名字?', - 'answer': - '回答', - 'mouse x': - '鼠标的x坐标', - 'mouse y': - '鼠标的y坐标', - 'mouse down?': - '按下鼠标?', - 'key %key pressed?': - '按键 %key 是否按下?', - 'distance to %dst': - '到 %dst 的距离', - 'reset timer': - '计时器归零', - 'timer': - '计时器', - 'http:// %s': - 'http:// %s', - 'turbo mode?': - 'Turbo模式', - 'set turbo mode to %b': - '设置Turbo模式 %b', - - 'filtered for %clr': - '选择颜色 %clr ', - 'stack size': - '堆栈大小', - 'frames': - '框架', - - // operators: - '%n mod %n': - '%n 除以 %n 的余数', - 'round %n': - '将 %n 四舍五入', - '%fun of %n': - '%fun %n', - 'pick random %n to %n': - '在 %n 到 %n 间随机选一个数', - '%b and %b': - '%b 且 %b', - '%b or %b': - '%b 或 %b', - 'not %b': - '%b 不成立', - 'true': - '成立', - 'false': - '不成立', - 'join %words': - '将 %words 加入到', - 'hello': - '你好', - 'world': - '行者老邓', - 'letter %n of %s': - '第 %n 位在文字 %s 中', - 'length of %s': - '%s 的长度', - 'unicode of %s': - '字符 %s 的Unicode编码值', - 'unicode %n as letter': - 'Unicode编码值为 %n 的字符', - 'is %s a %typ ?': - '%s 是 %typ 类型?', - 'is %s identical to %s ?': - '%s 与 %s 相同吗?', - - 'type of %s': - '%s 类型', - - // variables: - 'Make a variable': - '新建一个变量', - 'Variable name': - '变量名', - 'Delete a variable': - '删除变量', - - 'set %var to %s': - '将变量 %var 的值设定为 %s ', - 'change %var by %n': - '将变量 %var 的值增加 %n ', - 'show variable %var': - '显示变量 %var ', - 'hide variable %var': - '隐藏变量 %var ', - 'script variables %scriptVars': - '脚本变量 %scriptVars', - - // lists: - 'list %exp': - '链表 %exp', - '%s in front of %l': - '设定 %s 为链表 %l 第一项', - 'item %idx of %l': - '第 %idx 项在链表 %l 中', - 'all but first of %l': - '链表 %l 除第一记录以外内容', - 'length of %l': - '链表 %l 的长度', - '%l contains %s': - '链表 %l 包含 %s ', - 'thing': - '东西', - 'add %s to %l': - '将 %s 加入链表 %l ', - 'delete %ida of %l': - '删除链表 %ida 第 %l 项', - 'insert %s at %idx of %l': - '将 %s 插入到第 %idx 项在 %l 中', - 'replace item %idx of %l with %s': - '将第 %idx 项在链表 %l 中替换为 %s ', - - // other - 'Make a block': - '新建程序块', - - // menus - // snap menu - 'About...': - '关于Snap!...', - 'Reference manual': - '参考手册', - 'Snap! website': - '官方网站', - 'Download source': - '下载源码', - 'Switch back to user mode': - '切换到用户模式', - 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': - '禁用 变形语式\n快捷菜单\n\n与非\n友好用户界面', - 'Switch to dev mode': - '切换到开发人员模式', - 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': - '启用 正常语式\n快捷菜单\n与非检查\n友好用户界面', - - - // project menu - 'Project notes...': - '项目说明...', - 'New': - '新建', - 'Open...': - '打开...', - 'Save': - '保存', - 'Save As...': - '另存为...', - 'Import...': - '导入...', - 'file menu import hint': - '当你拖动到系统,注意查看检查报告\n' - + '要注意检查报告为空\n\n' - + '有一些浏览器不支持这一功能', - 'Export project as plain text...': - '纯文本格式导出项目...', - 'Export project...': - '导出项目...', - 'show project data as XML\nin a new browser window': - '新浏览窗以XML格式显示项目', - 'Export blocks...': - '导出程序块...', - 'show global custom block definitions as XML\nin a new browser window': - '新浏览窗以XML格式显示全局自定义程序块', - 'Import tools': - '导入工具包', - 'load the official library of\npowerful blocks': - '载入官方库和强大的程序块', - - // cloud menu - 'Login...': - '登录...', - 'Signup...': - '注册...', - // settings menu - 'Language...': - '语言选择...', - 'Zoom blocks...': - '放大程序块...', - 'Blurred shadows': - '半透明阴影', - 'uncheck to use solid drop\nshadows and highlights': - '取消选中 降低阴影和高亮的清晰度', - 'check to use blurred drop\nshadows and highlights': - '检测 降低阴影和高亮的模糊度', - 'Zebra coloring': - '斑马着色', - 'check to enable alternating\ncolors for nested blocks': - '检测 使嵌套块的颜色交换', - 'uncheck to disable alternating\ncolors for nested block': - '取消选中 使嵌套块的颜色交换', - 'Dynamic input labels': - '动态输入标签', - 'uncheck to disable dynamic\nlabels for variadic inputs': - '取消选中要禁用动态可变参数输入标签', - '检查启用动态可变参数输入标签': - 'marcar para habilitar etiquetas\ndin\u00E1micas para entradas varidic', - 'Prefer empty slot drops': - '喜欢减少空槽', - 'settings menu prefer empty slots hint': - '喜欢空槽设置菜单', - 'uncheck to allow dropped\nreporters to kick out others': - '取消选中 允许下降报告并取消其它报告', - 'Long form input dialog': - '长形式输入对话框', - 'check to always show slot\ntypes in the input dialog': - '检查显示插槽在输入对话框中的类型', - 'uncheck to use the input\ndialog in short form': - '取消选择 输入窗并显示简洁对话框', - 'Virtual keyboard': - '虚拟键盘', - 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': - '取消选中 禁用虚拟键盘、可移动设备', - 'check to enable\nvirtual keyboard support\nfor mobile devices': - '检查 使用虚拟键、可移动设备', - 'Input sliders': - '输入滑块', - 'uncheck to disable\ninput sliders for\nentry fields': - '取消选中 禁用输入滑块、输入字段', - 'check to enable\ninput sliders for\nentry fields': - '检查 使用输入滑块、输入字段', - 'Clicking sound': - '点击声音', - 'uncheck to turn\nblock clicking\nsound off': - '取消选中 关闭点击程序块的声音', - 'check to turn\nblock clicking\nsound on': - '检查 关闭点击程序块的声音', - 'Animations': - '动画', - 'uncheck to disable\nIDE animations': - '取消选中禁用IDE动画', - 'Turbo mode': - 'Turbo模式', - 'check to prioritize\nscript execution': - '检查的优先执行脚本顺序', - 'uncheck to run scripts\nat normal speed': - '取消选中正常速度运行脚本', - 'check to enable\nIDE animations': - '检查启用IDE动画', - 'Thread safe scripts': - '线程安全的脚本', - 'uncheck to allow\nscript reentrance': - '取消选中 允许脚本重新载入', - 'check to disallow\nscript reentrance': - '检查 不允许脚本重新载入', - 'Prefer smooth animations': - '不流畅的动画', - 'uncheck for greater speed\nat variable frame rates': - '取消选中在可变帧频更快的速度', - 'check for smooth, predictable\nanimations across computers': - '检查是否平滑,可预见的多台电脑动画', - - // inputs - 'with inputs': - '参数', - 'input names:': - '参数名:', - 'Input Names:': - '参数名:', - 'input list:': - '输入列表:', - - // context menus: - 'help': - '帮助', - - // blocks: - 'help...': - '帮助...', - 'relabel...': - '重新标记...', - 'duplicate': - '复制', - 'make a copy\nand pick it up': - '创建一个副本并抓起', - 'only duplicate this block': - '只复制此块', - 'delete': - '删除', - 'script pic...': - '将脚本存为图像...', - 'open a new window\nwith a picture of this script': - '新浏览窗口中打开脚本的图片', - 'ringify': - '环', - 'unringify': - '删除环', - - // custom blocks: - 'delete block definition...': - '删除自定义程序块', - 'edit...': - '编辑...', - - // sprites: - 'edit': - '编辑', - 'export...': - '导出...', - - 'show all': - '显示所有', - 'pic...': - '导出图像...', - 'open a new window\nwith a picture of the stage': - '打开一张图片舞台的新窗口,', - // scripting area - 'clean up': - '清除', - 'arrange scripts\nvertically': - '整理脚本,垂直排列', - 'add comment': - '添加注释', - 'make a block...': - '创建程序块...', - - // costumes - 'rename': - '重命名', - 'export': - '导出', - 'rename costume': - '重命名造型', - - // sounds - 'Play sound': - '播放声音', - 'Stop sound': - '停止声音', - 'Stop': - '停止', - 'Play': - '播放', - 'rename sound': - '重命名声音', - - // dialogs - // buttons - 'OK': - '确定', - 'Cancel': - '取消', - 'Yes': - '是', - 'No': - '否', - - // help - 'Help': - '帮助', - // zoom blocks - 'Zoom blocks': - '放大程序块', - 'build': - '建立', - 'your own': - '你自己', - 'blocks': - '程序块', - 'normal (1x)': - '标准 (1x)', - 'demo (1.2x)': - '演示 (1.2x)', - 'presentation (1.4x)': - '演示文稿 (1.4x)', - 'big (2x)': - '大(2x)', - 'huge (4x)': - '超大型 (4x)', - 'giant (8x)': - '巨人型 (8x)', - 'monstrous (10x)': - '无敌型 (10x)', - - 'Untitled': - '无标题', - 'Open Project': - '打开项目', - 'Open': - '打开', - '(empty)': - '(空)', - 'Saved!': - '已保存!', - 'Delete Project': - '删除项目', - 'Are you sure you want to delete': - '你确定要删除吗?', - 'rename...': - '重命名...', - // costume editor - 'Costume Editor': - '造型编辑器', - 'click or drag crosshairs to move the rotation center': - '点击或拖动十字准线,设置旋转中心', - - // project notes - 'Project Notes': - '项目注释', - - // new project - 'New Project': - '新建项目', - 'Replace the current project with a new one?': - '你要取消当前编辑的项目,重新建立项目吗?', - - // open project - 'Open Projekt': - '打开项目', - - // save project - 'Save Project As...': - '项目另存为...', - - // export blocks - 'Export blocks': - '导出程序块', - 'Import blocks': - '导入程序块', - 'this project doesn\'t have any\ncustom global blocks yet': - '这个项目没有包含全局性的自定义程序块', - 'select': - '选择', - 'all': - '全部', - 'none': - '无', - - // variable dialog - 'for all sprites': - '对所有的角色', - 'for this sprite only': - '只对这个角色', - - // block dialog - 'Change block': - '修改程序块', - 'Command': - '命令', - 'Reporter': - '记录', - 'Predicate': - '谓语', - - // block editor - 'Block Editor': - '程序块编辑器', - 'Apply': - '应用', - - // block deletion dialog - 'Delete Custom Block': - '删除自定义程序块', - 'block deletion dialog text': - '你确定要删除自定义程序块及所有实例吗?', - - // input dialog - 'Create input name': - '创建参数名', - 'Edit input name': - '编辑参数名', - 'Edit label fragment': - '编辑标签片段', - 'Title text': - '标题文本', - 'Input name': - '参数名', - 'Delete': - '删除', - 'Object': - '对象', - 'Number': - '数字', - 'Text': - '文本', - 'List': - '链表', - 'Any type': - '所有类型', - 'Boolean (T/F)': - '布尔值(是/否)', - 'Command\n(inline)': - '命令(内置)', - 'Command\n(C-shape)': - '命令(C型)', - 'Any\n(unevaluated)': - '任意(未评价)', - 'Boolean\n(unevaluated)': - '布尔(评价)', - 'Single input.': - '单一参数.', - 'Default Value:': - '默认值:', - 'Multiple inputs (value is list of inputs)': - '多行输入(值为参数列表)', - 'Upvar - make internal variable visible to caller': - '上传变量 - 使内部变量对调用者可见', - - // About Snap - 'About Snap': - '关于 Snap', - 'Back...': - '返回...', - 'License...': - '许可...', - 'Modules...': - '模块...', - 'Credits...': - '光荣榜...', - 'Translators...': - '翻译者', - 'License': - '许可', - 'current module versions:': - '目前版本的模块:', - 'Contributors': - '贡献者:', - 'Translations': - '翻译者', - - // variable watchers - 'normal': - '标准', - 'large': - '大型', - 'slider': - '滑块', - 'slider min...': - '滑块的最小值...', - 'slider max...': - '滑块的最大值...', - 'import...': - '导入...', - 'Slider minimum value': - '滑块的最小值', - 'Slider maximum value': - '滑块的最大值', - - // list watchers - 'length: ': - '长度: ', - - // coments - 'add comment here...': - '在这里添加注释...', - - // drow downs - // directions - '(90) right': - '(90) 右', - '(-90) left': - '(-90) 左', - '(0) up': - '(0) 上', - '(180) right': - '(180) 右', - - // collision detection - 'mouse-pointer': - '鼠标指针', - 'edge': - '边缘', - 'pen trails': - '画笔轨迹', - - // costumes - 'Turtle': - '海龟', - - // graphical effects - 'ghost': - '鬼影', - - // keys - 'space': - '空格键', - 'up arrow': - '上移键', - 'down arrow': - '下移键', - 'right arrow': - '右移键', - 'left arrow': - '左移键', - 'a': - 'a', - 'b': - 'b', - 'c': - 'c', - 'd': - 'd', - 'e': - 'e', - 'f': - 'f', - 'g': - 'g', - 'h': - 'h', - 'i': - 'i', - 'j': - 'j', - 'k': - 'k', - 'l': - 'l', - 'm': - 'm', - 'n': - 'n', - 'o': - 'o', - 'p': - 'p', - 'q': - 'q', - 'r': - 'r', - 's': - 's', - 't': - 't', - 'u': - 'u', - 'v': - 'v', - 'w': - 'w', - 'x': - 'x', - 'y': - 'y', - 'z': - 'z', - '0': - '0', - '1': - '1', - '2': - '2', - '3': - '3', - '4': - '4', - '5': - '5', - '6': - '6', - '7': - '7', - '8': - '8', - '9': - '9', - - // messages - 'new...': - '新建...', - - // math functions - 'abs': - '绝对值', - 'sqrt': - '平方根', - 'sin': - 'sin', - 'cos': - 'cos', - 'tan': - 'tan', - 'asin': - 'asin', - 'acos': - 'acos', - 'atan': - 'atan', - 'ln': - 'ln', - 'e^': - 'e^', - - // data types - 'number': - '数字', - 'text': - '文本', - 'Boolean': - '布尔值', - 'list': - '链表', - 'command': - '命令', - 'reporter': - '记录', - 'predicate': - '谓语', - - // list indices - 'last': - '最后', - 'any': - '任意', - - // missing entries - 'Untitled': - '无标题', - 'Open Project': - '打开项目', - 'Open': - '打开', - '(empty)': - '(空)', - 'Saved!': - '已保存!', - 'Delete Project': - '删除项目', - 'Are you sure you want to delete': - '你确定要删除吗?', - 'unringify': - '删除环', - 'rename...': - '重命名为...', - '(180) down': - '(180) 下', - 'Ok': - '确定' - -}; +/* + + lang-zh.js + + Simplified Chinese translation for SNAP! + + written by 五百刀/邓江华 + + Copyright (C) 2016 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + + Note to Translators: + -------------------- + At this stage of development, Snap! can be translated to any LTR language + maintaining the current order of inputs (formal parameters in blocks). + + Translating Snap! is easy: + + + 1. Download + + Download the sources and extract them into a local folder on your + computer: + + + + Use the German translation file (named 'lang-de.js') as template for your + own translations. Start with editing the original file, because that way + you will be able to immediately check the results in your browsers while + you're working on your translation (keep the local copy of snap.html open + in your web browser, and refresh it as you progress with your + translation). + + + 2. Edit + + Edit the translation file with a regular text editor, or with your + favorite JavaScript editor. + + In the first non-commented line (the one right below this + note) replace "de" with the two-letter ISO 639-1 code for your language, + e.g. + + fr - French => SnapTranslator.dict.fr = { + it - Italian => SnapTranslator.dict.it = { + pl - Polish => SnapTranslator.dict.pl = { + pt - Portuguese => SnapTranslator.dict.pt = { + es - Spanish => SnapTranslator.dict.es = { + el - Greek => => SnapTranslator.dict.el = { + + etc. (see ) + + + 3. Translate + + Then work through the dictionary, replacing the German strings against + your translations. The dictionary is a straight-forward JavaScript ad-hoc + object, for review purposes it should be formatted as follows: + + { + 'English string': + 'Translation string', + 'last key': + } 'last value' + + and you only edit the indented value strings. Note that each key-value + pair needs to be delimited by a comma, but that there shouldn't be a comma + after the last pair (again, just overwrite the template file and you'll be + fine). + + If something doesn't work, or if you're unsure about the formalities you + should check your file with + + + + This will inform you about any missed commas etc. + + + 4. Accented characters + + Depending on which text editor and which file encoding you use you can + directly enter special characters (e.g. Umlaut, accented characters) on + your keyboard. However, I've noticed that some browsers may not display + special characters correctly, even if other browsers do. So it's best to + check your results in several browsers. If you want to be on the safe + side, it's even better to escape these characters using Unicode. + + see: + + + 5. Block specs: + + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + + + 6. Submit + + When you're done, rename the edited file by replacing the "de" part of the + filename with the two-letter ISO 639-1 code for your language, e.g. + + fr - French => lang-fr.js + it - Italian => lang-it.js + pl - Polish => lang-pl.js + pt - Portuguese => lang-pt.js + es - Spanish => lang-es.js + el - Greek => => lang-el.js + + and send it to me for inclusion in the official Snap! distribution. + Once your translation has been included, Your name will the shown in the + "Translators" tab in the "About Snap!" dialog box, and you will be able to + directly launch a translated version of Snap! in your browser by appending + + lang:xx + + to the URL, xx representing your translations two-letter code. + + + 7. Known issues + + In some browsers accents or ornaments located in typographic ascenders + above the cap height are currently (partially) cut-off. + + Enjoy! + -Jens +*/ + +/*global SnapTranslator*/ + +SnapTranslator.dict.zh = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + '简体中文', + 'language_translator': + '五百刀/邓江华', + 'translator_e-mail': + 'ubertao@qq.com/djh@rhjxx.cn', + 'last_changed': + '2016-5-6', + + // GUI + // control bar: + 'untitled': + '无名项目', + 'development mode': + '开发模式', + + // categories: + 'Motion': + '运动', + 'Looks': + '外观', + 'Sound': + '声音', + 'Pen': + '画笔', + 'Control': + '控制', + 'Sensing': + '探测', + 'Operators': + '运算', + 'Variables': + '变量', + 'Lists': + '列表', + 'Other': + '其他', + + // editor: + 'draggable': + '允许拖动', + + // tabs: + 'Scripts': + '脚本', + 'Costumes': + '造型', + 'Sounds': + '声音', + + // names: + 'Sprite': + '角色', + 'Stage': + '舞台', + + // rotation styles: + 'don\'t rotate': + '不能旋转', + 'can rotate': + '可以旋转', + 'only face left/right': + '只能水平翻转', + + // new sprite button: + 'add a new sprite': + '添加角色', + 'add a new Turtle sprite': + '添加一个海龟角色', + + // tab help + 'costumes tab help': + '把网页或电脑中的图片拖到这里,可以添加一个造型', + + 'import a sound from your computer\nby dragging it into here': + '把电脑中的声音文件拖到这里,可以添加一个声音', + + // primitive blocks: + + /* + Attention Translators: + ---------------------- + At this time your translation of block specs will only work + correctly, if the order of formal parameters and their types + are unchanged. Placeholders for inputs (formal parameters) are + indicated by a preceding % prefix and followed by a type + abbreviation. + + For example: + + 'say %s for %n secs' + + can currently not be changed into + + 'say %n secs long %s' + + and still work as intended. + + Similarly + + 'point towards %dst' + + cannot be changed into + + 'point towards %cst' + + without breaking its functionality. + */ + + // motion: + 'Stage selected:\nno motion primitives': + '选中了舞台:\n舞台不能使用运动积木', + + + 'move %n steps': + '移动 %n 歩', + 'turn %clockwise %n degrees': + '旋转 %clockwise %n 度', + 'turn %counterclockwise %n degrees': + '旋转 %counterclockwise %n 度', + 'point in direction %dir': + '面向 %dir 度', + 'point towards %dst': + '面向 %dst', + 'go to x: %n y: %n': + '移到 x: %n y: %n', + 'go to %dst': + '移到 %dst', + 'glide %n secs to x: %n y: %n': + '在 %n 秒钟内滑到 x: %n y: %n', + 'change x by %n': + '把x坐标增加 %n', + 'set x to %n': + '把x坐标设定为 %n', + 'change y by %n': + '把y坐标增加 %n', + 'set y to %n': + '把y坐标设定为 %n', + 'if on edge, bounce': + '碰到边缘就反弹', + 'x position': + 'x坐标', + 'y position': + 'y坐标', + 'direction': + '方向', + + // looks: + 'switch to costume %cst': + '换成 %cst 造型', + 'next costume': + '下一个造型', + 'costume #': + '造型编号', + 'costume name': + '造型名称', + 'say %s for %n secs': + '说 %s %n 秒', + 'say %s': + '说 %s', + 'think %s for %n secs': + '思考 %s %n 秒', + 'think %s': + '思考 %s', + 'Hello!': + '你好!', + 'Hmm...': + '嗯……', + 'change %eff effect by %n': + '把 %eff 效果增加 %n', + 'set %eff effect to %n': + '把 %eff 效果设定为 %n', + 'clear graphic effects': + '清除所有图形效果', + 'change size by %n': + '把角色的大小增加 %n', + 'set size to %n %': + '把角色的大小设定为 %n %', + 'size': + '大小', + 'show': + '显示', + 'hide': + '隐藏', + 'go to front': + '移至最上层', + 'go back %n layers': + '下移 %n 层', + + // development mode + 'development mode \ndebugging primitives:': + '开发模式\n调试积木:', + 'wardrobe': + '全部造型', + 'console log %mult%s': + '控制台日志 %mult%s', + 'alert %mult%s': + '警告: %mult%s', + 'save %imgsource as costume named %s': + '把 %imgsource 保存为造型,命名为 %s', + 'stage image': + '舞台图片', + 'jukebox': + '全部声音', + 'processes': + '进程数量', + '%txtfun of %s': + '%txtfun %s', + 'map %repRing over %l': + 'map %repRing over %l', + 'for %upvar in %l %cl': + 'for %upvar in %l %cl', + 'each item': + '每一项', + 'show table %l': + '显示表格 %l', + 'entering development mode.\n\nerror catching is turned off,\nuse the browser\'s web console\nto see error messages.': + '进入开发模式。\n\n错误捕捉已关闭,请使用\n浏览器控制台查看错误消息。', + 'entering user mode': + '进入用户模式', + + // development mode: morph context menu + 'user features...': + '用户菜单…', + 'color...': + '颜色…', + 'choose another color \nfor this morph': + '指定morph的颜色', + 'transparency...': + '透明度…', + 'set this morph\'s\nalpha value': + '设置morph的alpha通道值', + 'resize...': + '改变大小…', + 'show a handle\nwhich can be dragged\nto change this morph\'s extent': + '显示一个把手,\n拖动可改变morph大小', + 'pick up': + '抓起', + 'disattach and put \ninto the hand': + '断开连接拿起morph', + 'attach...': + '连接到…', + 'stick this morph\nto another one': + '连接到另外一个morph', + 'move...': + '移动…', + 'show a handle\nwhich can be dragged\nto move this morph': + '显示一个把手,\n拖动可移动这个morph', + 'inspect...': + '查看…', + 'open a window\non all properties': + '打开查看器窗口\n显示所有属性', + 'open a new window\nwith a picture of this morph': // pick up + '打开新窗口\n展示这个morph的图片', + 'lock': + '锁定', + 'make this morph\nunmovable': + '固定morph不可移动', + 'unlock': + '解锁', + 'make this morph\nmovable': + '可以移动morph', + 'World...': + 'World…', // don't translate "World" + 'show the\nWorld\'s menu': + '显示World菜单', + 'font size...': + '字体大小…', + 'set this String\'s\nfont point size': + '设置字符串的字体点数', + 'align left': + '靠左', + 'align right': + '靠右', + 'align center': + '居中', + 'serif': + '衬线字体', + 'sans-serif': + '无衬线字体', + 'italic': + '斜体', + 'normal style': + '直体', + 'bold': + '粗体', + 'normal weight': + '正常粗细', + 'show blanks': + '显示空格', + 'hide blanks': + '隐藏空格', + 'hide characters': + '隐藏字符', + 'show characters': + '显示字符', + 'delete block': + '删除积木', + 'spec...': + '描述…', + 'spec': + '描述', + 'border width...': + '边框粗细…', + 'set the border\'s\nline size': + '设置边框线条尺寸', + 'border color...': + '边框颜色…', + 'set the border\'s\nline color': + '设置边框线条颜色', + 'corner size...': + '圆角大小…', + 'set the corner\'s\nradius': + '设置圆角半径', + 'alpha\nvalue:': + 'alpha通道值:', + 'color:': + '颜色:', + + // development mode: morph inspector + 'show...': + '显示…', + 'close': + '关闭', + 'attributes': + '属性', + 'methods': + '方法', + 'mark own': + '标记自有属性', + 'un-mark own': + '取消自有属性标记', + 'save': + '保存', + 'add property...': + '增加属性…', + 'remove...': + '删除…', + 'new property name:': + '新属性名:', + 'property name:': + '属性名:', + 'in new inspector...': + '新窗口…', + 'here...': + '此窗口…', + + // development mode: WorldMorph context menu + 'demo...': + '演示…', + 'sample morphs': + '各种morph示例', + 'hide all...': + '全部隐藏…', + 'show all...': + '全部显示…', + 'move all inside...': + '全部围住…', + 'keep all submorphs\nwithin and visible': + '围入所有子morph\n全部可见', + 'auto line wrap on...': + '自动折行', + 'enable automatic\nline wrapping': + '打开自动折行功能', + 'auto line wrap off...': + '不自动折行', + 'turn automatic\nline wrapping\noff': + '关闭自动折行功能', + 'screenshot...': + '屏幕截图…', + 'restore display': + '恢复显示', + 'redraw the\nscreen once': + '重画屏幕', + 'fill page...': + '填满页面…', + 'let the World automatically\nadjust to browser resizings': + '让Wolrd随浏览器改变大小', + 'sharp shadows...': + '锐利的阴影…', + 'sharp drop shadows\nuse for old browsers': + '对老旧浏览器\n使用锐利的阴影', + 'blurred shadows...': + '模糊的阴影…', + 'blurry shades,\n use for new browsers': + '对新浏览器\n使用模糊的阴影', + 'choose the World\'s\nbackground color': + '选择World的背景颜色', + 'touch screen settings': + '适合触摸屏', + 'bigger menu fonts\nand sliders': + '使用大号菜单字体和游标', + 'standard settings': + '适合普通屏幕', + 'smaller menu fonts\nand sliders': + '使用小号菜单字体和游标', + 'user mode...': + '用户模式…', + 'disable developers\'\ncontext menus': + '禁用开发者快捷菜单', + 'about morphic.js...': + '关于morphic.js…', + 'development mode...': + '开发者模式…', + + // development mode: World's demo context menu + 'make a morph': + '创建morph', + 'rectangle': + '矩形', + 'box': + '圆角框', + 'circle box': + '圆头框', + 'frame': + '框架', + 'scroll frame': + '可滚动框架', + 'handle': + '把手', + 'string': + '字符串', + 'speech bubble': + '对话气泡', + 'gray scale palette': + '灰度调色板', + 'color palette': + '彩色调色板', + 'color picker': + '颜色选择器', + 'sensor demo': + '探测器演示', + 'animation demo': + '动画演示', + 'pen': + '画笔', + + // sound: + 'play sound %snd': + '播放声音 %snd', + 'play sound %snd until done': + '播放声音 %snd 直到播放完毕', + 'stop all sounds': + '停止所有声音', + 'rest for %n beats': + '停止 %n 拍', + 'play note %n for %n beats': + '弹奏 %n %n 拍', + 'change tempo by %n': + '把节奏加快 %n', + 'set tempo to %n bpm': + '把节奏设定为 %n', + 'tempo': + '节奏', + + // pen: + 'clear': + '清空', + 'pen down': + '落笔', + 'pen up': + '抬笔', + 'set pen color to %clr': + '把画笔的颜色设定为 %clr', + 'change pen color by %n': + '把画笔的颜色值增加 %n', + 'set pen color to %n': + '把画笔的颜色值设定为 %n', + 'change pen shade by %n': + '把画笔的色度增加 %n', + 'set pen shade to %n': + '把画笔的色度设定为 %n', + 'change pen size by %n': + '把画笔的大小增加 %n', + 'set pen size to %n': + '把画笔的大小设定为 %n', + 'stamp': + '图章', + 'fill': + '填充', + 'tip': + '尖端', + 'middle': + '中间', + + // control: + 'when %greenflag clicked': + '当 %greenflag 被点击', + 'when %keyHat key pressed': + '当按下 %keyHat 键', + 'when I am %interaction': + '当 %interaction 我', + 'clicked': + '点击', + 'pressed': + '按下', + 'dropped': + '放下', + 'mouse-entered': + '鼠标碰到', + 'mouse-departed': + '鼠标离开', + 'when %b': + '当 %b', + 'when I receive %msgHat': + '当接收到 %msgHat', + 'broadcast %msg': + '广播 %msg', + 'broadcast %msg and wait': + '广播 %msg 并等待', + 'Message name': + '消息名称', + 'message': + '消息', + 'any message': + '任何消息', + 'wait %n secs': + '等待 %n 秒', + 'wait until %b': + '直到 %b 前都等待', + 'forever %c': + '重复执行 %c', + 'repeat %n %c': + '重复执行 %n %c', + 'repeat until %b %c': + '重复执行直到 %b %c', + 'if %b %c': + '如果 %b %c', + 'if %b %c else %c': + '如果 %b %c 否则 %c', + 'report %s': + '报告 %s', + 'stop %stopChoices': + '停止 %stopChoices', + 'all': + '全部', + 'this script': + '这个脚本', + 'this block': + '这块积木', + 'stop %stopOthersChoices': + '停止 %stopOthersChoices', + 'all but this script': + '所有其他脚本', + 'other scripts in sprite': + '这个角色的其他脚本', + 'pause all %pause': + '暂停所有的 %pause', + 'run %cmdRing %inputs': + '运行 %cmdRing %inputs', + 'launch %cmdRing %inputs': + '启动 %cmdRing %inputs', + 'call %repRing %inputs': + '调用 %repRing %inputs', + 'run %cmdRing w/continuation': + '持续运行 %cmdRing', + 'call %cmdRing w/continuation': + '持续调用 %cmdRing', + 'warp %c': + '一步完成 %c', + 'when I start as a clone': + '当我被克隆', + 'create a clone of %cln': + '克隆一个 %cln', + 'myself': + '自己', + 'delete this clone': + '删除这个克隆', + + // sensing: + 'touching %col ?': + '碰到 %col ?', + 'touching %clr ?': + '碰到颜色 %clr ?', + 'color %clr is touching %clr ?': + '颜色 %clr 碰到颜色 %clr ?', + 'ask %s and wait': + '询问 %s 并等待', + 'what\'s your name?': + '你的名字?', + 'answer': + '回答', + 'mouse x': + '鼠标的x坐标', + 'mouse y': + '鼠标的y坐标', + 'mouse down?': + '按下了鼠标?', + 'key %key pressed?': + '按下了 %key 键?', + 'distance to %dst': + '到 %dst 的距离', + 'reset timer': + '计时器归零', + 'timer': + '计时器', + '%att of %spr': + '取 %att 于 %spr', + 'my %get': + '我的 %get', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + '启动了加速?', + 'set turbo mode to %b': + '设置加速开关为 %b', + + 'filtered for %clr': + '用 %clr 过滤造型', + 'stack size': + '堆栈大小', + 'frames': + '栈帧层数', + + 'current %dates': + '当前的 %dates', + 'year': + '年份', + 'month': + '月份', + 'date': + '日期', + 'hour': + '钟点', + 'minute': + '分钟', + 'second': + '秒钟', + 'time in milliseconds': + '时间戳', + 'day of week': + '星期几', + + // operators: + '%n mod %n': + '%n 除以 %n 的余数', + 'round %n': + '把 %n 四舍五入', + '%fun of %n': + '%fun %n', + 'pick random %n to %n': + '在 %n 到 %n 间随机选一个数', + '%b and %b': + '%b 且 %b', + '%b or %b': + '%b 或 %b', + 'not %b': + '%b 不成立', + 'true': + '真', + 'false': + '假', + 'join %words': + '把 %words 连起来', + 'split %s by %delim': + '把 %s 按 %delim 分开', + 'hello': + '你好', + 'world': + '世界', + 'letter %n of %s': + '第 %n 个字符在文字 %s 中', + 'length of %s': + '%s 的长度', + 'unicode of %s': + '字符 %s 的Unicode码', + 'unicode %n as letter': + 'Unicode码为 %n 的字符', + 'is %s a %typ ?': + '%s 的类型是 %typ ?', + 'is %s identical to %s ?': + '%s 与 %s 相同?', + + 'type of %s': + '%s 的类型', + + // variables: + 'Make a variable': + '新建一个变量', + 'Variable name': + '变量名', + 'Script variable name': + '脚本变量名', + 'Delete a variable': + '删除变量', + + 'set %var to %s': + '把 %var 设定为 %s', + 'change %var by %n': + '把 %var 增加 %n', + 'show variable %var': + '显示变量 %var', + 'hide variable %var': + '隐藏变量 %var', + 'script variables %scriptVars': + '脚本变量 %scriptVars', + + // lists: + 'list %exp': + '列表 %exp', + '%s in front of %l': + '%s 放在 %l 前面', + 'item %idx of %l': + '第 %idx 项 %l', + 'all but first of %l': + '%l 第一项以外', + 'length of %l': + '%l 的长度', + '%l contains %s': + '%l 含有 %s', + 'thing': + '东西', + 'add %s to %l': + '把 %s 放到 %l 后面', + 'delete %ida of %l': + '删除第 %ida 项 %l', + 'insert %s at %idx of %l': + '把 %s 插入到第 %idx 项 %l', + 'replace item %idx of %l with %s': + '把第 %idx 项 %l 替换为 %s', + + // other + 'Make a block': + '制作积木', + + // menus + // snap menu + 'About...': + '关于Snap!…', + 'Reference manual': + '参考手册', + 'Snap! website': + '官方网站', + 'Download source': + '下载源代码', + 'Switch back to user mode': + '回到用户模式', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + '禁用Morphic快捷菜单\n显示正常的用户界面', + 'Switch to dev mode': + '切换到开发者模式', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + '启用Morphic快捷菜单和查看器\n用户界面不友好', + + // project menu + 'Project notes...': + '项目备注…', + 'New': + '新建', + 'Open...': + '打开…', + 'Save': + '保存', + 'Save to disk': + '存盘', + 'store this project\nin the downloads folder\n(in supporting browsers)': + '保存到下载文件夹\n(部分浏览器支持)', + + 'Save As...': + '另存为…', + 'Import...': + '导入…', + 'file menu import hint': + '加载导出的项目、积木库、造型或声音', + + + 'Export project as plain text...': + '用文字格式导出项目…', + 'Export project...': + '导出项目…', + '(in a new window)': + '(打开新窗口)', + 'save project data as XML\nto your downloads folder': + '把项目数据以XML格式\n保存到下载文件夹', + 'show project data as XML\nin a new browser window': + '打开新窗口,展示项目的XML数据', + 'Export Project As...': + '把项目导出到…', + 'Exported!': + '导出好了!', + 'Export blocks...': + '导出积木…', + 'show global custom block definitions as XML\nin a new browser window': + '打开新窗口,以XML格式展示全局自制积木', + 'Unused blocks...': + '没用到的积木…', + 'find unused global custom blocks\nand remove their definitions': + '查找没用到的全局自制积木,\n删除它们的定义', + 'Remove unused blocks': + '删除没用到的积木', + 'there are currently no unused\nglobal custom blocks in this project': + '这个项目里目前没有\n没用到的全局自制积木', + 'unused block(s) removed': + '删掉了没用到的积木', + 'Export summary...': + '导出项目总结…', + 'open a new browser browser window\n with a summary of this project': + '打开新窗口,展示这个项目的总结', + 'Export summary with drop-shadows...': + '导出项目总结(带阴影)…', + 'open a new browser browser window\nwith a summary of this project\nwith drop-shadows on all pictures.\nnot supported by all browsers': + '打开新窗口,使用带有阴影的图形\n展示这个项目的总结\n(只有部分浏览器可以)', + + 'Contents': + '内容', + 'Kind of': + '类型:', + 'Part of': + '属于:', + 'Parts': + '组件', + 'Blocks': + '积木', + 'For all Sprites': + '对所有角色', + 'Import tools': + '导入工具包', + 'load the official library of\npowerful blocks': + '载入强大的官方积木库', + 'Libraries...': + '积木库…', + 'Select categories of additional blocks to add to this project.': + '挑选更多积木,添加到项目中。', + 'Select a costume from the media library': + '从媒体库中挑选一个造型', + 'Select a sound from the media library': + '从媒体库中挑选一个声音', + 'Import': + '导入', + 'Import library': + '导入积木库', + 'Backgrounds': + '背景', + + // cloud menu + 'Login...': + '登录…', + 'Signup...': + '注册…', + 'Reset Password...': + '重设密码…', + 'Logout': + '登出', + 'Change Password...': + '修改密码…', + 'Change Password': + '修改密码', + 'password has been changed.': + '密码改好了。', + 'Export all scripts as pic...': + '把所有脚本导出为图片…', + 'show a picture of all scripts\nand block definitions': + '把所有脚本和积木设计图展示成一张图片', + 'url...': + 'URL…', + 'Service:': + '服务:', + 'Reset Password': + '重设密码', + 'An e-mail with a link to\nreset your password\nhas been sent to the address provided': + '重设密码的网址已发往你的电子邮件地址', + 'Signup': + '注册', + 'export project media only...': + '仅导出项目中的媒体文件…', + 'export project without media...': + '导出项目,不含媒体…', + 'export project as cloud data...': + '把项目以云端数据格式导出…', + 'open shared project from cloud...': + '打开共享在云端的项目…', + + // Sign up dialog + 'Sign up': + '注册', + 'User name:': + '用户名:', + 'Birth date:': + '出生日期:', + 'year:': + '年:', + 'E-mail address:': + '电子邮件:', + 'E-mail address of parent or guardian:': + '家长电子邮件:', + 'Terms of Service...': + '服务条款…', + 'Privacy...': + '隐私政策…', + 'I have read and agree\nto the Terms of Service': + '我已阅读并同意《服务条款》', + 'January': + '一月', + 'February': + '二月', + 'March': + '三月', + 'April': + '四月', + 'May': + '五月', + 'June': + '六月', + 'July': + '七月', + 'August': + '八月', + 'September': + '九月', + 'October': + '十月', + 'November': + '十一月', + 'December': + '十二月', + 'or before': + '或更早', + 'please fill out\nthis field': + '请填写这里', + 'User name must be four\ncharacters or longer': + '用户名不能少于4个字符', + 'please provide a valid\nemail address': + '请填写有效的电子邮件地址', + 'password must be six\ncharacters or longer': + '密码不能少于6个字符', + 'passwords do\nnot match': + '两次填写的密码不一致', + 'please agree to\nthe TOS': + '请同意《服务条款》', + 'Sign in': + '登录', + 'Password:': + '密码:', + 'stay signed in on this computer\nuntil logging out': + '保持登录,直到登出', + 'Reset password': + '重设密码', + 'could not connect to:': + '连不上这个网站:', + 'now connected.': + '已经登录到云端。', + 'disconnected.': + '已经从云端登出。', + 'Old password:': + '老密码:', + 'New password:': + '新密码:', + 'Repeat new password:': + '重复一遍新密码:', + + // settings menu + 'Language...': + '语言…', + 'Zoom blocks...': + '放大积木…', + 'Stage size...': + '舞台大小…', + 'Stage size': + '舞台大小', + 'Stage width': + '舞台宽度', + 'Stage height': + '舞台高度', + 'Default': + '默认', + 'Blurred shadows': + '半透明阴影', + 'uncheck to use solid drop\nshadows and highlights': + '关:使用不透明的阴影和加亮', + 'check to use blurred drop\nshadows and highlights': + '关:使用透明的阴影和加亮', + 'Zebra coloring': + '积木颜色相间', + 'check to enable alternating\ncolors for nested blocks': + '开:使用深浅相间的颜色\n显示嵌套的同类积木', + 'uncheck to disable alternating\ncolors for nested block': + '关:使用同样的颜色\n显示嵌套的同类积木', + 'Dynamic input labels': + '动态输入标记', + 'uncheck to disable dynamic\nlabels for variadic inputs': + '关:可变输入项不使用动态标记', + 'check to enable dynamic\nlabels for variadic inputs': + '开:可变输入项使用动态标记', + 'Prefer empty slot drops': + '只放空白项', + 'settings menu prefer empty slots hint': + '开:“报告积木”优先\n放在没有积木的输入项上', + 'uncheck to allow dropped\nreporters to kick out others': + '关:“报告积木”可以\n踢走输入项上已有的积木', + 'Long form input dialog': + '输入类型说明', + 'check to always show slot\ntypes in the input dialog': + '开:在输入项对话框里显示类型说明', + 'uncheck to use the input\ndialog in short form': + '关:显示简洁的输入项对话框', + 'Plain prototype labels': + '简洁的设计图', + 'uncheck to always show (+) symbols\nin block prototype labels': + '关:在积木设计图上显示(+)号', + 'check to hide (+) symbols\nin block prototype labels': + '开:不在积木设计图上显示(+)号', + 'Virtual keyboard': + '虚拟键盘', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + '关:不使用移动设备的虚拟键盘', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + '开:使用移动设备的虚拟键盘', + 'Input sliders': + '使用游标', + 'uncheck to disable\ninput sliders for\nentry fields': + '关:不使用游标修改输入字段', + 'check to enable\ninput sliders for\nentry fields': + '开:使用游标修改输入字段', + 'Execute on slider change': + '游标改变时运行脚本', + 'uncheck to supress\nrunning scripts\nwhen moving the slider': + '关:滑动游标时暂停运行脚本', + 'check to run\nthe edited script\nwhen moving the slider': + '开:滑动游标时运行改变的脚本', + 'Clicking sound': + '点击音效', + 'uncheck to turn\nblock clicking\nsound off': + '关:点击积木时不发出声音', + 'check to turn\nblock clicking\nsound on': + '开:点击积木发出声音', + 'Animations': + '动画', + 'uncheck to disable\nIDE animations': + '关:不显示编辑器动画效果', + 'Turbo mode': + '加速模式', + 'check to prioritize\nscript execution': + '开:加速脚本运行', + 'uncheck to run scripts\nat normal speed': + '关:正常速度运行脚本', + 'check to enable\nIDE animations': + '开:显示编辑器动画效果', + 'Flat design': + '扁平外观', + 'check for alternative\nGUI design': + '开:使用扁平风格的用户界面', + 'uncheck for default\nGUI design': + '关:使用默认的用户界面', + 'Keyboard Editing': + '键盘编辑', + 'uncheck to disable\nkeyboard editing support': + '关:不使用键盘编辑', + 'check to enable\nkeyboard editing support': + '开:使用键盘编辑', + 'Table support': + '使用表格功能', + 'uncheck to disable\nmulti-column list views': + '关:不使用多栏(如2维)列表', + 'check for multi-column\nlist view support': + '开:使用多栏(如2维)列表', + 'Table lines': + '表格线', + 'uncheck for less contrast\nmulti-column list views': + '关:浅色表格线', + 'check for higher contrast\ntable views': + '开:深色表格线', + 'Thread safe scripts': + '线程安全的脚本', + 'uncheck to allow\nscript reentrance': + '关:允许脚本重入', + 'check to disallow\nscript reentrance': + '开:不允许脚本重入', + 'Prefer smooth animations': + '动画尽可能平滑', + 'uncheck for greater speed\nat variable frame rates': + '关:改变帧率保证播放速度\n(牺牲平滑程度)', + 'check for smooth, predictable\nanimations across computers': + '开:平滑地显示动画\n(牺牲播放速度)', + 'Flat line ends': + '平头线条', + 'check for flat ends of lines': + '开:线条的端点是平的', + 'uncheck for round ends of lines': + '关:线条的端点是圆的', + 'Codification support': + '可转换成代码', + 'uncheck to disable\nblock to text mapping features': + '不选:关闭积木转文字的功能', + 'check for block\nto text mapping features': + '开:打开积木转文字的功能', + 'Inheritance support': + '母子角色(继承)', + 'uncheck to disable\nsprite inheritance features': + '关:角色不可以继承', + 'check for sprite\ninheritance features': + '开:角色可以继承', + 'Sprite Nesting': + '角色组合', + 'check to enable\nsprite composition': + '开:允许角色组合', + 'uncheck to disable\nsprite composition': + '关:不允许角色组合', + 'First-Class Sprites': + '高等角色', + 'uncheck to disable support\nfor first-class sprites': + '关:不使用高等角色', + 'check to enable support\n for first-class sprite': + '开:使用高等角色', + 'Dragging threshold...': + '拖放最小距离…', + 'specify the distance the hand has to move\nbefore it picks up an object': + '要抓起东西\n鼠标需要移动的最小距离', + 'Cache Inputs': + '缓存输入数据', + 'uncheck to stop caching\ninputs (for debugging the evaluator)': + '关:不缓存输入数据\n(以便调试求值过程)', + 'check to cache inputs\nboosts recursion': + '开:缓存输入数据\n递归速度更快', + 'Project URLs': + '项目网址', + 'check to enable\nproject data in URLs': + '开:网址携带项目数据', + 'uncheck to disable\nproject data in URLs': + '关:网址不携带项目数据', + 'Rasterize SVGs': + 'SVG点阵化', + 'uncheck for smooth\nscaling of vector costumes': + '关:矢量造型平滑缩放', + 'check to rasterize\nSVGs on import': + '开:导入SVG时把它点阵化', + 'Persist linked sublist IDs': + '保存子列表ID', + 'uncheck to disable\nsaving linked sublist identities': + '关:不保存子列表的ID', + 'check to enable\nsaving linked sublist identities': + '开:保存子列表的ID', + // inputs + 'with inputs': + '输入项', + 'input names:': + '输入项:', + 'Input Names:': + '输入项:', + 'input list:': + '输入列表:', + 'options...': + '选项…', + 'read-only': + '只读', + 'Input Slot Options': + '输入项选项', + 'Enter one option per line.Optionally use "=" as key/value delimiter\ne.g.\n the answer=42': + '每行一个选项。可使用“=”作为键和值的分隔符。例如:\n    答案=42', + // context menus: + 'help': + '帮助', + + // palette: + 'hide primitives': + '隐藏原始积木', + 'show primitives': + '显示原始积木', + 'header mapping...': + '对应的定义代码…', + 'code mapping...': + '对应的调用代码…', + 'code list mapping...': + '列表对应的代码…', + 'code item mapping...': + '列表项对应的代码…', + 'code delimiter mapping...': + '列表项分隔符对应的代码…', + + // codification dialog: + 'Header mapping': + '对应的定义代码', + 'Code mapping': + '对应的调用代码', + 'Code mapping - ': + '对应的代码 -', + 'Code mapping - String <#1>': + '对应的代码 - 字符串 <#1>', + 'Code mapping - list contents <#1>': + '对应的代码 - 列表内容 <#1>', + 'Code mapping - list item <#1>': + '对应的代码 - 列表项', + 'Code mapping - list item delimiter': + '对应的代码 - 列表项分隔符', + 'Enter code that corresponds to the block\'s definition. Use the formal parameter\nnames as shown and to reference the definition body\'s generated text code.': + '输入积木对应的定义/实现部分代码。\n使用上图所示的形参名,使用来引用积木的定义。', + 'Enter code that corresponds to the block\'s definition. Choose your own\nformal parameter names (ignoring the ones shown).': + '输入积木对应的定义/实现部分代码。\n使用自己选择的形参名字(忽略上图所示的形参名)。', + 'Enter code that corresponds to the block\'s operation (usually a single\nfunction invocation). Use <#n> to reference actual arguments as shown.': + '输入积木对应的调用代码。\n用<#n>来引用上图所示的实参。', + + // codification blocks + 'map %cmdRing to %codeKind %code': + '把 %cmdRing 转换成 %codeKind %code', + 'map String to code %code': + '把字符串转成代码 %code', + 'map %codeListPart of %codeListKind to code %code': + '把 %codeListKind 的 %codeListPart 转成代码 %code', + 'code of %cmdRing': + '%cmdRing 的代码', + 'item': + '项', + 'delimiter': + '分隔符', + 'collection': + '集合', + 'variables': + '变量', + 'parameters': + '参数', + 'code': + '调用代码', + 'header': + '定义代码', + 'code string mapping...': + '字符串对应的代码…', + // sprites: + 'parent:': + '母角色:', + 'parent...': + '母角色…', + 'current parent': + '母角色', + + // blocks: + 'help...': + '帮助…', + 'relabel...': + '更换…', + 'duplicate': + '复制', + 'make a copy\nand pick it up': + '复制并抓起这个积木', + 'only duplicate this block': + '复制单个积木', + 'delete': + '删除', + 'script pic...': + '显示脚本图片…', + 'open a new window\nwith a picture of this script': + '打开一个新窗口,\n显示这个脚本的图片', + 'script pic with result...': + '带结果的脚本图片…', + 'open a new window\nwith a picture of both\nthis script and its result': + '打开一个新窗口,\n显示这个脚本和运行结果的图片', + 'ringify': + '加上环', + 'unringify': + '去掉环', + 'transient': + '不记录', + 'uncheck to save contents\nin the project': + '关:把变量内容保存在项目里', + 'check to prevent contents\nfrom being saved': + '开:不保存变量内容', + 'find blocks...': + '找积木…', + + // custom blocks: + 'delete block definition...': + '删除积木定义…', + 'edit...': + '编辑…', + + // sprites: + 'edit': + '编辑', + 'move': + '移动', + 'detach from': + '脱离', + 'detach all parts': + '拆除所有组件', + 'export...': + '导出…', + 'paint a new sprite': + '画一个新角色', + + // stage: + 'show all': + '显示所有', + 'pic...': + '展示图片…', + 'open a new window\nwith a picture of the stage': + '打开一个新窗口,显示舞台的图片', + 'turn pen trails into new costume...': + '把笔迹变成新造型…', + 'turn all pen trails and stamps\ninto a new costume for the\ncurrently selected sprite': + '把所有笔迹和图章变成当前选中角色的一个新造型', + + // scripting area + 'clean up': + '整理', + 'arrange scripts\nvertically': + '垂直排列脚本', + 'add comment': + '添加说明', + 'undrop': + '收回积木', + 'undo the last\nblock drop\nin this pane': + '收回刚刚放下的积木', + 'scripts pic...': + '脚本图片…', + 'open a new window\nwith a picture of all scripts': + '打开一个新窗口,展示所有脚本的图片', + 'make a block...': + '制作新积木…', + + // costumes + 'rename': + '改名', + 'export': + '导出', + 'rename costume': + '给造型改名', + 'Paint a new costume': + '画一个新造型', + 'edit rotation point only...': + '修改旋转中心点…', + + // sounds + 'Play sound': + '播放声音', + 'Stop sound': + '停止声音', + 'Stop': + '停止', + 'Play': + '播放', + 'rename sound': + '给声音改名', + + // lists and tables + 'list view...': + '展示为列表…', + 'table view...': + '展示为表格…', + 'open in dialog...': + '在对话框中查看…', + 'open in another dialog...': + '在另一个对话框中查看…', + 'reset columns': + '重置列', + 'items': + '项', + 'Table view': + '查看表格', + + // dialogs + // buttons + 'OK': + '确定', + 'Ok': + '确定', + 'Cancel': + '取消', + 'Yes': + '是', + 'No': + '否', + + // help + 'Help': + '帮助', + + // zoom blocks + 'Zoom blocks': + '放大积木', + 'build': + '建立', + 'your own': + '你自己', + 'blocks': + '积木', + 'normal (1x)': + '标准 (1x)', + 'demo (1.2x)': + '演示 (1.2倍)', + 'presentation (1.4x)': + '幻灯片 (1.4x)', + 'big (2x)': + '大(2x)', + 'huge (4x)': + '超大 (4x)', + 'giant (8x)': + '巨大 (8x)', + 'monstrous (10x)': + '无敌 (10x)', + + // Project Manager + 'Untitled': + '无名项目', + 'Open Project': + '打开项目', + '(empty)': + '(空的)', + 'Saved!': + '已保存!', + 'Delete Project': + '删除项目', + 'Are you sure you want to delete': + '你确定要删除', + 'rename...': + '改名为…', + 'Open': + '打开', + 'Cloud': + '云端', + 'Snap!Cloud': + 'Snap!云端', + 'Browser': + '浏览器', + 'Examples': + '例子', + 'You are not logged in': + '你还没有登录', + 'Updating\nproject list...': + '正在更新项目列表…', + 'last changed': + '最后修改', + 'Share': + '分享', + 'Unshare': + '不分享', + 'Share Project': + '分享项目', + 'Unshare Project': + '不分享项目', + 'Are you sure you want to publish': + '确定让其他人看到项目', + 'Are you sure you want to unpublish': + '确定不让其他人看到项目', + 'sharing\nproject...': + '正在分享项目…', + 'shared.': + '项目已分享给其他人。', + 'unsharing\nproject...': + '正在取消项目分享…', + 'unshared.': + '其他人已看不到项目。', + 'Fetching project\nfrom the cloud...': + '从云端下载下项目…', + 'Opening project...': + '正在打开项目…', + 'Save Project': + '保存项目…', + 'Saving project\nto the cloud...': + '把项目保存到云端…', + 'saved.': + '项目已保存。', + + // costume editor + 'Costume Editor': + '造型编辑器', + 'click or drag crosshairs to move the rotation center': + '点击或拖动准星,设置旋转中心点', + + // project notes + 'Project Notes': + '项目备注', + + // new project + 'New Project': + '新建项目', + 'Replace the current project with a new one?': + '你要放弃正在编辑的项目,重新开始吗?', + + // save project + 'Save Project As...': + '项目另存为…', + + // export blocks + 'Export blocks': + '导出积木', + 'Import blocks': + '导入积木', + 'this project doesn\'t have any\ncustom global blocks yet': + '这个项目没有包含全局性的自制', + 'select': + '选择', + 'none': + '无', + + // variable dialog + 'for all sprites': + '给所有角色用', + 'for this sprite only': + '给这个角色用', + + // block dialog + 'Change block': + '修改积木', + 'Command': + '命令', + 'Reporter': + '报告', + 'Predicate': + '判断', + + // block editor + 'Block Editor': + '积木编辑器', + 'Apply': + '应用', + + // block deletion dialog + 'Delete Custom Block': + '删除自制积木', + 'block deletion dialog text': + '你确实要删除所有这种自制积木和它的定义吗?', + 'block variables...': + '积木变量…', + 'block variables': + '积木变量', + 'Block variable name': + '积木变量名字', + 'remove block variables...': + '删除积木变量…', + '(temporary)': + '(临时)', + + // input dialog + 'Create input name': + '创建输入项', + 'Edit input name': + '编辑输入项', + 'Edit label fragment': + '编辑标签片段', + 'Title text': + '标题文本', + 'Input name': + '输入项', + 'Delete': + '删除', + 'Object': + '对象', + 'Number': + '数字', + 'Text': + '文本', + 'List': + '列表', + 'Any type': + '任一类型', + 'Boolean (T/F)': + '布尔(真/假)', + 'Command\n(inline)': + '命令(嵌入)', + 'Command\n(C-shape)': + '命令(C型)', + 'Any\n(unevaluated)': + '任意(不计算)', + 'Boolean\n(unevaluated)': + '布尔(不计算)', + 'Single input.': + '输入单个值。', + 'Default Value:': + '默认值:', + 'Multiple inputs (value is list of inputs)': + '输入多个值(列表)', + 'Upvar - make internal variable visible to caller': + '回传变量 - 让调用者可以使用这个变量', + + // About Snap + 'About Snap': + '关于 Snap', + 'Back...': + '返回…', + 'License...': + '许可…', + 'Modules...': + '模块…', + 'Credits...': + '光荣榜…', + 'Translators...': + '翻译者…', + 'License': + '许可协议', + 'current module versions:': + '目前模块的版本:', + 'Contributors': + '贡献者:', + 'Translations': + '翻译者', + + // variable watchers + 'normal': + '标准', + 'large': + '大型', + 'slider': + '游标', + 'slider min...': + '游标最小值…', + 'slider max...': + '游标最大值…', + 'import...': + '导入…', + 'Slider minimum value': + '游标的最小值', + 'Slider maximum value': + '游标的最大值', + + // list watchers + 'length: ': + '长度:', + + // coments + 'add comment here...': + '在这里添加说明…', + 'comment pic...': + '展示图片…', + 'open a new window\nwith a picture of this comment': + '打开新窗口,展示这条说明的图片', + + // drow downs + // directions + '(90) right': + '右(90)', + '(-90) left': + '左(-90)', + '(0) up': + '上(0)', + '(180) down': + '下(180)', + + // collision detection + 'mouse-pointer': + '鼠标指针', + 'edge': + '边缘', + 'pen trails': + '画笔轨迹', + + // costumes + 'Turtle': + '海龟', + 'Empty': + '空白', + + // graphical effects + 'brightness': + '亮度', + 'ghost': + '透明', + 'negative': + '底片', + 'comic': + '漫画', + 'confetti': + '彩纸', + + // keys + 'space': + '空格', + 'up arrow': + '↑', + 'down arrow': + '↓', + 'right arrow': + '→', + 'left arrow': + '←', + 'any key': + '任意', + 'a': + 'a', + 'b': + 'b', + 'c': + 'c', + 'd': + 'd', + 'e': + 'e', + 'f': + 'f', + 'g': + 'g', + 'h': + 'h', + 'i': + 'i', + 'j': + 'j', + 'k': + 'k', + 'l': + 'l', + 'm': + 'm', + 'n': + 'n', + 'o': + 'o', + 'p': + 'p', + 'q': + 'q', + 'r': + 'r', + 's': + 's', + 't': + 't', + 'u': + 'u', + 'v': + 'v', + 'w': + 'w', + 'x': + 'x', + 'y': + 'y', + 'z': + 'z', + '0': + '0', + '1': + '1', + '2': + '2', + '3': + '3', + '4': + '4', + '5': + '5', + '6': + '6', + '7': + '7', + '8': + '8', + '9': + '9', + + // messages + 'new...': + '新建…', + + // math functions + 'abs': + '绝对值', + 'ceiling': + '向上取整', + 'floor': + '向下取整', + 'sqrt': + '平方根', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // delimiters + 'character': + '字符', + 'letter': + '字母', + 'whitespace': + '空白', + 'line': + '行', + 'tab': + '制表符', + 'cr': + '回车', + + // data types + 'number': + '数字', + 'text': + '文字', + 'Boolean': + '布尔', + 'list': + '列表', + 'command': + '命令', + 'reporter': + '记录', + 'predicate': + '判断', + 'sprite': + '角色', + + // list indices + 'last': + '最后', + 'any': + '任意', + + // attributes + 'neighbors': + '邻居', + 'self': + '本身', + 'other sprites': + '其他角色', + 'parts': + '组件', + 'anchor': + '组合后角色', + 'parent': + '母角色', + 'children': + '子角色', + 'clones': + '克隆', + 'other clones': + '其他克隆', + 'dangling?': + '是否悬垂?', + 'rotation x': + '旋转点x坐标', + 'rotation y': + '旋转点y坐标', + 'center x': + '中心点x坐标', + 'center y': + '中心店y坐标', + 'name': + '名字', + 'stage': + '舞台', + + // Paint.js + 'Paint Editor': + '画板', + 'undo': + '撤销', + 'Paintbrush tool\n(free draw)': + '画笔(鼠标作画)', + 'Stroked Rectangle\n(shift: square)': + '矩形框\n(shift: 正方形)', + 'Stroked Ellipse\n(shift: circle)': + '椭圆圈\n(shift: 圆圈)', + 'Eraser tool': + '橡皮', + 'Set the rotation center': + '设定旋转中心点', + 'Line tool\n(shift: vertical/horizontal)': + '直线\n(shift: 垂直或水平)', + 'Filled Rectangle\n(shift: square)': + '实心矩形\n(shift: 正方形)', + 'Filled Ellipse\n(shift: circle)': + '实心椭圆形\n(shift: 圆形)', + 'Fill a region': + '涂满一个区域', + 'Pipette tool\n(pick a color anywhere)': + '滴管\n(从屏幕上选一个颜色)', + 'grow': + '增大', + 'shrink': + '减小', + 'flip \u2194': + '↔ 翻', + 'flip \u2195': + '↕ 翻', + 'Brush size': + '画笔粗细', + 'Constrain proportions of shapes?\n(you can also hold shift)': + '只画正方形/圆形/垂直或水平线\n(相当于按住shift键)', + + // thread.js + 'a variable of name \'': + '这个上下文中不存在“', + '\'\ndoes not exist in this context': + '”这个变量', + 'expecting': + '此处要求填写', + 'input(s), but getting': + '个输入项,但实际得到输入项个数是' + +}; diff --git a/libraries/LIBRARIES b/libraries/LIBRARIES index 8905553b..24dcb15c 100644 --- a/libraries/LIBRARIES +++ b/libraries/LIBRARIES @@ -1,7 +1,12 @@ -iteration-composition Iteration, composition -list-utilities List utilities -stream-tools Streams (lazy lists) -variadic-reporters Variadic reporters -word-sentence Words, sentences -cases Multi-branched conditional (switch) -leap-library LEAP Motion controller +iteration-composition.xml Iteration, composition +list-utilities.xml List utilities +stream-tools.xml Streams (lazy lists) +variadic-reporters.xml Variadic reporters +httpBlocks.xml Web services access (https) +word-sentence.xml Words, sentences +cases.xml Multi-branched conditional (switch) +leap-library.xml LEAP Motion controller +setrgb.xml Set RGB or HSV pen color +penTrails.xml Save and restore pictures drawn by pen +try-catch.xml Catch errors in a script +multiline.xml Allow multi-line text input to a block diff --git a/libraries/cases.xml b/libraries/cases.xml index 8a170f7b..49d1f710 100644 --- a/libraries/cases.xml +++ b/libraries/cases.xml @@ -1 +1 @@ -
cont
catchtag
\ No newline at end of file +
test
cont
catchtag
\ No newline at end of file diff --git a/libraries/httpBlocks.xml b/libraries/httpBlocks.xml new file mode 100644 index 00000000..e4c76d53 --- /dev/null +++ b/libraries/httpBlocks.xml @@ -0,0 +1,4 @@ +
GETGET +POST +PUT +DELETEhttp://snap.berkeley.edu
Reports a three-item list containing the latitude and longitude of the user, and the precision of the measurements. Works only if the user allows snap.berkeley.edu access to location data. Some browsers also require an HTTPS connection to Snap!.
\ No newline at end of file diff --git a/libraries/iteration-composition.xml b/libraries/iteration-composition.xml index d2afe059..e588c42d 100644 --- a/libraries/iteration-composition.xml +++ b/libraries/iteration-composition.xml @@ -1 +1 @@ -Call f(f(f(...(f(x))))) n times where the three input slots are n, f, and x from left to right. The # variable can be used inside f to represent how many times f has been called.
Call f(f(f(...(f(x))))) until condition is true, where the three input slots are condition, f, and x from left to right. The # variable can be used inside f or condition to indicate how many times f has been called.
Returns the function f(g(x)) where f and g are the two inputs.
Like the built-in REPEAT UNTIL block, except that the ending condition is not tested until the script has been run the first time. So the script is run at least once.
Run the script repeatedly, as long as the given condition is true. Runs the script at least once before testing the condition.
Runs the script repeatedly, as long as the condition is true. Tests the condition before the first time the script is run. Like the built in REPEAT UNTIL except that in this block the condition must be true, not false.
Runs the script the specified number of times, like the built-in REPEAT block, but this one provides the # variable that can be used inside the script. Try REPEAT (200) MOVE (#) STEPS RIGHT 92 with the pen down.
1110
\ No newline at end of file +Call f(f(f(...(f(x))))) n times where the three input slots are n, f, and x from left to right. The # variable can be used inside f to represent how many times f has been called.
Call f(f(f(...(f(x))))) until condition is true, where the three input slots are condition, f, and x from left to right. The # variable can be used inside f or condition to indicate how many times f has been called.
Returns the function f(g(x)) where f and g are the two inputs.
Like the built-in REPEAT UNTIL block, except that the ending condition is not tested until the script has been run the first time. So the script is run at least once.
Run the script repeatedly, as long as the given condition is true. Runs the script at least once before testing the condition.
Runs the script repeatedly, as long as the condition is true. Tests the condition before the first time the script is run. Like the built in REPEAT UNTIL except that in this block the condition must be true, not false.
Runs the script the specified number of times, like the built-in REPEAT block, but this one provides the # variable that can be used inside the script. Try REPEAT (200) MOVE (#) STEPS RIGHT 92 with the pen down.
1110
Provides LOOP as a function of one input that runs the body of the LET with A set to the function input, so the body can run itself recursively. See COPY block in Variables for an example of use.
new value
copy VALUE N times reports a list containing N (identical) copies of VALUE
\ No newline at end of file diff --git a/libraries/multiline.xml b/libraries/multiline.xml new file mode 100644 index 00000000..795551fe --- /dev/null +++ b/libraries/multiline.xml @@ -0,0 +1 @@ +
diff --git a/libraries/penTrails.xml b/libraries/penTrails.xml new file mode 100644 index 00000000..50e7bbb3 --- /dev/null +++ b/libraries/penTrails.xml @@ -0,0 +1 @@ +
\ No newline at end of file diff --git a/libraries/setrgb.xml b/libraries/setrgb.xml new file mode 100644 index 00000000..4b43eefe --- /dev/null +++ b/libraries/setrgb.xml @@ -0,0 +1 @@ +
25500
0.30.70.6
\ No newline at end of file diff --git a/lists.js b/lists.js index 94592287..4db9272e 100644 --- a/lists.js +++ b/lists.js @@ -7,7 +7,7 @@ written by Jens Mönig and Brian Harvey jens@moenig.org, bh@cs.berkeley.edu - Copyright (C) 2015 by Jens Mönig and Brian Harvey + Copyright (C) 2016 by Jens Mönig and Brian Harvey This file is part of Snap!. @@ -56,12 +56,13 @@ // Global settings ///////////////////////////////////////////////////// -/*global modules, contains, BoxMorph, WorldMorph, HandleMorph, -PushButtonMorph, SyntaxElementMorph, Color, Point, WatcherMorph, -StringMorph, SpriteMorph, ScrollFrameMorph, CellMorph, ArrowMorph, -MenuMorph, snapEquals, Morph, isNil, localize, MorphicPreferences*/ +/*global modules, BoxMorph, HandleMorph, PushButtonMorph, SyntaxElementMorph, +Color, Point, WatcherMorph, StringMorph, SpriteMorph, ScrollFrameMorph, +CellMorph, ArrowMorph, MenuMorph, snapEquals, Morph, isNil, localize, +MorphicPreferences, TableDialogMorph, SpriteBubbleMorph, SpeechBubbleMorph, +TableFrameMorph, TableMorph, Variable, isSnapObject*/ -modules.lists = '2015-July-27'; +modules.lists = '2016-July-14'; var List; var ListWatcherMorph; @@ -111,8 +112,14 @@ function List(array) { this.lastChanged = Date.now(); } +// List global preferences + +List.prototype.enableTables = false; // default, to not confuse NYC teachers + +// List printing + List.prototype.toString = function () { - return 'a List [' + this.asArray() + ']'; + return 'a List [' + this.length + ' elements]'; }; // List updating: @@ -234,6 +241,79 @@ List.prototype.contains = function (element) { }); }; +// List table (2D) accessing (for table morph widget): + +List.prototype.isTable = function () { + return this.enableTables && (this.length() > 100 || this.cols() > 1); +}; + +List.prototype.get = function (col, row) { + var r, len, cols; + if (!col) { + if (!row) {return [this.length()]; } + if (row > this.rows()) {return null; } + return this.rowName(row); + } else if (!row) { + if (this.cols() === 1) {return localize('items'); } + return this.colName(col); + } + r = this.at(row); + + // encode "orphaned" as arrays and overshooting ones as Variables + if (r instanceof List) { + len = r.length(); + cols = this.cols(); + if (col > len) { + return null; + } else if (cols === 1 && len > 1) { + return [r]; + } else if (col >= cols && len > cols) { // overshooting + return new Variable(r.at(col)); + } + return r.at(col); + } + if (col === 1 && row <= this.rows()) { + return [r]; + } + return null; +}; + +List.prototype.rows = function () { + return this.length(); +}; + +List.prototype.cols = function () { + var r = (this.at(1)); + return r instanceof List ? r.length() : 1; +}; + +List.prototype.colName = function (col) { + if (col > this.cols()) {return null; } + return String.fromCharCode(64 + ((col % 26) || 26)).repeat( + Math.floor((col - 1) / 26) + 1 + ); +}; + +List.prototype.rowName = function (row) { + return row; +}; + +List.prototype.columnNames = function () { + return []; +}; + +List.prototype.version = function (startRow, rows) { + var l = Math.min(startRow + rows, this.length()), + v = this.lastChanged, + r, + i; + for (i = startRow; i <= l; i += 1) { + r = this.at(i); + v = Math.max(v, r.lastChanged ? r.lastChanged : 0); + } + return v; +}; + // List conversion: List.prototype.asArray = function () { @@ -242,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, @@ -273,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; @@ -296,9 +387,11 @@ List.prototype.becomeLinked = function () { stop = this.length(); for (i = 0; i < stop; i += 1) { tail.first = this.contents[i]; - tail.rest = new List(); - tail.isLinked = true; - tail = tail.rest; + if (i < (stop - 1)) { + tail.rest = new List(); + tail.isLinked = true; + tail = tail.rest; + } } this.contents = []; this.isLinked = true; @@ -443,7 +536,7 @@ ListWatcherMorph.prototype.init = function (list, parentCell) { ); this.color = new Color(220, 220, 220); - this.isDraggable = true; + this.isDraggable = false; this.setExtent(new Point(80, 70).multiplyBy( SyntaxElementMorph.prototype.scale )); @@ -463,10 +556,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(); + } } }); @@ -682,6 +777,68 @@ ListWatcherMorph.prototype.arrangeCells = function () { this.frame.contents.adjustBounds(); }; +ListWatcherMorph.prototype.expand = function (maxExtent) { + // make sure to show all (first 100) cells + var fe = this.frame.contents.extent(), + ext = new Point(fe.x + 6, fe.y + this.label.height() + 6); + if (maxExtent) { + ext = ext.min(maxExtent); + } + this.setExtent(ext); + this.handle.setRight(this.right() - 3); + this.handle.setBottom(this.bottom() - 3); +}; + +// ListWatcherMorph context menu + +ListWatcherMorph.prototype.userMenu = function () { + if (!List.prototype.enableTables) { + return this.escalateEvent('userMenu'); + } + var menu = new MenuMorph(this), + myself = this; + menu.addItem('table view...', 'showTableView'); + menu.addLine(); + menu.addItem( + 'open in dialog...', + function () { + new TableDialogMorph(myself.list).popUp(myself.world()); + } + ); + return menu; +}; + +ListWatcherMorph.prototype.showTableView = function () { + var view = this.parentThatIsAnyOf([ + SpriteBubbleMorph, + SpeechBubbleMorph, + CellMorph + ]); + if (!view) {return; } + if (view instanceof SpriteBubbleMorph) { + view.changed(); + view.drawNew(true); + } else if (view instanceof SpeechBubbleMorph) { + view.contents = new TableFrameMorph(new TableMorph(this.list, 10)); + view.contents.expand(this.extent()); + view.drawNew(true); + } else { // watcher cell + view.drawNew(true, 'table'); + view.contentsMorph.expand(this.extent()); + } + view.fixLayout(); +}; + +// ListWatcherMorph events: + +ListWatcherMorph.prototype.mouseDoubleClick = function (pos) { + if (List.prototype.enableTables) { + new TableDialogMorph(this.list).popUp(this.world()); + } else { + this.escalateEvent('mouseDoubleClick', pos); + } +}; + // ListWatcherMorph hiding/showing: ListWatcherMorph.prototype.show = function () { diff --git a/locale.js b/locale.js index 151314c4..c2bef177 100644 --- a/locale.js +++ b/locale.js @@ -6,7 +6,7 @@ written by Jens Mönig - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -42,7 +42,7 @@ /*global modules, contains*/ -modules.locale = '2015-August-06'; +modules.locale = '2016-November-10'; // Global stuff @@ -123,7 +123,11 @@ SnapTranslator.dict.en = { 'translator_e-mail': 'jens@moenig.org', 'last_changed': - '2012-10-16', + '2015-12-22', + + // rewordings in English avoiding having to adjust all other translations + 'any': + 'random', // long strings look-up only 'file menu import hint': @@ -137,11 +141,18 @@ SnapTranslator.dict.en = { + 'a file on your computer by dropping it here\n', 'block deletion dialog text': 'Are you sure you want to delete this\n' - + 'custom block and all its instances?' + + 'custom block and all its instances?', + 'download to disk text': + 'This item could not be opened in a new tab.\n' + + 'It has been saved to your browser\'s downloads folder.', + 'unable to export text': + 'This item could not be exported from Snap!.\n' + + 'It\'s likely that your project may contain a lot of media ' + + '(sounds and images) or that you are using an older browser.' + + 'Please try using a recent version of Chrome, Firefox, or Safari.' }; SnapTranslator.dict.de = { - // meta information 'language_name': 'Deutsch', 'language_translator': @@ -149,23 +160,21 @@ SnapTranslator.dict.de = { 'translator_e-mail': 'jens@moenig.org', 'last_changed': - '2015-08-06' + '2016-11-10' }; SnapTranslator.dict.it = { - // meta information 'language_name': 'Italiano', 'language_translator': - 'Stefano Federici, Alberto Firpo', + 'Stefano Federici, Alberto Firpo, Massimo Ghisalberti', 'translator_e-mail': - 's_federici@yahoo.com, albertofirpo12@gmail.com', + 's_federici@yahoo.com, albertofirpo12@gmail.com, zairik@gmail.com', 'last_changed': - '2015-01-12' + '2016-05-10' }; SnapTranslator.dict.ja = { - // meta information 'language_name': '日本語', 'language_translator': @@ -177,7 +186,6 @@ SnapTranslator.dict.ja = { }; SnapTranslator.dict.ja_HIRA = { - // meta information 'language_name': 'にほんご', 'language_translator': @@ -189,7 +197,6 @@ SnapTranslator.dict.ja_HIRA = { }; SnapTranslator.dict.ko = { - // meta information 'language_name': '한국어', 'language_translator': @@ -201,7 +208,6 @@ SnapTranslator.dict.ko = { }; SnapTranslator.dict.pt = { - // meta information 'language_name': 'Português', 'language_translator': @@ -209,35 +215,32 @@ SnapTranslator.dict.pt = { 'translator_e-mail': 'mmsequeira@gmail.com', 'last_changed': - '2015-08-02' + '2016-10-30' }; SnapTranslator.dict.cs = { - // meta information 'language_name': 'Česky', 'language_translator': - 'Michal Moc', + 'Michal Moc, Jan Tomsa', 'translator_e-mail': - 'info@iguru.eu', + 'info@iguru.eu, jan.tomsa.1976@gmail.com', 'last_changed': - '2013-03-11' + '2015-11-16' }; SnapTranslator.dict.zh = { - // meta information 'language_name': '简体中文', 'language_translator': - '邓江华', + '五百刀/邓江华', 'translator_e-mail': - 'djh@rhjxx.cn', + 'ubertao@qq.com/djh@rhjxx.cn', 'last_changed': - '2013-03-25' + '2016-05-09' }; SnapTranslator.dict.eo = { - // meta information 'language_name': 'Esperanto', 'language_translator': @@ -249,43 +252,39 @@ SnapTranslator.dict.eo = { }; SnapTranslator.dict.fr = { - // meta information 'language_name': 'Fran\u00E7ais', 'language_translator': - 'Jean-Jacques Valliet, Mark Rafter, Martin Quinson', + 'Jean-Jacques Valliet, Mark Rafter, Martin Quinson, Damien Caselli', 'translator_e-mail': 'i.scool@mac.com', 'last_changed': - '2015-06-25' + '2016-10-27' }; SnapTranslator.dict.si = { - // meta information 'language_name': 'Sloven\u0161\u010Dina', 'language_translator': - 'Sasa Divjak', + 'Sasa Divjak, Gorazd Breskvar', 'translator_e-mail': 'sasa.divjak@fri.uni-lj.si', 'last_changed': - '2013-01-07' + '2016-04-22' }; SnapTranslator.dict.ru = { - // meta information 'language_name': 'Русский', 'language_translator': - 'Svetlana Ptashnaya', + 'Svetlana Ptashnaya, Проскурнёв Артём', 'translator_e-mail': - 'svetlanap@berkeley.edu', + 'svetlanap@berkeley.edu, tema@school830.ru', 'last_changed': - '2014-09-29' + '2016-05-25' }; SnapTranslator.dict.es = { - // meta information 'language_name': 'Espa\u00F1ol', 'language_translator': @@ -297,7 +296,6 @@ SnapTranslator.dict.es = { }; SnapTranslator.dict.nl = { - // meta information 'language_name': 'Nederlands', 'language_translator': @@ -305,11 +303,10 @@ SnapTranslator.dict.nl = { 'translator_e-mail': 'frank.sierens@telenet.be, sjoerddirk@fromScratchEd.nl', 'last_changed': - '2013-08-12' + '2015-12-15' }; SnapTranslator.dict.pl = { - // meta information 'language_name': 'Polski', 'language_translator': @@ -317,11 +314,10 @@ SnapTranslator.dict.pl = { 'translator_e-mail': 'witek@oeiizk.waw.pl', 'last_changed': - '2015-08-06' + '2015-09-23' }; SnapTranslator.dict.tw = { - // meta information 'language_name': '繁體中文', 'language_translator': @@ -333,7 +329,6 @@ SnapTranslator.dict.tw = { }; SnapTranslator.dict.no = { - // meta information 'language_name': 'Norsk', 'language_translator': @@ -345,7 +340,6 @@ SnapTranslator.dict.no = { }; SnapTranslator.dict.dk = { - // meta information 'language_name': 'Dansk', 'language_translator': @@ -357,7 +351,6 @@ SnapTranslator.dict.dk = { }; SnapTranslator.dict.el = { - // meta information 'language_name': 'Ελληνικά', 'language_translator': @@ -369,19 +362,17 @@ SnapTranslator.dict.el = { }; SnapTranslator.dict.ca = { - // meta information 'language_name': 'Català', 'language_translator': - 'Bernat Romagosa Carrasquer', + 'Bernat Romagosa Carrasquer, Joan Guillén i Pelegay', 'translator_e-mail': - 'bromagosa@citilab.eu', + 'bernat@arduino.org, jguille2@xtec.cat', 'last_changed': - '2015-01-21' + '2016-07-07' }; SnapTranslator.dict.fi = { - // meta information 'language_name': 'suomi', 'language_translator': @@ -393,7 +384,6 @@ SnapTranslator.dict.fi = { }; SnapTranslator.dict.sv = { - // meta information 'language_name': 'svenska', 'language_translator': @@ -401,11 +391,10 @@ SnapTranslator.dict.sv = { 'translator_e-mail': 'eolsson@gmail.com', 'last_changed': - '2014-12-14' + '2016-06-09' }; SnapTranslator.dict.pt_BR = { - // meta information 'language_name': 'Português do Brasil', 'language_translator': @@ -417,7 +406,6 @@ SnapTranslator.dict.pt_BR = { }; SnapTranslator.dict.bn = { - // meta information 'language_name': 'বাংলা', 'language_translator': @@ -429,7 +417,6 @@ SnapTranslator.dict.bn = { }; SnapTranslator.dict.kn = { - // translations meta information 'language_name': '\u0C95\u0CA8\u0CCD\u0CA8\u0CA1', 'language_translator': @@ -441,7 +428,6 @@ SnapTranslator.dict.kn = { }; SnapTranslator.dict.ml = { - // translations meta information 'language_name': 'Malayalam', 'language_translator': @@ -453,7 +439,6 @@ SnapTranslator.dict.ml = { }; SnapTranslator.dict.ta = { - // translations meta information 'language_name': 'Tamil', 'language_translator': @@ -465,7 +450,6 @@ SnapTranslator.dict.ta = { }; SnapTranslator.dict.te = { - // translations meta information 'language_name': 'Telagu', // the name as it should appear in the language menu 'language_translator': @@ -477,7 +461,6 @@ SnapTranslator.dict.te = { }; SnapTranslator.dict.tr = { - // translations meta information 'language_name': 'Türkçe', 'language_translator': @@ -489,7 +472,6 @@ SnapTranslator.dict.tr = { }; SnapTranslator.dict.hu = { - // translations meta information 'language_name': 'Magyar', 'language_translator': @@ -501,7 +483,6 @@ SnapTranslator.dict.hu = { }; SnapTranslator.dict.ia = { - // translations meta information 'language_name': 'Interlingua', 'language_translator': @@ -511,3 +492,80 @@ SnapTranslator.dict.ia = { 'last_changed': '2015-08-09' }; + +SnapTranslator.dict.hr = { + 'language_name': + 'Hrvatski', + 'language_translator': + '\u017Deljko Hrvoj', + 'translator_e-mail': + 'zeljko.hrvoj@zg.t-com.hr', + 'last_changed': + '2015-09-15' +}; + +SnapTranslator.dict.bg = { + 'language_name': + 'Български', + 'language_translator': + 'Ivan Savov', + 'translator_e-mail': + 'ivan.savov@gmail.com', + 'last_changed': + '2015-11-16' +}; + +SnapTranslator.dict.ro = { + 'language_name': + 'Român', + 'language_translator': + 'Cristian Macarascu', + 'translator_e-mail': + '', + 'last_changed': + '2015-10-24' +}; + +SnapTranslator.dict.ar = { + 'language_name': + 'العربية', // the name as it should appear in the language menu + 'language_translator': + 'طارق جلال', // your name for the Translators tab + 'translator_e-mail': + 'tarekgalal46@hotmail.com', // optional + 'last_changed': + '2016-02-24' +}; + +SnapTranslator.dict.id = { + 'language_name': + 'Bahasa Indonesia', + 'language_translator': + 'Alexander Raphael Liu', + 'translator_e-mail': + 'raphaxander@gmail.com', + 'last_changed': + '2016-5-2' +}; + +SnapTranslator.dict.et = { + 'language_name': + 'Eesti', + 'language_translator': + 'Hasso Tepper', + 'translator_e-mail': + 'hasso.tepper@gmail.com', + 'last_changed': + '2016-05-03' +}; + +SnapTranslator.dict.gl = { + 'language_name': + 'Galego', + 'language_translator': + 'tecnoloxia', + 'translator_e-mail': + '', + 'last_changed': + '2016-11-09' +}; diff --git a/manifest.mf b/manifest.mf deleted file mode 100644 index d0409612..00000000 --- a/manifest.mf +++ /dev/null @@ -1,13 +0,0 @@ -CACHE MANIFEST -snap.html -blocks.js -byob.js -gui.js -lists.js -morphic.js -objects.js -threads.js -widgets.js -store.js -xml.js -snap_logo_sm.png diff --git a/morphic.js b/morphic.js index e704f4a0..3dc14eab 100644 --- a/morphic.js +++ b/morphic.js @@ -8,7 +8,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -54,7 +54,8 @@ (6) development and user modes (7) turtle graphics (8) damage list housekeeping - (9) minifying morphic.js + (9) supporting high-resolution "retina" screens + (10) minifying morphic.js VIII. acknowledgements IX. contributors @@ -104,7 +105,6 @@ the following list shows the order in which all constructors are defined. Use this list to locate code in this document: - Global settings Global functions @@ -147,12 +147,13 @@ III. yet to implement --------------------- - keyboard support for scroll frames and lists + - full keyboard support for menus (partial support exists) - virtual keyboard support for Android and IE IV. open issues ---------------- - - blurry shadows don't work well in Chrome + - clipboard support (copy & paste) for non-textual data V. browser compatibility @@ -163,12 +164,15 @@ - Firefox for Windows - Firefox for Mac - - Chrome for Windows (blurry shadows have some issues) + - Firefox for Android + - Chrome for Windows - Chrome for Mac - - Safari for Windows + - Chrome for Android + - Safari for Windows (deprecated) - safari for Mac - Safari for iOS (mobile) - IE for Windows + - Edge for Windows - Opera for Windows - Opera for Mac @@ -258,10 +262,11 @@ window.onload = function () { world = new WorldMorph( document.getElementById('world')); - setInterval(loop, 50); + loop(); }; function loop() { + requestAnimationFrame(loop); world.doOneCycle(); } @@ -305,10 +310,11 @@ document.getElementById('world1'), false); world2 = new WorldMorph( document.getElementById('world2'), false); - setInterval(loop, 50); + loop(); }; function loop() { + requestAnimationFrame(loop); world1.doOneCycle(); world2.doOneCycle(); } @@ -330,7 +336,7 @@ (c) an application ------------------- Of course, most of the time you don't want to just plain use the - standard Morhic World "as is" out of the box, but write your own + standard Morphic World "as is" out of the box, but write your own application (something like Scratch!) in it. For such an application you'll create your own morph prototypes, perhaps assemble your own "window frame" and bring it all to life in a @@ -373,10 +379,11 @@ x = 0; y += 1; } - setInterval(loop, 50); + loop(); }; function loop() { + requestAnimationFrame(loop); world.doOneCycle(); } @@ -966,7 +973,7 @@ single submorph's changes tremendous performance improvements can be achieved by setting the trackChanges flag to false before propagating the layout changes, setting it to true again and then storing the full - bounds of the surrounding morph. An an example refer to the + bounds of the surrounding morph. As an example refer to the moveBy() @@ -982,8 +989,54 @@ methods of SyntaxElementMorph in the Snap application. - (9) minifying morphic.js - ------------------------ + (9) supporting high-resolution "retina" screens + ----------------------------------------------- + By default retina support gets installed when Morphic.js loads. There + are two global functions that let you test for retina availability: + + isRetinaSupported() - Bool, answers if retina support is available + isRetinaEnabled() - Bool, answers if currently in retina mode + + and two more functions that let you control retina support if it is + available: + + enableRetinaSupport() + disableRetinaSupport() + + Both of these internally test whether retina is available, so they are + safe to call directly. For an example how to make retina support + user-specifiable refer to + + Snap! >> guis.js >> toggleRetina() + + Even when in retina mode it often makes sense to use normal-resolution + canvasses for simple shapes in order to save system resources and + optimize performance. Examples are costumes and backgrounds in Snap. + In Morphic you can create new canvas elements using + + newCanvas(extentPoint [, nonRetinaFlag]) + + If retina support is enabled such new canvasses will automatically be + high-resolution canvasses, unless the newCanvas() function is given an + otherwise optional second Boolean argument that explicitly makes + it a non-retina canvas. + + Not the whole canvas API is supported by Morphic's retina utilities. + Especially if your code uses putImageData() you will want to "downgrade" + a target high-resolution canvas to a normal-resolution ("non-retina") + one before using + + normalizeCanvas(aCanvas [, copyFlag]) + + This will change the target canvas' resolution in place (!). If you + pass in the optional second Boolean flag the function returns + a non-retina copy and leaves the target canvas unchanged. An example + of this normalize mechanism is converting the penTrails layer of Snap's + stage (high-resolution) into a sprite-costume (normal resolution). + + + (10) minifying morphic.js + ------------------------- Coming from Smalltalk and being a Squeaker at heart I am a huge fan of browsing the code itself to make sense of it. Therefore I have included this documentation and (too little) inline comments so all @@ -1028,7 +1081,8 @@ I have originally written morphic.js in Florian Balmer's Notepad2 editor for Windows, later switched to Apple's Dashcode and later still to Apple's Xcode. I've also come to depend on both Douglas - Crockford's JSLint, Mozilla's Firebug and Google's Chrome to get + Crockford's JSLint and later the JSHint project, as well as on + Mozilla's Firebug and Google's Chrome to get it right. @@ -1039,16 +1093,17 @@ background texture handling, countless bug fixes and optimizations. Ian Reynolds contributed backspace key handling for Chrome. Davide Della Casa contributed performance optimizations for Firefox. + Jason N (@cyderize) contributed native copy & paste for text editing. + Bartosz Leper contributed retina display support. - Jens Mönig */ // Global settings ///////////////////////////////////////////////////// -/*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio, -FileList, getBlurredShadowSupport*/ +/*global window, HTMLCanvasElement, FileReader, Audio, FileList*/ -var morphicVersion = '2015-July-28'; +var morphicVersion = '2016-October-27'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -1068,7 +1123,8 @@ var standardSettings = { useVirtualKeyboard: true, isTouchDevice: false, // turned on by touch events, don't set rasterizeSVGs: false, - isFlat: false + isFlat: false, + grabThreshold: 5 }; var touchScreenSettings = { @@ -1087,11 +1143,40 @@ var touchScreenSettings = { useVirtualKeyboard: true, isTouchDevice: false, rasterizeSVGs: false, - isFlat: false + isFlat: false, + grabThreshold: 5 }; var MorphicPreferences = standardSettings; +// first, try enabling support for retina displays - can be turned off later + +/* + Support for retina displays has been pioneered and contributed by + Bartosz Leper. + + NOTE: this will make changes to the HTMLCanvasElement that - mostly - + make Morphic usable on retina displays in very high resolution mode + with crisp fonts and clear fine lines without you (the programmer) + needing to know any specifics, provided both the display and the browser + support these (Safari currently doesn't), otherwise these utilities will + not be installed. + If you don't want your Morphic application to support retina resolutions + you don't have to edit this morphic.js file to comment out the next line + of code, instead you can simply call + + disableRetinaSupport(); + + before you create your World(s) in the html page. Disabling retina + support also will simply do nothing if retina support is not possible + or already disabled, so it's equally safe to call. + + For an example how to make retina support user-specifiable refer to + Snap! >> guis.js >> toggleRetina() +*/ + +enableRetinaSupport(); + // Global Functions //////////////////////////////////////////////////// function nop() { @@ -1160,13 +1245,23 @@ function fontHeight(height) { return minHeight * 1.2; // assuming 1/5 font size for ascenders } -function newCanvas(extentPoint) { +function isWordChar(aCharacter) { + // can't use \b or \w because they ignore diacritics + return aCharacter.match(/[A-zÀ-ÿ0-9]/); +} + +function newCanvas(extentPoint, nonRetina) { // answer a new empty instance of Canvas, don't display anywhere + // nonRetina - optional Boolean "false" + // by default retina support is automatic var canvas, ext; ext = extentPoint || {x: 0, y: 0}; canvas = document.createElement('canvas'); canvas.width = ext.x; canvas.height = ext.y; + if (nonRetina && canvas.isRetinaEnabled) { + canvas.isRetinaEnabled = false; + } return canvas; } @@ -1271,6 +1366,364 @@ function copy(target) { return c; } +// Retina Display Support ////////////////////////////////////////////// + +/* + By default retina support gets installed when Morphic.js loads. There + are two global functions that let you test for retina availability: + + isRetinaSupported() - Boolean, whether retina support is available + isRetinaEnabled() - Boolean, whether currently in retina mode + + and two more functions that let you control retina support if it is + available: + + enableRetinaSupport() + disableRetinaSupport() + + Both of these internally test whether retina is available, so they are + safe to call directly. + + Even when in retina mode it often makes sense to use non-high-resolution + canvasses for simple shapes in order to save system resources and + optimize performance. Examples are costumes and backgrounds in Snap. + In Morphic you can create new canvas elements using + + newCanvas(extentPoint [, nonRetinaFlag]) + + If retina support is enabled such new canvasses will automatically be + high-resolution canvasses, unless the newCanvas() function is given an + otherwise optional second Boolean argument that explicitly makes + it a non-retina canvas. + + Not the whole canvas API is supported by Morphic's retina utilities. + Especially if your code uses putImageData() you will want to "downgrade" + a target high-resolution canvas to a normal-resolution ("non-retina") + one before using + + normalizeCanvas(aCanvas [, copyFlag]) + + This will change the target canvas' resolution in place (!). If you + pass in the optional second Boolean flag the function returns + a non-retina copy and leaves the target canvas unchanged. An example + of this normalize mechanism is converting the penTrails layer of Snap's + stage (high-resolution) into a sprite-costume (normal resolution). +*/ + +function enableRetinaSupport() { +/* + === contributed by Bartosz Leper === + + This installs a series of utilities that allow using Canvas the same way + on retina and non-retina displays. If the display is a retina one, the + underlying dimensions of the Canvas elements are doubled, but this will + be transparent to the code that uses Canvas. All dimensions read or + written to the Canvas element will be scaled appropriately. + + NOTE: This implementation is not exhaustive; it only implements what is + needed by the Snap! UI. + + [Jens]: like all other retina screen support implementations I've seen + Bartosz's patch also does not address putImageData() compatibility when + mixing retina-enabled and non-retina canvasses. If you need to manipulate + pixels in such mixed canvasses, make sure to "downgrade" them all using + normalizeCanvas() below. +*/ + + // Get the window's pixel ratio for canvas elements. + // See: http://www.html5rocks.com/en/tutorials/canvas/hidpi/ + var ctx = document.createElement("canvas").getContext("2d"), + backingStorePixelRatio = ctx.webkitBackingStorePixelRatio || + ctx.mozBackingStorePixelRatio || + ctx.msBackingStorePixelRatio || + ctx.oBackingStorePixelRatio || + ctx.backingStorePixelRatio || 1, + + // Unfortunately, it's really hard to make this work well when changing + // zoom level, so let's leave it like this right now, and stick to + // whatever the ratio was in the beginning. + + // originalDevicePixelRatio = window.devicePixelRatio, + + // [Jens]: As of summer 2016 non-integer devicePixelRatios lead to + // artifacts when blitting images onto canvas elements in all browsers + // except Chrome, especially Firefox, Edge, IE (Safari doesn't even + // support retina mode as implemented here). + // therefore - to ensure crisp fonts - use the ceiling of whatever + // the devicePixelRatio is. This needs more memory, but looks nicer. + + originalDevicePixelRatio = Math.ceil(window.devicePixelRatio), + + canvasProto = HTMLCanvasElement.prototype, + contextProto = CanvasRenderingContext2D.prototype, + + // [Jens]: keep track of original properties in a dictionary + // so they can be iterated over and restored + uber = { + drawImage: contextProto.drawImage, + getImageData: contextProto.getImageData, + + width: Object.getOwnPropertyDescriptor( + canvasProto, + 'width' + ), + height: Object.getOwnPropertyDescriptor( + canvasProto, + 'height' + ), + shadowOffsetX: Object.getOwnPropertyDescriptor( + contextProto, + 'shadowOffsetX' + ), + shadowOffsetY: Object.getOwnPropertyDescriptor( + contextProto, + 'shadowOffsetY' + ), + shadowBlur: Object.getOwnPropertyDescriptor( + contextProto, + 'shadowBlur' + ) + }; + + // [Jens]: only install retina utilities if the display supports them + if (backingStorePixelRatio === originalDevicePixelRatio) {return; } + // [Jens]: check whether properties can be overridden, needed for Safari + if (Object.keys(uber).some(function (any) { + var prop = uber[any]; + return prop.hasOwnProperty('configurable') && (!prop.configurable); + })) {return; } + + function getPixelRatio(imageSource) { + return imageSource.isRetinaEnabled ? + (originalDevicePixelRatio || 1) / backingStorePixelRatio : 1; + } + + canvasProto._isRetinaEnabled = true; + // [Jens]: remember the original non-retina properties, + // so they can be restored again + canvasProto._bak = uber; + + Object.defineProperty(canvasProto, 'isRetinaEnabled', { + get: function() { + return this._isRetinaEnabled; + }, + set: function(enabled) { + var prevPixelRatio = getPixelRatio(this); + var prevWidth = this.width; + var prevHeight = this.height; + + this._isRetinaEnabled = enabled; + if (getPixelRatio(this) != prevPixelRatio) { + this.width = prevWidth; + this.height = prevHeight; + } + }, + configurable: true // [Jens]: allow to be deleted an reconfigured + }); + + Object.defineProperty(canvasProto, 'width', { + get: function() { + return uber.width.get.call(this) / getPixelRatio(this); + }, + set: function(width) { + var pixelRatio = getPixelRatio(this); + uber.width.set.call(this, width * pixelRatio); + var context = this.getContext('2d'); + context.restore(); + context.save(); + context.scale(pixelRatio, pixelRatio); + } + }); + + Object.defineProperty(canvasProto, 'height', { + get: function() { + return uber.height.get.call(this) / getPixelRatio(this); + }, + set: function(height) { + var pixelRatio = getPixelRatio(this); + uber.height.set.call(this, height * pixelRatio); + var context = this.getContext('2d'); + context.restore(); + context.save(); + context.scale(pixelRatio, pixelRatio); + } + }); + + contextProto.drawImage = function(image) { + var pixelRatio = getPixelRatio(image), + sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight; + + // Different signatures of drawImage() method have different + // parameter assignments. + switch (arguments.length) { + case 9: + sx = arguments[1]; + sy = arguments[2]; + sWidth = arguments[3]; + sHeight = arguments[4]; + dx = arguments[5]; + dy = arguments[6]; + dWidth = arguments[7]; + dHeight = arguments[8]; + break; + + case 5: + sx = 0; + sy = 0; + sWidth = image.width; + sHeight = image.height; + dx = arguments[1]; + dy = arguments[2]; + dWidth = arguments[3]; + dHeight = arguments[4]; + break; + + case 3: + sx = 0; + sy = 0; + sWidth = image.width; + sHeight = image.height; + dx = arguments[1]; + dy = arguments[2]; + dWidth = image.width; + dHeight = image.height; + break; + + default: + throw Error('Called drawImage() with ' + arguments.length + + ' arguments'); + } + uber.drawImage.call( + this, image, + sx * pixelRatio, sy * pixelRatio, + sWidth * pixelRatio, sHeight * pixelRatio, + dx, dy, + dWidth, dHeight); + }; + + contextProto.getImageData = function(sx, sy, sw, sh) { + var pixelRatio = getPixelRatio(this.canvas); + return uber.getImageData.call( + this, + sx * pixelRatio, sy * pixelRatio, + sw * pixelRatio, sh * pixelRatio); + }; + + Object.defineProperty(contextProto, 'shadowOffsetX', { + get: function() { + return uber.shadowOffsetX.get.call(this) / + getPixelRatio(this.canvas); + }, + set: function(offset) { + var pixelRatio = getPixelRatio(this.canvas); + uber.shadowOffsetX.set.call(this, offset * pixelRatio); + } + }); + + Object.defineProperty(contextProto, 'shadowOffsetY', { + get: function() { + return uber.shadowOffsetY.get.call(this) / + getPixelRatio(this.canvas); + }, + set: function(offset) { + var pixelRatio = getPixelRatio(this.canvas); + uber.shadowOffsetY.set.call(this, offset * pixelRatio); + } + }); + + Object.defineProperty(contextProto, 'shadowBlur', { + get: function() { + return uber.shadowBlur.get.call(this) / + getPixelRatio(this.canvas); + }, + set: function(blur) { + var pixelRatio = getPixelRatio(this.canvas); + uber.shadowBlur.set.call(this, blur * pixelRatio); + } + }); +} + +function isRetinaSupported () { + var ctx = document.createElement("canvas").getContext("2d"), + backingStorePixelRatio = ctx.webkitBackingStorePixelRatio || + ctx.mozBackingStorePixelRatio || + ctx.msBackingStorePixelRatio || + ctx.oBackingStorePixelRatio || + ctx.backingStorePixelRatio || 1, + canvasProto = HTMLCanvasElement.prototype, + contextProto = CanvasRenderingContext2D.prototype, + uber = { + drawImage: contextProto.drawImage, + getImageData: contextProto.getImageData, + + width: Object.getOwnPropertyDescriptor( + canvasProto, + 'width' + ), + height: Object.getOwnPropertyDescriptor( + canvasProto, + 'height' + ), + shadowOffsetX: Object.getOwnPropertyDescriptor( + contextProto, + 'shadowOffsetX' + ), + shadowOffsetY: Object.getOwnPropertyDescriptor( + contextProto, + 'shadowOffsetY' + ), + shadowBlur: Object.getOwnPropertyDescriptor( + contextProto, + 'shadowBlur' + ) + }; + return backingStorePixelRatio !== window.devicePixelRatio && + !(Object.keys(uber).some(function (any) { + var prop = uber[any]; + return prop.hasOwnProperty('configurable') && (!prop.configurable); + }) + ); +} + +function isRetinaEnabled () { + return HTMLCanvasElement.prototype.hasOwnProperty('_isRetinaEnabled'); +} + +function disableRetinaSupport() { + // uninstalls Retina utilities. Make sure to re-create every Canvas + // element afterwards + var canvasProto, contextProto, uber; + if (!isRetinaEnabled()) {return; } + canvasProto = HTMLCanvasElement.prototype; + contextProto = CanvasRenderingContext2D.prototype; + uber = canvasProto._bak; + Object.defineProperty(canvasProto, 'width', uber.width); + Object.defineProperty(canvasProto, 'height', uber.height); + contextProto.drawImage = uber.drawImage; + contextProto.getImageData = uber.getImageData; + Object.defineProperty(contextProto, 'shadowOffsetX', uber.shadowOffsetX); + Object.defineProperty(contextProto, 'shadowOffsetY', uber.shadowOffsetY); + Object.defineProperty(contextProto, 'shadowBlur', uber.shadowBlur); + delete canvasProto._isRetinaEnabled; + delete canvasProto.isRetinaEnabled; + delete canvasProto._bak; +} + +function normalizeCanvas(aCanvas, getCopy) { + // make sure aCanvas is non-retina, otherwise convert it in place (!) + // or answer a normalized copy if the "getCopy" flag is + var cpy; + if (!aCanvas.isRetinaEnabled) {return aCanvas; } + cpy = newCanvas(new Point(aCanvas.width, aCanvas.height), true); + cpy.getContext('2d').drawImage(aCanvas, 0, 0); + if (getCopy) {return cpy; } + aCanvas.isRetinaEnabled = false; + aCanvas.width = cpy.width; + aCanvas.height = cpy.height; + aCanvas.getContext('2d').drawImage(cpy, 0, 0); + return aCanvas; +} + // Colors ////////////////////////////////////////////////////////////// // Color instance creation: @@ -1927,7 +2380,7 @@ Rectangle.prototype.round = function () { Rectangle.prototype.spread = function () { // round me by applying floor() to my origin and ceil() to my corner // expand by 1 to be on the safe side, this eliminates rounding - // artefacts caused by Safari's auto-scaling on retina displays + // artifacts caused by Safari's auto-scaling on retina displays return this.origin.floor().corner(this.corner.ceil()).expandBy(1); }; @@ -2079,6 +2532,20 @@ Node.prototype.forAllChildren = function (aFunction) { aFunction.call(null, this); }; +Node.prototype.anyChild = function (aPredicate) { + // includes myself + var i; + if (aPredicate.call(null, this)) { + return true; + } + for (i = 0; i < this.children.length; i += 1) { + if (this.children[i].anyChild(aPredicate)) { + return true; + } + } + return false; +}; + Node.prototype.allLeafs = function () { var result = []; this.allChildren().forEach(function (element) { @@ -2177,7 +2644,7 @@ Morph.uber = Node.prototype; single submorph's changes tremendous performance improvements can be achieved by setting the trackChanges flag to false before propagating the layout changes, setting it to true again and then storing the full - bounds of the surrounding morph. An an example refer to the + bounds of the surrounding morph. As an example refer to the fixLayout() @@ -2203,7 +2670,10 @@ function Morph() { Morph.prototype.init = function (noDraw) { Morph.uber.init.call(this); this.isMorph = true; + this.image = null; this.bounds = new Rectangle(0, 0, 50, 40); + this.cachedFullImage = null; + this.cachedFullBounds = null; this.color = new Color(80, 80, 80); this.texture = null; // optional url of a fill-image this.cachedTexture = null; // internal cache of actual bg image @@ -2280,9 +2750,7 @@ Morph.prototype.nextSteps = function (arrayOfFunctions) { } }; -Morph.prototype.step = function () { - nop(); -}; +Morph.prototype.step = nop; // Morph accessing - geometry getting: @@ -2408,6 +2876,9 @@ Morph.prototype.silentMoveBy = function (delta) { var children = this.children, i = children.length; this.bounds = this.bounds.translateBy(delta); + if (this.cachedFullBounds) { + this.cachedFullBounds = this.cachedFullBounds.translateBy(delta); + } // ugly optimization avoiding forEach() for (i; i > 0; i -= 1) { children[i - 1].silentMoveBy(delta); @@ -2529,7 +3000,12 @@ Morph.prototype.scrollIntoView = function () { // Morph accessing - dimensional changes requiring a complete redraw -Morph.prototype.setExtent = function (aPoint) { +Morph.prototype.setExtent = function (aPoint, silently) { + // silently avoids redrawing the receiver + if (silently) { + this.silentSetExtent(aPoint); + return; + } if (!aPoint.eq(this.extent())) { this.changed(); this.silentSetExtent(aPoint); @@ -2636,31 +3112,33 @@ Morph.prototype.drawCachedTexture = function () { */ Morph.prototype.drawOn = function (aCanvas, aRect) { - var rectangle, area, delta, src, context, w, h, sl, st; + var rectangle, area, delta, src, context, w, h, sl, st, + pic = this.cachedFullImage || this.image, + bounds = this.cachedFullBounds || this.bounds; if (!this.isVisible) { return null; } - rectangle = aRect || this.bounds(); - area = rectangle.intersect(this.bounds).round(); + rectangle = aRect || bounds; + area = rectangle.intersect(bounds); if (area.extent().gt(new Point(0, 0))) { - delta = this.position().neg(); - src = area.copy().translateBy(delta).round(); + delta = bounds.position().neg(); + src = area.copy().translateBy(delta); context = aCanvas.getContext('2d'); context.globalAlpha = this.alpha; sl = src.left(); st = src.top(); - w = Math.min(src.width(), this.image.width - sl); - h = Math.min(src.height(), this.image.height - st); + w = Math.min(src.width(), pic.width - sl); + h = Math.min(src.height(), pic.height - st); if (w < 1 || h < 1) { return null; } context.drawImage( - this.image, - src.left(), - src.top(), + pic, + sl, + st, w, h, area.left(), @@ -2668,42 +3146,6 @@ Morph.prototype.drawOn = function (aCanvas, aRect) { w, h ); - - /* "for debugging purposes:" - - try { - context.drawImage( - this.image, - src.left(), - src.top(), - w, - h, - area.left(), - area.top(), - w, - h - ); - } catch (err) { - alert('internal error\n\n' + err - + '\n ---' - + '\n canvas: ' + aCanvas - + '\n canvas.width: ' + aCanvas.width - + '\n canvas.height: ' + aCanvas.height - + '\n ---' - + '\n image: ' + this.image - + '\n image.width: ' + this.image.width - + '\n image.height: ' + this.image.height - + '\n ---' - + '\n w: ' + w - + '\n h: ' + h - + '\n sl: ' + sl - + '\n st: ' + st - + '\n area.left: ' + area.left() - + '\n area.top ' + area.top() - ); - } - */ - } }; @@ -2712,8 +3154,9 @@ Morph.prototype.fullDrawOn = function (aCanvas, aRect) { if (!this.isVisible) { return null; } - rectangle = aRect || this.fullBounds(); + rectangle = aRect || this.cachedFullBounds || this.fullBounds(); this.drawOn(aCanvas, rectangle); + if (this.cachedFullImage) {return; } this.children.forEach(function (child) { child.fullDrawOn(aCanvas, rectangle); }); @@ -2746,11 +3189,10 @@ Morph.prototype.toggleVisibility = function () { // Morph full image: Morph.prototype.fullImageClassic = function () { - // why doesn't this work for all Morphs? - var fb = this.fullBounds(), + var fb = this.cachedFullBounds || this.fullBounds(), // use the cache since fullDrawOn() will img = newCanvas(fb.extent()), ctx = img.getContext('2d'); - ctx.translate(-this.bounds.origin.x, -this.bounds.origin.y); + ctx.translate(-fb.origin.x, -fb.origin.y); this.fullDrawOn(img, fb); img.globalAlpha = this.alpha; return img; @@ -2907,7 +3349,9 @@ Morph.prototype.fullChanged = function () { if (this.trackChanges) { var w = this.root(); if (w instanceof WorldMorph) { - w.broken.push(this.fullBounds().spread()); + w.broken.push( + (this.cachedFullBounds || this.fullBounds()).spread() + ); } } }; @@ -3685,9 +4129,13 @@ Morph.prototype.evaluateString = function (code) { Morph.prototype.isTouching = function (otherMorph) { var oImg = this.overlappingImage(otherMorph), - data = oImg.getContext('2d') - .getImageData(1, 1, oImg.width, oImg.height) - .data; + data; + if (!oImg.width || !oImg.height) { + return false; + } + data = oImg.getContext('2d') + .getImageData(1, 1, oImg.width, oImg.height) + .data; return detect( data, function (each) { @@ -4512,6 +4960,72 @@ CursorMorph.prototype.init = function (aStringOrTextMorph) { this.target.setAlignmentToLeft(); } this.gotoSlot(this.slot); + this.initializeClipboardHandler(); +}; + +CursorMorph.prototype.initializeClipboardHandler = function () { + // Add hidden text box for copying and pasting + var myself = this, + wrrld = this.target.world(); + + this.clipboardHandler = document.createElement('textarea'); + this.clipboardHandler.style.position = 'absolute'; + this.clipboardHandler.style.right = '101%'; // placed just out of view + + document.body.appendChild(this.clipboardHandler); + + this.clipboardHandler.value = this.target.selection(); + this.clipboardHandler.focus(); + this.clipboardHandler.select(); + + this.clipboardHandler.addEventListener( + 'keypress', + function (event) { + myself.processKeyPress(event); + this.value = myself.target.selection(); + this.select(); + }, + false + ); + + this.clipboardHandler.addEventListener( + 'keydown', + function (event) { + myself.processKeyDown(event); + if (event.shiftKey) { + wrrld.currentKey = 16; + } + this.value = myself.target.selection(); + this.select(); + + // Make sure tab prevents default + if (event.key === 'U+0009' || + event.key === 'Tab') { + myself.processKeyPress(event); + event.preventDefault(); + } + }, + false + ); + + this.clipboardHandler.addEventListener( + 'keyup', + function (event) { + wrrld.currentKey = null; + }, + false + ); + + this.clipboardHandler.addEventListener( + 'input', + function (event) { + if (this.value === '') { + myself.gotoSlot(myself.target.selectionStartSlot()); + myself.target.deleteSelection(); + } + }, + false + ); }; // CursorMorph event processing: @@ -4531,7 +5045,7 @@ CursorMorph.prototype.processKeyPress = function (event) { return null; } if (event.keyCode) { // Opera doesn't support charCode - if (event.ctrlKey) { + if (event.ctrlKey && (!event.altKey)) { this.ctrl(event.keyCode, event.shiftKey); } else if (event.metaKey) { this.cmd(event.keyCode, event.shiftKey); @@ -4542,7 +5056,7 @@ CursorMorph.prototype.processKeyPress = function (event) { ); } } else if (event.charCode) { // all other browsers - if (event.ctrlKey) { + if (event.ctrlKey && (!event.altKey)) { this.ctrl(event.charCode, event.shiftKey); } else if (event.metaKey) { this.cmd(event.charCode, event.shiftKey); @@ -4559,28 +5073,47 @@ CursorMorph.prototype.processKeyPress = function (event) { CursorMorph.prototype.processKeyDown = function (event) { // this.inspectKeyEvent(event); - var shift = event.shiftKey; + var shift = event.shiftKey, + wordNavigation = event.ctrlKey || event.altKey, + selecting = this.target.selection().length > 0; + this.keyDownEventUsed = false; - if (event.ctrlKey) { + if (event.ctrlKey && (!event.altKey)) { this.ctrl(event.keyCode, event.shiftKey); // notify target's parent of key event this.target.escalateEvent('reactToKeystroke', event); - return; } if (event.metaKey) { this.cmd(event.keyCode, event.shiftKey); // notify target's parent of key event this.target.escalateEvent('reactToKeystroke', event); - return; } switch (event.keyCode) { case 37: - this.goLeft(shift); + if (selecting && !shift && !wordNavigation) { + this.gotoSlot(Math.min(this.target.startMark, this.target.endMark)); + this.target.clearSelection(); + } else { + this.goLeft( + shift, + wordNavigation ? + this.slot - this.target.previousWordFrom(this.slot) + : 1); + } this.keyDownEventUsed = true; break; case 39: - this.goRight(shift); + if (selecting && !shift && !wordNavigation) { + this.gotoSlot(Math.max(this.target.startMark, this.target.endMark)); + this.target.clearSelection(); + } else { + this.goRight( + shift, + wordNavigation ? + this.target.nextWordFrom(this.slot) - this.slot + : 1); + } this.keyDownEventUsed = true; break; case 38: @@ -4608,7 +5141,7 @@ CursorMorph.prototype.processKeyDown = function (event) { this.keyDownEventUsed = true; break; case 13: - if (this.target instanceof StringMorph) { + if ((this.target instanceof StringMorph) || shift) { this.accept(); } else { this.insert('\n'); @@ -4671,9 +5204,9 @@ CursorMorph.prototype.gotoSlot = function (slot) { } }; -CursorMorph.prototype.goLeft = function (shift) { +CursorMorph.prototype.goLeft = function (shift, howMany) { this.updateSelection(shift); - this.gotoSlot(this.slot - 1); + this.gotoSlot(this.slot - (howMany || 1)); this.updateSelection(shift); }; @@ -4716,7 +5249,7 @@ CursorMorph.prototype.gotoPos = function (aPoint) { CursorMorph.prototype.updateSelection = function (shift) { if (shift) { - if (!this.target.endMark && !this.target.startMark) { + if (isNil(this.target.endMark) && isNil(this.target.startMark)) { this.target.startMark = this.slot; this.target.endMark = this.slot; } else if (this.target.endMark !== this.slot) { @@ -4865,9 +5398,25 @@ CursorMorph.prototype.destroy = function () { this.target.drawNew(); this.target.changed(); } + this.destroyClipboardHandler(); CursorMorph.uber.destroy.call(this); }; +CursorMorph.prototype.destroyClipboardHandler = function () { + var nodes = document.body.children, + each, + i; + if (this.clipboardHandler) { + for (i = 0; i < nodes.length; i += 1) { + each = nodes[i]; + if (each === this.clipboardHandler) { + document.body.removeChild(this.clipboardHandler); + this.clipboardHandler = null; + } + } + } +}; + // CursorMorph utilities: CursorMorph.prototype.inspectKeyEvent = function (event) { @@ -7280,7 +7829,7 @@ StringMorph.prototype.renderWithBlanks = function (context, startX, y) { }); }; -// StringMorph mesuring: +// StringMorph measuring: StringMorph.prototype.slotPosition = function (slot) { // answer the position point of the given index ("slot") @@ -7305,8 +7854,10 @@ StringMorph.prototype.slotPosition = function (slot) { }; StringMorph.prototype.slotAt = function (aPoint) { - // answer the slot (index) closest to the given point + // answer the slot (index) closest to the given point taking + // in account how far from the middle of the character it is, // so the cursor can be moved accordingly + var txt = this.isPassword ? this.password('*', this.text.length) : this.text, idx = 0, @@ -7324,7 +7875,14 @@ StringMorph.prototype.slotAt = function (aPoint) { } } } - return idx - 1; + + // see where our click fell with respect to the middle of the char + if (aPoint.x - this.left() > + charX - context.measureText(txt[idx - 1]).width / 2) { + return idx; + } else { + return idx - 1; + } }; StringMorph.prototype.upFrom = function (slot) { @@ -7347,6 +7905,41 @@ StringMorph.prototype.endOfLine = function () { return this.text.length; }; +StringMorph.prototype.previousWordFrom = function (aSlot) { + // answer the slot (index) slots indicating the position of the + // previous word to the left of aSlot + var index = aSlot - 1; + + // while the current character is non-word one, we skip it, so that + // if we are in the middle of a non-alphanumeric sequence, we'll get + // right to the beginning of the previous word + while (index > 0 && !isWordChar(this.text[index])) { + index -= 1; + } + + // while the current character is a word one, we skip it until we + // find the beginning of the current word + while (index > 0 && isWordChar(this.text[index - 1])) { + index -= 1; + } + + return index; +}; + +StringMorph.prototype.nextWordFrom = function (aSlot) { + var index = aSlot; + + while (index < this.endOfLine() && !isWordChar(this.text[index])) { + index += 1; + } + + while (index < this.endOfLine() && isWordChar(this.text[index])) { + index += 1; + } + + return index; +}; + StringMorph.prototype.rawHeight = function () { // answer my corrected fontSize return this.height() / 1.2; @@ -7512,13 +8105,13 @@ StringMorph.prototype.selectionStartSlot = function () { StringMorph.prototype.clearSelection = function () { if (!this.currentlySelecting && - this.startMark === 0 && - this.endMark === 0) { + isNil(this.startMark) && + isNil(this.endMark)) { return; } this.currentlySelecting = false; - this.startMark = 0; - this.endMark = 0; + this.startMark = null; + this.endMark = null; this.drawNew(); this.changed(); }; @@ -7534,8 +8127,13 @@ StringMorph.prototype.deleteSelection = function () { }; StringMorph.prototype.selectAll = function () { + var cursor; if (this.isEditable) { this.startMark = 0; + cursor = this.root().cursor; + if (cursor) { + cursor.gotoSlot(this.text.length); + } this.endMark = this.text.length; this.drawNew(); this.changed(); @@ -7543,13 +8141,31 @@ StringMorph.prototype.selectAll = function () { }; StringMorph.prototype.mouseDownLeft = function (pos) { - if (this.isEditable) { + if (this.world().currentKey === 16) { + this.shiftClick(pos); + } else if (this.isEditable) { this.clearSelection(); } else { this.escalateEvent('mouseDownLeft', pos); } }; +StringMorph.prototype.shiftClick = function (pos) { + var cursor = this.root().cursor; + + if (cursor) { + if (!this.startMark) { + this.startMark = cursor.slot; + } + cursor.gotoPos(pos); + this.endMark = cursor.slot; + this.drawNew(); + this.changed(); + } + this.currentlySelecting = false; + this.escalateEvent('mouseDownLeft', pos); +}; + StringMorph.prototype.mouseClickLeft = function (pos) { var cursor; if (this.isEditable) { @@ -7566,18 +8182,79 @@ StringMorph.prototype.mouseClickLeft = function (pos) { } }; +StringMorph.prototype.mouseDoubleClick = function (pos) { + // selects the word at pos + // if there is no word, we select whatever is between + // the previous and next words + var slot = this.slotAt(pos); + + if (this.isEditable) { + this.edit(); + + if (slot === this.text.length) { + slot -= 1; + } + + if (isWordChar(this.text[slot])) { + this.selectWordAt(slot); + } else { + this.selectBetweenWordsAt(slot); + } + } else { + this.escalateEvent('mouseDoubleClick', pos); + } + +}; + +StringMorph.prototype.selectWordAt = function (slot) { + var cursor = this.root().cursor; + + if (slot === 0 || isWordChar(this.text[slot - 1])) { + cursor.gotoSlot(this.previousWordFrom(slot)); + this.startMark = cursor.slot; + this.endMark = this.nextWordFrom(cursor.slot); + } else { + cursor.gotoSlot(slot); + this.startMark = slot; + this.endMark = this.nextWordFrom(slot); + } + + this.drawNew(); + this.changed(); +}; + +StringMorph.prototype.selectBetweenWordsAt = function (slot) { + var cursor = this.root().cursor; + + cursor.gotoSlot(this.nextWordFrom(this.previousWordFrom(slot))); + this.startMark = cursor.slot; + this.endMark = cursor.slot; + + while (this.endMark < this.text.length + && !isWordChar(this.text[this.endMark])) { + this.endMark += 1; + } + + this.drawNew(); + this.changed(); +}; + StringMorph.prototype.enableSelecting = function () { this.mouseDownLeft = function (pos) { var crs = this.root().cursor, already = crs ? crs.target === this : false; - this.clearSelection(); - if (this.isEditable && (!this.isDraggable)) { - this.edit(); - this.root().cursor.gotoPos(pos); - this.startMark = this.slotAt(pos); - this.endMark = this.startMark; - this.currentlySelecting = true; - if (!already) {this.escalateEvent('mouseDownLeft', pos); } + if (this.world().currentKey === 16) { + this.shiftClick(pos); + } else { + this.clearSelection(); + if (this.isEditable && (!this.isDraggable)) { + this.edit(); + this.root().cursor.gotoPos(pos); + this.startMark = this.slotAt(pos); + this.endMark = this.startMark; + this.currentlySelecting = true; + if (!already) {this.escalateEvent('mouseDownLeft', pos); } + } } }; this.mouseMove = function (pos) { @@ -7897,8 +8574,10 @@ TextMorph.prototype.slotPosition = function (slot) { }; TextMorph.prototype.slotAt = function (aPoint) { - // answer the slot (index) closest to the given point + // answer the slot (index) closest to the given point taking + // in account how far from the middle of the character it is, // so the cursor can be moved accordingly + var charX = 0, row = 0, col = 0, @@ -7910,11 +8589,19 @@ TextMorph.prototype.slotAt = function (aPoint) { row += 1; } row = Math.max(row, 1); + while (aPoint.x - this.left() > charX) { charX += context.measureText(this.lines[row - 1][col]).width; col += 1; } - return this.lineSlots[Math.max(row - 1, 0)] + col - 1; + + // see where our click fell with respect to the middle of the char + if (aPoint.x - this.left() > + charX - context.measureText(this.lines[row - 1][col]).width / 2) { + return this.lineSlots[Math.max(row - 1, 0)] + col; + } else { + return this.lineSlots[Math.max(row - 1, 0)] + col - 1; + } }; TextMorph.prototype.upFrom = function (slot) { @@ -7956,6 +8643,10 @@ TextMorph.prototype.endOfLine = function (slot) { this.lines[this.columnRow(slot).y].length - 1; }; +TextMorph.prototype.previousWordFrom = StringMorph.prototype.previousWordFrom; + +TextMorph.prototype.nextWordFrom = StringMorph.prototype.nextWordFrom; + // TextMorph editing: TextMorph.prototype.edit = StringMorph.prototype.edit; @@ -7973,8 +8664,17 @@ TextMorph.prototype.selectAll = StringMorph.prototype.selectAll; TextMorph.prototype.mouseDownLeft = StringMorph.prototype.mouseDownLeft; +TextMorph.prototype.shiftClick = StringMorph.prototype.shiftClick; + TextMorph.prototype.mouseClickLeft = StringMorph.prototype.mouseClickLeft; +TextMorph.prototype.mouseDoubleClick = StringMorph.prototype.mouseDoubleClick; + +TextMorph.prototype.selectWordAt = StringMorph.prototype.selectWordAt; + +TextMorph.prototype.selectBetweenWordsAt + = StringMorph.prototype.selectBetweenWordsAt; + TextMorph.prototype.enableSelecting = StringMorph.prototype.enableSelecting; TextMorph.prototype.disableSelecting = StringMorph.prototype.disableSelecting; @@ -8742,7 +9442,8 @@ ScrollFrameMorph.prototype.init = function (scroller, size, sliderColor) { ScrollFrameMorph.uber.init.call(this); this.scrollBarSize = size || MorphicPreferences.scrollBarSize; this.autoScrollTrigger = null; - this.isScrollingByDragging = true; // change if desired + this.enableAutoScrolling = true; // change to suppress + this.isScrollingByDragging = true; // change to suppress this.hasVelocity = true; // dto. this.padding = 0; // around the scrollable area this.growth = 0; // pixels or Point to grow right/left when near edge @@ -8912,6 +9613,7 @@ ScrollFrameMorph.prototype.mouseDownLeft = function (pos) { return null; } var world = this.root(), + hand = world.hand, oldPos = pos, myself = this, deltaX = 0, @@ -8920,10 +9622,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); @@ -9444,7 +10154,7 @@ function HandMorph(aWorld) { // HandMorph initialization: HandMorph.prototype.init = function (aWorld) { - HandMorph.uber.init.call(this); + HandMorph.uber.init.call(this, true); this.bounds = new Rectangle(); // additional properties: @@ -9452,6 +10162,7 @@ HandMorph.prototype.init = function (aWorld) { this.mouseButton = null; this.mouseOverList = []; this.morphToGrab = null; + this.grabPosition = null; this.grabOrigin = null; this.temporaries = []; this.touchHoldTimeout = null; @@ -9516,6 +10227,8 @@ HandMorph.prototype.grab = function (aMorph) { if (aMorph.prepareToBeGrabbed) { aMorph.prepareToBeGrabbed(this); } + aMorph.cachedFullImage = aMorph.fullImageClassic(); + aMorph.cachedFullBounds = aMorph.fullBounds(); this.add(aMorph); this.changed(); if (oldParent && oldParent.reactToGrabOf) { @@ -9531,6 +10244,8 @@ HandMorph.prototype.drop = function () { target = this.dropTargetFor(morphToDrop); this.changed(); target.add(morphToDrop); + morphToDrop.cachedFullImage = null; + morphToDrop.cachedFullBounds = null; morphToDrop.changed(); morphToDrop.removeShadow(); this.children = []; @@ -9541,7 +10256,6 @@ HandMorph.prototype.drop = function () { if (target.reactToDropOf) { target.reactToDropOf(morphToDrop, this); } - this.dragOrigin = null; } }; @@ -9568,6 +10282,7 @@ HandMorph.prototype.processMouseDown = function (event) { this.destroyTemporaries(); this.contextMenuEnabled = true; this.morphToGrab = null; + this.grabPosition = null; if (this.children.length !== 0) { this.drop(); this.mouseButton = null; @@ -9595,6 +10310,7 @@ HandMorph.prototype.processMouseDown = function (event) { } if (!morph.mouseMove) { this.morphToGrab = morph.rootForGrab(); + this.grabPosition = this.bounds.origin.copy(); } if (event.button === 2 || event.ctrlKey) { this.mouseButton = 'right'; @@ -9704,8 +10420,7 @@ HandMorph.prototype.processMouseMove = function (event) { mouseOverNew, myself = this, morph, - topMorph, - fb; + topMorph; pos = new Point( event.pageX - posInDocument.x, @@ -9729,7 +10444,11 @@ HandMorph.prototype.processMouseMove = function (event) { } // if a morph is marked for grabbing, just grab it - if (this.mouseButton === 'left' && this.morphToGrab) { + if (this.mouseButton === 'left' && + this.morphToGrab && + (this.grabPosition.distanceTo(this.bounds.origin) > + MorphicPreferences.grabThreshold)) { + this.setPosition(this.grabPosition); if (this.morphToGrab.isDraggable) { morph = this.morphToGrab; this.grab(morph); @@ -9743,16 +10462,7 @@ HandMorph.prototype.processMouseMove = function (event) { this.grab(morph); this.grabOrigin = this.morphToGrab.situation(); } - if (morph) { - // if the mouse has left its fullBounds, allow to center it - fb = morph.fullBounds(); - if (!fb.containsPoint(pos) && - morph.isCorrectingOutsideDrag()) { - this.bounds.origin = fb.center(); - this.grab(morph); - this.setPosition(pos); - } - } + this.setPosition(pos); } } @@ -9778,7 +10488,12 @@ HandMorph.prototype.processMouseMove = function (event) { // autoScrolling support: if (myself.children.length > 0) { - if (newMorph instanceof ScrollFrameMorph) { + if (newMorph instanceof ScrollFrameMorph && + newMorph.enableAutoScrolling && + newMorph.contents.allChildren().some(function (any) { + return any.wantsDropOf(myself.children[0]); + }) + ) { if (!newMorph.bounds.insetBy( MorphicPreferences.scrollBarSize * 3 ).containsPoint(myself.bounds.origin)) { @@ -9867,7 +10582,7 @@ HandMorph.prototype.processDrop = function (event) { target = target.parent; } pic.onload = function () { - canvas = newCanvas(new Point(pic.width, pic.height)); + canvas = newCanvas(new Point(pic.width, pic.height), true); canvas.getContext('2d').drawImage(pic, 0, 0); target.droppedImage(canvas, aFile.name); }; @@ -9958,7 +10673,7 @@ HandMorph.prototype.processDrop = function (event) { } img = new Image(); img.onload = function () { - canvas = newCanvas(new Point(img.width, img.height)); + canvas = newCanvas(new Point(img.width, img.height), true); canvas.getContext('2d').drawImage(img, 0, 0); target.droppedImage(canvas); }; @@ -9970,7 +10685,7 @@ HandMorph.prototype.processDrop = function (event) { } img = new Image(); img.onload = function () { - canvas = newCanvas(new Point(img.width, img.height)); + canvas = newCanvas(new Point(img.width, img.height), true); canvas.getContext('2d').drawImage(img, 0, 0); target.droppedImage(canvas); }; @@ -10041,8 +10756,8 @@ WorldMorph.prototype.init = function (aCanvas, fillPage) { this.broken = []; this.hand = new HandMorph(this); this.keyboardReceiver = null; - this.lastEditedText = null; this.cursor = null; + this.lastEditedText = null; this.activeMenu = null; this.activeHandle = null; this.virtualKeyboard = null; @@ -10110,22 +10825,16 @@ WorldMorph.prototype.doOneCycle = function () { }; WorldMorph.prototype.fillPage = function () { - var pos = getDocumentPositionOf(this.worldCanvas), - clientHeight = window.innerHeight, + var clientHeight = window.innerHeight, clientWidth = window.innerWidth, myself = this; + this.worldCanvas.style.position = "absolute"; + this.worldCanvas.style.left = "0px"; + this.worldCanvas.style.right = "0px"; + this.worldCanvas.style.width = "100%"; + this.worldCanvas.style.height = "100%"; - if (pos.x > 0) { - this.worldCanvas.style.position = "absolute"; - this.worldCanvas.style.left = "0px"; - pos.x = 0; - } - if (pos.y > 0) { - this.worldCanvas.style.position = "absolute"; - this.worldCanvas.style.top = "0px"; - pos.y = 0; - } if (document.documentElement.scrollTop) { // scrolled down b/c of viewport scaling clientHeight = document.documentElement.clientHeight; @@ -10220,14 +10929,12 @@ WorldMorph.prototype.initVirtualKeyboard = function () { myself.keyboardReceiver.processKeyDown(event); } // supress backspace override - if (event.keyIdentifier === 'U+0008' || - event.keyIdentifier === 'Backspace') { + if (event.keyCode === 8) { event.preventDefault(); } // supress tab override and make sure tab gets // received by all browsers - if (event.keyIdentifier === 'U+0009' || - event.keyIdentifier === 'Tab') { + if (event.keyCode === 9) { if (myself.keyboardReceiver) { myself.keyboardReceiver.processKeyPress(event); } @@ -10352,21 +11059,19 @@ WorldMorph.prototype.initEventListeners = function () { myself.keyboardReceiver.processKeyDown(event); } // supress backspace override - if (event.keyIdentifier === 'U+0008' || - event.keyIdentifier === 'Backspace') { + if (event.keyCode === 8) { event.preventDefault(); } // supress tab override and make sure tab gets // received by all browsers - if (event.keyIdentifier === 'U+0009' || - event.keyIdentifier === 'Tab') { + if (event.keyCode === 9) { if (myself.keyboardReceiver) { myself.keyboardReceiver.processKeyPress(event); } event.preventDefault(); } - if ((event.ctrlKey || event.metaKey) && - (event.keyIdentifier !== 'U+0056')) { // allow pasting-in + if ((event.ctrlKey && (!event.altKey) || event.metaKey) && + (event.keyCode !== 86)) { // allow pasting-in event.preventDefault(); } }, @@ -10816,9 +11521,6 @@ WorldMorph.prototype.edit = function (aStringOrTextMorph) { if (this.cursor) { this.cursor.destroy(); } - if (this.lastEditedText) { - this.lastEditedText.clearSelection(); - } this.cursor = new CursorMorph(aStringOrTextMorph); aStringOrTextMorph.parent.add(this.cursor); this.keyboardReceiver = this.cursor; @@ -10836,6 +11538,11 @@ WorldMorph.prototype.edit = function (aStringOrTextMorph) { this.slide(aStringOrTextMorph); } } + + if (this.lastEditedText !== aStringOrTextMorph) { + aStringOrTextMorph.escalateEvent('freshTextEdit', aStringOrTextMorph); + } + this.lastEditedText = aStringOrTextMorph; }; WorldMorph.prototype.slide = function (aStringOrTextMorph) { @@ -10881,10 +11588,10 @@ WorldMorph.prototype.slide = function (aStringOrTextMorph) { WorldMorph.prototype.stopEditing = function () { if (this.cursor) { - this.lastEditedText = this.cursor.target; + this.cursor.target.escalateEvent('reactToEdit', this.cursor.target); + this.cursor.target.clearSelection(); this.cursor.destroy(); this.cursor = null; - this.lastEditedText.escalateEvent('reactToEdit', this.lastEditedText); } this.keyboardReceiver = null; if (this.virtualKeyboard) { @@ -10892,6 +11599,7 @@ WorldMorph.prototype.stopEditing = function () { document.body.removeChild(this.virtualKeyboard); this.virtualKeyboard = null; } + this.lastEditedText = null; this.worldCanvas.focus(); }; diff --git a/morphic.txt b/morphic.txt index 11a323df..a2a5ffad 100755 --- a/morphic.txt +++ b/morphic.txt @@ -7,9 +7,9 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2016 by Jens Mönig - this documentation last changed: June 26, 2015 + this documentation last changed: July 14, 2016 This file is part of Snap!. @@ -55,7 +55,8 @@ (6) development and user modes (7) turtle graphics (8) damage list housekeeping - (9) minifying morphic.js + (9) supporting high-resolution "retina" screens + (10) minifying morphic.js VIII. acknowledgements IX. contributors @@ -105,7 +106,6 @@ the following list shows the order in which all constructors are defined. Use this list to locate code in this document: - Global settings Global functions @@ -148,12 +148,13 @@ III. yet to implement --------------------- - keyboard support for scroll frames and lists + - full keyboard support for menus (partial support exists) - virtual keyboard support for Android and IE IV. open issues ---------------- - - blurry shadows don't work well in Chrome + - clipboard support (copy & paste) for non-textual data V. browser compatibility @@ -164,12 +165,15 @@ - Firefox for Windows - Firefox for Mac - - Chrome for Windows (blurry shadows have some issues) + - Firefox for Android + - Chrome for Windows - Chrome for Mac - - Safari for Windows + - Chrome for Android + - Safari for Windows (deprecated) - safari for Mac - Safari for iOS (mobile) - IE for Windows + - Edge for Windows - Opera for Windows - Opera for Mac @@ -259,10 +263,11 @@ window.onload = function () { world = new WorldMorph( document.getElementById('world')); - setInterval(loop, 50); + loop(); }; function loop() { + requestAnimationFrame(loop); world.doOneCycle(); } @@ -306,10 +311,11 @@ document.getElementById('world1'), false); world2 = new WorldMorph( document.getElementById('world2'), false); - setInterval(loop, 50); + loop(); }; function loop() { + requestAnimationFrame(loop); world1.doOneCycle(); world2.doOneCycle(); } @@ -331,7 +337,7 @@ (c) an application ------------------- Of course, most of the time you don't want to just plain use the - standard Morhic World "as is" out of the box, but write your own + standard Morphic World "as is" out of the box, but write your own application (something like Scratch!) in it. For such an application you'll create your own morph prototypes, perhaps assemble your own "window frame" and bring it all to life in a @@ -374,10 +380,11 @@ x = 0; y += 1; } - setInterval(loop, 50); + loop(); }; function loop() { + requestAnimationFrame(loop); world.doOneCycle(); } @@ -967,7 +974,7 @@ single submorph's changes tremendous performance improvements can be achieved by setting the trackChanges flag to false before propagating the layout changes, setting it to true again and then storing the full - bounds of the surrounding morph. An an example refer to the + bounds of the surrounding morph. As an example refer to the moveBy() @@ -983,8 +990,54 @@ methods of SyntaxElementMorph in the Snap application. - (9) minifying morphic.js - ------------------------ + (9) supporting high-resolution "retina" screens + ----------------------------------------------- + By default retina support gets installed when Morphic.js loads. There + are two global functions that let you test for retina availability: + + isRetinaSupported() - Bool, answers if retina support is available + isRetinaEnabled() - Bool, answers if currently in retina mode + + and two more functions that let you control retina support if it is + available: + + enableRetinaSupport() + disableRetinaSupport() + + Both of these internally test whether retina is available, so they are + safe to call directly. For an example how to make retina support + user-specifiable refer to + + Snap! >> guis.js >> toggleRetina() + + Even when in retina mode it often makes sense to use normal-resolution + canvasses for simple shapes in order to save system resources and + optimize performance. Examples are costumes and backgrounds in Snap. + In Morphic you can create new canvas elements using + + newCanvas(extentPoint [, nonRetinaFlag]) + + If retina support is enabled such new canvasses will automatically be + high-resolution canvasses, unless the newCanvas() function is given an + otherwise optional second Boolean argument that explicitly makes + it a non-retina canvas. + + Not the whole canvas API is supported by Morphic's retina utilities. + Especially if your code uses putImageData() you will want to "downgrade" + a target high-resolution canvas to a normal-resolution ("non-retina") + one before using + + normalizeCanvas(aCanvas [, copyFlag]) + + This will change the target canvas' resolution in place (!). If you + pass in the optional second Boolean flag the function returns + a non-retina copy and leaves the target canvas unchanged. An example + of this normalize mechanism is converting the penTrails layer of Snap's + stage (high-resolution) into a sprite-costume (normal resolution). + + + (10) minifying morphic.js + ------------------------- Coming from Smalltalk and being a Squeaker at heart I am a huge fan of browsing the code itself to make sense of it. Therefore I have included this documentation and (too little) inline comments so all @@ -1029,7 +1082,8 @@ I have originally written morphic.js in Florian Balmer's Notepad2 editor for Windows, later switched to Apple's Dashcode and later still to Apple's Xcode. I've also come to depend on both Douglas - Crockford's JSLint, Mozilla's Firebug and Google's Chrome to get + Crockford's JSLint and later the JSHint project, as well as on + Mozilla's Firebug and Google's Chrome to get it right. @@ -1040,5 +1094,7 @@ background texture handling, countless bug fixes and optimizations. Ian Reynolds contributed backspace key handling for Chrome. Davide Della Casa contributed performance optimizations for Firefox. + Jason N (@cyderize) contributed native copy & paste for text editing. + Bartosz Leper contributed retina display support. - Jens Mönig diff --git a/objects.js b/objects.js index bebff6cf..e4b68ce1 100644 --- a/objects.js +++ b/objects.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -61,71 +61,28 @@ sound handling Achal Dave contributed research and prototyping for creating music using the Web Audio API - Yuan Yuan contributed graphic effects for costumes + Yuan Yuan and Dylan Servilla contributed graphic effects for costumes */ -// globals from paint.js: -/*global PaintEditorMorph*/ - -// globals from lists.js: - -/*global ListWatcherMorph*/ - -// gloabls from widgets.js: - -/*global PushButtonMorph, ToggleMorph, DialogBoxMorph, InputFieldMorph*/ - -// gloabls from gui.js: - -/*global WatcherMorph, SpriteIconMorph*/ - -// globals from threads.js: - -/*global ArgMorph, BlockMorph, Process, StackFrame, ThreadManager, -VariableFrame, detect, threadsVersion*/ - -// globals from blocks.js: - -/*global ArgMorph, ArrowMorph, BlockHighlightMorph, BlockMorph, -BooleanSlotMorph, BoxMorph, Color, ColorPaletteMorph, ColorSlotMorph, -CommandBlockMorph, CommandSlotMorph, FrameMorph, HatBlockMorph, -InputSlotMorph, MenuMorph, Morph, MultiArgMorph, Point, -ReporterBlockMorph, ScriptsMorph, ShaAwMorph, StringMorph, -SyntaxElementMorph, TextMorph, WorldMorph, blocksVersion, contains, -degrees, detect, getDocumentPositionOf, newCanvas, nop, radians, -useBlurredShadows*/ - -// globals from morphic.js: - -/*global Array, BlinkerMorph, BouncerMorph, BoxMorph, CircleBoxMorph, -Color, ColorPaletteMorph, ColorPickerMorph, CursorMorph, Date, -FrameMorph, Function, GrayPaletteMorph, HandMorph, HandleMorph, -InspectorMorph, ListMorph, Math, MenuItemMorph, MenuMorph, Morph, -MorphicPreferences, MouseSensorMorph, Node, Object, PenMorph, Point, -Rectangle, ScrollFrameMorph, ShadowMorph, SliderButtonMorph, -SliderMorph, String, StringFieldMorph, StringMorph, TextMorph, -TriggerMorph, WorldMorph, contains, copy, degrees, detect, -document, getDocumentPositionOf, isNaN, isObject, isString, newCanvas, -nop, parseFloat, radians, standardSettings, touchScreenSettings, -useBlurredShadows, version, window, modules, IDE_Morph, VariableDialogMorph, -HTMLCanvasElement, Context, List, SpeechBubbleMorph, RingMorph, isNil, -FileReader*/ - -// globals from byob.js: - -/*global CustomBlockDefinition, BlockEditorMorph, BlockDialogMorph, -PrototypeHatBlockMorph*/ - -// globals from locale.js: - -/*global localize*/ - -// temporary globals - // Global stuff //////////////////////////////////////////////////////// -modules.objects = '2015-July-27'; +/*global PaintEditorMorph, ListWatcherMorph, PushButtonMorph, ToggleMorph, +DialogBoxMorph, InputFieldMorph, SpriteIconMorph, BlockMorph, +ThreadManager, VariableFrame, detect, BlockMorph, BoxMorph, Color, +CommandBlockMorph, FrameMorph, HatBlockMorph, MenuMorph, Morph, MultiArgMorph, +Point, ReporterBlockMorph, ScriptsMorph, StringMorph, SyntaxElementMorph, +TextMorph, contains, degrees, detect, newCanvas, nop, radians, Array, +CursorMorph, Date, FrameMorph, HandMorph, Math, MenuMorph, Morph, +MorphicPreferences, Object, PenMorph, Point, Rectangle, ScrollFrameMorph, +SliderMorph, String, StringMorph, TextMorph, contains, copy, degrees, detect, +document, isNaN, isString, newCanvas, nop, parseFloat, radians, window, +modules, IDE_Morph, VariableDialogMorph, HTMLCanvasElement, Context, List, +SpeechBubbleMorph, RingMorph, isNil, FileReader, TableDialogMorph, +BlockEditorMorph, BlockDialogMorph, PrototypeHatBlockMorph, localize, +TableMorph, TableFrameMorph, normalizeCanvas, BooleanSlotMorph*/ + +modules.objects = '2016-October-27'; var SpriteMorph; var StageMorph; @@ -141,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 @@ -187,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; @@ -569,6 +531,12 @@ SpriteMorph.prototype.initBlocks = function () { category: 'pen', spec: 'stamp' }, + floodFill: { + only: SpriteMorph, + type: 'command', + category: 'pen', + spec: 'fill' + }, // Control receiveGo: { @@ -583,7 +551,7 @@ SpriteMorph.prototype.initBlocks = function () { }, /* migrated to a newer block version: - + receiveClick: { type: 'hat', category: 'control', @@ -602,6 +570,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', @@ -895,6 +868,12 @@ SpriteMorph.prototype.initBlocks = function () { category: 'sensing', spec: 'current %dates' }, + reportGet: { + type: 'reporter', + category: 'sensing', + spec: 'my %get', + defaults: [['neighbors']] + }, // Operators reifyScript: { @@ -989,15 +968,18 @@ SpriteMorph.prototype.initBlocks = function () { category: 'operators', spec: 'not %b' }, - reportTrue: { + reportBoolean: { type: 'predicate', category: 'operators', - spec: 'true' + spec: '%bool', + alias: 'true boolean' }, - reportFalse: { + reportFalse: { // special case for keyboard entry and search type: 'predicate', category: 'operators', - spec: 'false' + spec: '%bool', + defaults: [false], + alias: 'false boolean' }, reportJoinWords: { type: 'reporter', @@ -1184,10 +1166,19 @@ SpriteMorph.prototype.initBlocks = function () { dev: true, type: 'command', category: 'lists', - spec: 'for %upvar in %l %cs', + spec: 'for %upvar in %l %cl', defaults: [localize('each item')] }, + // Tables - experimental + + doShowTable: { + dev: true, + type: 'command', + category: 'lists', + spec: 'show table %l' + }, + // Code mapping - experimental doMapCodeOrHeader: { // experimental type: 'command', @@ -1232,6 +1223,14 @@ SpriteMorph.prototype.initBlockMigrations = function () { receiveClick: { selector: 'receiveInteraction', inputs: [['clicked']] + }, + reportTrue: { + selector: 'reportBoolean', + inputs: [true] + }, + reportFalse: { + selector: 'reportBoolean', + inputs: [false] } }; }; @@ -1304,8 +1303,6 @@ SpriteMorph.prototype.blockAlternatives = { reportGreaterThan: ['reportEquals', 'reportLessThan'], reportAnd: ['reportOr'], reportOr: ['reportAnd'], - reportTrue: ['reportFalse'], - reportFalse: ['reportTrue'], // variables doSetVar: ['doChangeVar'], @@ -1333,6 +1330,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 @@ -1348,17 +1346,19 @@ SpriteMorph.prototype.init = function (globals) { this.idx = 0; // not to be serialized (!) - used for de-serialization this.wasWarped = false; // not to be serialized, used for fast-tracking - this.graphicsValues = { 'negative': 0, - 'fisheye': 0, - 'whirl': 0, - 'pixelate': 0, - 'mosaic': 0, - 'brightness': 0, - 'color': 0, - 'comic': 0, - 'duplicate': 0, - 'confetti': 0 - }; + this.graphicsValues = { + 'color': 0, + 'fisheye': 0, + 'whirl': 0, + 'pixelate': 0, + 'mosaic': 0, + 'duplicate': 0, + 'negative': 0, + 'comic': 0, + 'confetti': 0, + 'saturation': 0, + 'brightness': 0 + }; // sprite inheritance this.exemplar = null; @@ -1375,31 +1375,32 @@ SpriteMorph.prototype.init = function (globals) { // SpriteMorph duplicating (fullCopy) -SpriteMorph.prototype.fullCopy = function () { +SpriteMorph.prototype.fullCopy = function (forClone) { var c = SpriteMorph.uber.fullCopy.call(this), myself = this, arr = [], - dp, - cb; + cb, effect; c.stopTalking(); c.color = this.color.copy(); c.blocksCache = {}; c.paletteCache = {}; - c.scripts = this.scripts.fullCopy(); + c.scripts = this.scripts.fullCopy(forClone); c.scripts.owner = c; c.variables = this.variables.copy(); c.variables.owner = c; c.customBlocks = []; - this.customBlocks.forEach(function (def) { - cb = def.copyAndBindTo(c); - c.customBlocks.push(cb); - c.allBlockInstances(def).forEach(function (block) { - block.definition = cb; + if (!forClone) { + this.customBlocks.forEach(function (def) { + cb = def.copyAndBindTo(c); + c.customBlocks.push(cb); + c.allBlockInstances(def).forEach(function (block) { + block.definition = cb; + }); }); - }); + } this.costumes.asArray().forEach(function (costume) { - var cst = costume.copy(); + var cst = forClone ? costume : costume.copy(); arr.push(cst); if (costume === myself.costume) { c.costume = cst; @@ -1408,29 +1409,38 @@ SpriteMorph.prototype.fullCopy = function () { c.costumes = new List(arr); arr = []; this.sounds.asArray().forEach(function (sound) { - arr.push(sound); + var snd = forClone ? sound : sound.copy(); + arr.push(snd); }); c.sounds = new List(arr); + arr = []; c.nestingScale = 1; c.rotatesWithAnchor = true; c.anchor = null; c.parts = []; this.parts.forEach(function (part) { - dp = part.fullCopy(); + var dp = part.fullCopy(forClone); dp.nestingScale = part.nestingScale; dp.rotatesWithAnchor = part.rotatesWithAnchor; c.attachPart(dp); }); - + c.graphicsValues = {}; + for (effect in this.graphicsValues) { + if (this.graphicsValues.hasOwnProperty(effect)) { + c.graphicsValues[effect] = this.graphicsValues[effect]; + } + } return c; }; 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); }); @@ -1507,7 +1517,7 @@ SpriteMorph.prototype.drawNew = function () { // create a new, adequately dimensioned canvas // and draw the costume on it - this.image = newCanvas(costumeExtent); + this.image = newCanvas(costumeExtent, true); this.silentSetExtent(costumeExtent); ctx = this.image.getContext('2d'); ctx.scale(this.scale * stageScale, this.scale * stageScale); @@ -1536,7 +1546,7 @@ SpriteMorph.prototype.drawNew = function () { 1000 ); this.silentSetExtent(new Point(newX, newX)); - this.image = newCanvas(this.extent()); + this.image = newCanvas(this.extent(), true); this.setCenter(currentCenter, true); // just me SpriteMorph.uber.drawNew.call(this, facing); this.rotationOffset = this.extent().divideBy(2); @@ -1554,7 +1564,7 @@ SpriteMorph.prototype.drawNew = function () { } } - this.version = Date.now(); + this.version = Date.now(); // for observer optimization }; SpriteMorph.prototype.endWarp = function () { @@ -1584,8 +1594,13 @@ SpriteMorph.prototype.colorFiltered = function (aColor) { i, dta; - src = this.image.getContext('2d').getImageData(0, 0, ext.x, ext.y); - morph.image = newCanvas(ext); + src = normalizeCanvas(this.image, true).getContext('2d').getImageData( + 0, + 0, + ext.x, + ext.y + ); + morph.image = newCanvas(ext, true); morph.bounds = this.bounds.copy(); ctx = morph.image.getContext('2d'); dta = ctx.createImageData(ext.x, ext.y); @@ -1861,12 +1876,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')); @@ -1942,7 +1959,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')); @@ -1993,9 +2015,7 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push(block('reportAnd')); blocks.push(block('reportOr')); blocks.push(block('reportNot')); - blocks.push('-'); - blocks.push(block('reportTrue')); - blocks.push(block('reportFalse')); + blocks.push(block('reportBoolean')); blocks.push('-'); blocks.push(block('reportJoinWords')); blocks.push(block('reportTextSplit')); @@ -2014,9 +2034,9 @@ SpriteMorph.prototype.blockTemplates = function (category) { if (this.world().isDevMode) { blocks.push('-'); - txt = new TextMorph( + txt = new TextMorph(localize( 'development mode \ndebugging primitives:' - ); + )); txt.fontSize = 9; txt.setColor(this.paletteTextColor); blocks.push(txt); @@ -2127,6 +2147,7 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push(block('reportMap')); blocks.push('-'); blocks.push(block('doForEach')); + blocks.push(block('doShowTable')); } ///////////////////////////////// @@ -2326,7 +2347,7 @@ SpriteMorph.prototype.freshPalette = function (category) { x = block.right() + unit / 2; ry = block.bottom(); } else { - if (block.fixLayout) {block.fixLayout(); } + // if (block.fixLayout) {block.fixLayout(); } x = 0; y += block.height(); } @@ -2443,7 +2464,7 @@ SpriteMorph.prototype.blocksMatching = function ( // variable getters varNames.forEach(function (vName) { - var rel = relevance(labelOf(vName), search); + var rel = relevance(labelOf(vName.toLowerCase()), search); if (rel !== -1) { blocks.push([myself.variableBlock(vName), rel + '1']); } @@ -2776,7 +2797,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) { @@ -2834,16 +2857,34 @@ SpriteMorph.prototype.remove = function () { } }; -// SpriteMorph cloning (experimental) +// SpriteMorph cloning -SpriteMorph.prototype.createClone = function () { +/* + clones are temporary, partially shallow copies of sprites that don't + appear as icons in the corral. Clones get deleted when the red stop button + is pressed. Shallow-copying clones' scripts and costumes makes spawning + very fast, so they can be used for particle system simulations. + This speed-up, however, comes at the cost of some detrimental side + effects: Changes to a costume or a script of the original sprite are + in some cases shared with all of its clones, however such shared changes + are hard to predict for users and not actively propagated, so they don't + offer any reliable feature, and will not be supported as such. + Changes to the original sprite's scripts affect all of its clones, unless + the script contains any custom block whose definition contains one or more + block variables (in which case the script does get deep-copied). + The original sprite's scripting area, costumes wardrobe or sounds jukebox + are also not shared. therefore adding or deleting a script, sound or + costume in the original sprite has no effect on any of its clones. +*/ + +SpriteMorph.prototype.createClone = function (immediately) { var stage = this.parentThatIsA(StageMorph); - if (stage && stage.cloneCount <= 1000) { - this.fullCopy().clonify(stage); + if (stage && stage.cloneCount <= 2000) { + this.fullCopy(true).clonify(stage, immediately); } }; -SpriteMorph.prototype.clonify = function (stage) { +SpriteMorph.prototype.clonify = function (stage, immediately) { var hats; this.parts.forEach(function (part) { part.clonify(stage); @@ -2856,19 +2897,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 + immediately // without yielding + ); }); + 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: @@ -2895,8 +2952,10 @@ SpriteMorph.prototype.setColor = function (aColor) { y = this.yPosition(); if (!this.color.eq(aColor)) { this.color = aColor.copy(); - this.drawNew(); - this.gotoXY(x, y); + if (!this.costume) { + this.drawNew(); + this.silentGotoXY(x, y); + } } }; @@ -2970,11 +3029,11 @@ SpriteMorph.prototype.goBack = function (layers) { SpriteMorph.prototype.overlappingImage = function (otherSprite) { // overrides method from Morph because Sprites aren't nested Morphs var oRect = this.bounds.intersect(otherSprite.bounds), - oImg = newCanvas(oRect.extent()), + oImg = newCanvas(oRect.extent(), true), ctx = oImg.getContext('2d'); if (oRect.width() < 1 || oRect.height() < 1) { - return newCanvas(new Point(1, 1)); + return newCanvas(new Point(1, 1), true); } ctx.drawImage( this.image, @@ -2995,17 +3054,21 @@ SpriteMorph.prototype.overlappingImage = function (otherSprite) { SpriteMorph.prototype.doStamp = function () { var stage = this.parent, context = stage.penTrails().getContext('2d'), - isWarped = this.isWarped; + isWarped = this.isWarped, + originalAlpha = context.globalAlpha; + if (isWarped) { this.endWarp(); } context.save(); context.scale(1 / stage.scale, 1 / stage.scale); + context.globalAlpha = this.alpha; context.drawImage( this.image, (this.left() - stage.left()), (this.top() - stage.top()) ); + context.globalAlpha = originalAlpha; context.restore(); this.changed(); if (isWarped) { @@ -3092,105 +3155,311 @@ SpriteMorph.prototype.graphicsChanged = function () { }; SpriteMorph.prototype.applyGraphicsEffects = function (canvas) { -// For every effect: apply transform of that effect(canvas, stored value) -// The future: write more effects here - var ctx, imagedata, pixels, newimagedata; + // For every effect: apply transform of that effect(canvas, stored value) + // Graphic effects from Scratch are heavily based on ScratchPlugin.c - function transform_negative(p, value) { - var i, rcom, gcom, bcom; - if (value !== 0) { - for (i = 0; i < p.length; i += 4) { - rcom = 255 - p[i]; - gcom = 255 - p[i + 1]; - bcom = 255 - p[i + 2]; + var ctx, imagedata; - if (p[i] < rcom) { //compare to the complement - p[i] += value; - } else if (p[i] > rcom) { - p[i] -= value; + function transform_fisheye (imagedata, value) { + var pixels, newImageData, newPixels, centerX, centerY, + w, h, x, y, dx, dy, r, angle, srcX, srcY, i, srcI; + + w = imagedata.width; + h = imagedata.height; + pixels = imagedata.data; + newImageData = ctx.createImageData(w, h); + newPixels = newImageData.data; + + centerX = w / 2; + centerY = h / 2; + value = Math.max(0, (value + 100) / 100); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dx = (x - centerX) / centerX; + dy = (y - centerY) / centerY; + r = Math.pow(Math.sqrt(dx * dx + dy * dy), value); + if (r <= 1) { + angle = Math.atan2(dy, dx); + srcX = Math.floor(centerX + (r * Math.cos(angle) * centerX)); + srcY = Math.floor(centerY + (r * Math.sin(angle) * centerY)); + } else { + srcX = x; + srcY = y; } - if (p[i + 1] < gcom) { - p[i + 1] += value; - } else if (p[i + 1] > gcom) { - p[i + 1] -= value; + i = (y * w + x) * 4; + srcI = (srcY * w + srcX) * 4; + newPixels[i] = pixels[srcI]; + newPixels[i + 1] = pixels[srcI + 1]; + newPixels[i + 2] = pixels[srcI + 2]; + newPixels[i + 3] = pixels[srcI + 3]; + } + } + return newImageData; + } + + function transform_whirl (imagedata, value) { + var pixels, newImageData, newPixels, w, h, centerX, centerY, + x, y, radius, scaleX, scaleY, whirlRadians, radiusSquared, + dx, dy, d, factor, angle, srcX, srcY, i, srcI, sina, cosa; + + w = imagedata.width; + h = imagedata.height; + pixels = imagedata.data; + newImageData = ctx.createImageData(w, h); + newPixels = newImageData.data; + + centerX = w / 2; + centerY = h / 2; + radius = Math.min(centerX, centerY); + if (w < h) { + scaleX = h / w; + scaleY = 1; + } else { + scaleX = 1; + scaleY = w / h; + } + whirlRadians = -radians(value); + radiusSquared = radius * radius; + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + dx = scaleX * (x - centerX); + dy = scaleY * (y - centerY); + d = dx * dx + dy * dy; + if (d < radiusSquared) { + factor = 1 - (Math.sqrt(d) / radius); + angle = whirlRadians * (factor * factor); + sina = Math.sin(angle); + cosa = Math.cos(angle); + srcX = Math.floor((cosa * dx - sina * dy) / scaleX + centerX); + srcY = Math.floor((sina * dx + cosa * dy) / scaleY + centerY); + } else { + srcX = x; + srcY = y; } - if (p[i + 2] < bcom) { - p[i + 2] += value; - } else if (p[i + 2] > bcom) { - p[i + 2] -= value; + i = (y * w + x) * 4; + srcI = (srcY * w + srcX) * 4; + newPixels[i] = pixels[srcI]; + newPixels[i + 1] = pixels[srcI + 1]; + newPixels[i + 2] = pixels[srcI + 2]; + newPixels[i + 3] = pixels[srcI + 3]; + } + } + return newImageData; + } + + function transform_pixelate (imagedata, value) { + var pixels, newImageData, newPixels, w, h, + x, y, srcX, srcY, i, srcI; + + w = imagedata.width; + h = imagedata.height; + pixels = imagedata.data; + newImageData = ctx.createImageData(w, h); + newPixels = newImageData.data; + + value = Math.floor(Math.abs(value / 10) + 1); + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + srcX = Math.floor(x / value) * value; + srcY = Math.floor(y / value) * value; + i = (y * w + x) * 4; + srcI = (srcY * w + srcX) * 4; + newPixels[i] = pixels[srcI]; + newPixels[i + 1] = pixels[srcI + 1]; + newPixels[i + 2] = pixels[srcI + 2]; + newPixels[i + 3] = pixels[srcI + 3]; + } + } + return newImageData; + } + + function transform_mosaic (imagedata, value) { + var pixels, i, l, newImageData, newPixels, srcI; + pixels = imagedata.data; + newImageData = ctx.createImageData(imagedata.width, imagedata.height); + newPixels = newImageData.data; + + value = Math.round((Math.abs(value) + 10) / 10); + value = Math.max(0, Math.min(value, Math.min(imagedata.width, imagedata.height))); + for (i = 0, l = pixels.length; i < l; i += 4) { + srcI = i * value % l; + newPixels[i] = pixels[srcI]; + newPixels[i + 1] = pixels[srcI + 1]; + newPixels[i + 2] = pixels[srcI + 2]; + newPixels[i + 3] = pixels[srcI + 3]; + } + return newImageData; + } + + function transform_duplicate (imagedata, value) { + var pixels, i; + pixels = imagedata.data; + for (i = 0; i < pixels.length; i += 4) { + pixels[i] = pixels[i * value]; + pixels[i + 1] = pixels[i * value + 1]; + pixels[i + 2] = pixels[i * value + 2]; + pixels[i + 3] = pixels[i * value + 3]; + } + return imagedata; + } + + function transform_HSV (imagedata, hueShift, saturationShift, brightnessShift) { + var pixels, index, l, r, g, b, max, min, span, + h, s, v, i, f, p, q, t, newR, newG, newB; + pixels = imagedata.data; + for (index = 0, l = pixels.length; index < l; index += 4) { + r = pixels[index]; + g = pixels[index + 1]; + b = pixels[index + 2]; + + max = Math.max(r, g, b); + min = Math.min(r, g, b); + span = max - min; + if (span === 0) { + h = s = 0; + } else { + if (max === r) { + h = (60 * (g - b)) / span; + } else if (max === g) { + h = 120 + ((60 * (b - r)) / span); + } else if (max === b) { + h = 240 + ((60 * (r - g)) / span); } + s = (max - min) / max; } + if (h < 0) { + h += 360; + } + v = max / 255; + + h = (h + hueShift * 360 / 200) % 360; + s = Math.max(0, Math.min(s + saturationShift / 100, 1)); + v = Math.max(0, Math.min(v + brightnessShift / 100, 1)); + + i = Math.floor(h / 60); + f = (h / 60) - i; + p = v * (1 - s); + q = v * (1 - (s * f)); + t = v * (1 - (s * (1 - f))); + + if (i === 0 || i === 6) { + newR = v; + newG = t; + newB = p; + } else if (i === 1) { + newR = q; + newG = v; + newB = p; + } else if (i === 2) { + newR = p; + newG = v; + newB = t; + } else if (i === 3) { + newR = p; + newG = q; + newB = v; + } else if (i === 4) { + newR = t; + newG = p; + newB = v; + } else if (i === 5) { + newR = v; + newG = p; + newB = q; + } + + pixels[index] = newR * 255; + pixels[index + 1] = newG * 255; + pixels[index + 2] = newB * 255; } - return p; + return imagedata; } - function transform_brightness(p, value) { - var i; - if (value !== 0) { - for (i = 0; i < p.length; i += 4) { - p[i] += value; //255 = 100% of this color - p[i + 1] += value; - p[i + 2] += value; + function transform_negative (imagedata, value) { + var pixels, i, l, rcom, gcom, bcom; + pixels = imagedata.data; + for (i = 0, l = pixels.length; i < l; i += 4) { + rcom = 255 - pixels[i]; + gcom = 255 - pixels[i + 1]; + bcom = 255 - pixels[i + 2]; + + if (pixels[i] < rcom) { //compare to the complement + pixels[i] += value; + } else if (pixels[i] > rcom) { + pixels[i] -= value; + } + if (pixels[i + 1] < gcom) { + pixels[i + 1] += value; + } else if (pixels[i + 1] > gcom) { + pixels[i + 1] -= value; + } + if (pixels[i + 2] < bcom) { + pixels[i + 2] += value; + } else if (pixels[i + 2] > bcom) { + pixels[i + 2] -= value; } } - return p; + return imagedata; } - function transform_comic(p, value) { - var i; - if (value !== 0) { - for (i = 0; i < p.length; i += 4) { - p[i] += Math.sin(i * value) * 127 + 128; - p[i + 1] += Math.sin(i * value) * 127 + 128; - p[i + 2] += Math.sin(i * value) * 127 + 128; - } + function transform_comic (imagedata, value) { + var pixels, i, l; + pixels = imagedata.data; + for (i = 0, l = pixels.length; i < l; i += 4) { + pixels[i] += Math.sin(i * value) * 127 + 128; + pixels[i + 1] += Math.sin(i * value) * 127 + 128; + pixels[i + 2] += Math.sin(i * value) * 127 + 128; } - return p; + return imagedata; } - function transform_duplicate(p, value) { - var i; - if (value !== 0) { - for (i = 0; i < p.length; i += 4) { - p[i] = p[i * value]; - p[i + 1] = p[i * value + 1]; - p[i + 2] = p[i * value + 2]; - p[i + 3] = p[i * value + 3]; - } + function transform_confetti (imagedata, value) { + var pixels, i, l; + pixels = imagedata.data; + for (i = 0, l = pixels.length; i < l; i += 1) { + pixels[i] = Math.sin(value * pixels[i]) * 127 + pixels[i]; } - return p; - } - - function transform_confetti(p, value) { - var i; - if (value !== 0) { - for (i = 0; i < p.length; i += 1) { - p[i] = Math.sin(value * p[i]) * 127 + p[i]; - } - } - return p; + return imagedata; } if (this.graphicsChanged()) { ctx = canvas.getContext("2d"); imagedata = ctx.getImageData(0, 0, canvas.width, canvas.height); - pixels = imagedata.data; - //A sprite should wear all 7 effects at once - /*pixels = transform_whirl(pixels, this.graphicsValues.whirl);*/ - pixels = transform_negative(pixels, this.graphicsValues.negative); - pixels = transform_brightness(pixels, this.graphicsValues.brightness); - pixels = transform_comic(pixels, this.graphicsValues.comic); - /*pixels = transform_pixelate(pixels, this.graphicsValues.pixelate);*/ - pixels = transform_duplicate(pixels, this.graphicsValues.duplicate); - /*pixels = transform_color(pixels, this.graphicsValues.color);*/ - /*pixels = transform_fisheye(pixels, this.graphicsValues.fisheye);*/ - pixels = transform_confetti(pixels, this.graphicsValues.confetti); + if (this.graphicsValues.fisheye) { + imagedata = transform_fisheye(imagedata, this.graphicsValues.fisheye); + } + if (this.graphicsValues.whirl) { + imagedata = transform_whirl(imagedata, this.graphicsValues.whirl); + } + if (this.graphicsValues.pixelate) { + imagedata = transform_pixelate(imagedata, this.graphicsValues.pixelate); + } + if (this.graphicsValues.mosaic) { + imagedata = transform_mosaic(imagedata, this.graphicsValues.mosaic); + } + if (this.graphicsValues.duplicate) { + imagedata = transform_duplicate(imagedata, this.graphicsValues.duplicate); + } + if (this.graphicsValues.color || this.graphicsValues.saturation || this.graphicsValues.brightness) { + imagedata = transform_HSV( + imagedata, + this.graphicsValues.color, + this.graphicsValues.saturation, + this.graphicsValues.brightness + ); + } + if (this.graphicsValues.negative) { + imagedata = transform_negative(imagedata, this.graphicsValues.negative); + } + if (this.graphicsValues.comic) { + imagedata = transform_comic(imagedata, this.graphicsValues.comic); + } + if (this.graphicsValues.confetti) { + imagedata = transform_confetti(imagedata, this.graphicsValues.confetti); + } - //the last object will have all the transformations done on it - newimagedata = ctx.createImageData(imagedata); //make imgdata object - newimagedata.data.set(pixels); //add transformed pixels - ctx.putImageData(newimagedata, 0, 0); + ctx.putImageData(imagedata, 0, 0); } return canvas; @@ -3249,7 +3518,7 @@ SpriteMorph.prototype.bubble = function (data, isThought, isQuestion) { if (data === '' || isNil(data)) {return; } bubble = new SpriteBubbleMorph( data, - stage ? stage.scale : 1, + stage, isThought, isQuestion ); @@ -3311,6 +3580,10 @@ SpriteMorph.prototype.isCorrectingOutsideDrag = function () { }; SpriteMorph.prototype.justDropped = function () { + var stage = this.parentThatIsA(StageMorph); + if (stage) { + stage.enableCustomHatBlocks = true; + } this.restoreLayers(); this.positionTalkBubble(); this.receiveUserInteraction('dropped'); @@ -3350,6 +3623,63 @@ SpriteMorph.prototype.drawLine = function (start, dest) { } }; +SpriteMorph.prototype.floodFill = function () { + if (!this.parent.bounds.containsPoint(this.rotationCenter())) { + return; + } + var layer = normalizeCanvas(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) { @@ -3466,10 +3796,14 @@ SpriteMorph.prototype.setHeading = function (degrees) { turn = dir - this.heading; // apply to myself - this.changed(); - SpriteMorph.uber.setHeading.call(this, dir); - this.silentGotoXY(x, y, true); // just me - this.positionTalkBubble(); + if (this.rotationStyle) { // optimization, only redraw if rotatable + this.changed(); + SpriteMorph.uber.setHeading.call(this, dir); + this.silentGotoXY(x, y, true); // just me + this.positionTalkBubble(); + } else { + this.heading = parseFloat(degrees) % 360; + } // propagate to my parts this.parts.forEach(function (part) { @@ -3539,6 +3873,7 @@ SpriteMorph.prototype.gotoXY = function (x, y, justMe) { newY, dest; + if (!stage) {return; } newX = stage.center().x + (+x || 0) * stage.scale; newY = stage.center().y - (+y || 0) * stage.scale; if (this.costume) { @@ -3623,17 +3958,83 @@ 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 () { - var msgs = []; - this.scripts.allChildren().forEach(function (morph) { - var txt; - if (morph.selector) { - if (contains( - ['receiveMessage', 'doBroadcast', 'doBroadcastAndWait'], - morph.selector - )) { + var msgs = [], + all = this.scripts.children.slice(); + this.customBlocks.forEach(function (def) { + if (def.body) { + all.push(def.body.expression); + } + def.scripts.forEach(function (scr) { + all.push(scr); + }); + }); + if (this.globalBlocks) { + this.globalBlocks.forEach(function (def) { + if (def.body) { + all.push(def.body.expression); + } + def.scripts.forEach(function (scr) { + all.push(scr); + }); + }); + } + all.forEach(function (script) { + script.allChildren().forEach(function (morph) { + var txt; + if (morph.selector && contains( + ['receiveMessage', 'doBroadcast', 'doBroadcastAndWait'], + morph.selector + )) { txt = morph.inputs()[0].evaluate(); if (isString(txt) && txt !== '') { if (!contains(msgs, txt)) { @@ -3641,7 +4042,7 @@ SpriteMorph.prototype.allMessageNames = function () { } } } - } + }); }); return msgs; }; @@ -3673,7 +4074,8 @@ SpriteMorph.prototype.allHatBlocksForKey = function (key) { return this.scripts.children.filter(function (morph) { if (morph.selector) { if (morph.selector === 'receiveKey') { - return morph.inputs()[0].evaluate()[0] === key; + var evt = morph.inputs()[0].evaluate()[0]; + return evt === key || evt === 'any key'; } } return false; @@ -3691,6 +4093,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 () { @@ -4028,7 +4439,12 @@ SpriteMorph.prototype.paletteBlockInstance = function (definition) { ); }; -SpriteMorph.prototype.usesBlockInstance = function (definition) { +SpriteMorph.prototype.usesBlockInstance = function ( + definition, + forRemoval, // optional bool + skipGlobals, // optional bool + skipBlocks // optional array with ignorable definitions +) { var inDefinitions, inScripts = detect( this.scripts.allChildren(), @@ -4039,6 +4455,24 @@ SpriteMorph.prototype.usesBlockInstance = function (definition) { if (inScripts) {return true; } + if (definition.isGlobal && !skipGlobals) { + inDefinitions = []; + this.parentThatIsA(StageMorph).globalBlocks.forEach( + function (def) { + if (forRemoval && (definition === def)) {return; } + if (skipBlocks && contains(skipBlocks, def)) {return; } + if (def.body) { + def.body.expression.allChildren().forEach(function (c) { + if (c.definition && (c.definition === definition)) { + inDefinitions.push(c); + } + }); + } + } + ); + if (inDefinitions.length > 0) {return true; } + } + inDefinitions = []; this.customBlocks.forEach(function (def) { if (def.body) { @@ -4287,7 +4721,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( @@ -4296,6 +4746,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; }; @@ -4329,11 +4784,10 @@ SpriteMorph.prototype.fullThumbnail = function (extentPoint) { // SpriteMorph Boolean visual representation SpriteMorph.prototype.booleanMorph = function (bool) { - // answer a block which can be shown in watchers, speech bubbles etc. - var block = new ReporterBlockMorph(true); - block.color = SpriteMorph.prototype.blockColor.operators; - block.setSpec(localize(bool.toString())); - return block; + var sym = new BooleanSlotMorph(bool); + sym.isStatic = true; + sym.drawNew(); + return sym; }; // SpriteMorph nesting @@ -4633,7 +5087,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 @@ -4649,8 +5102,8 @@ StageMorph.prototype.hiddenPrimitives = {}; StageMorph.prototype.codeMappings = {}; StageMorph.prototype.codeHeaders = {}; StageMorph.prototype.enableCodeMapping = false; - StageMorph.prototype.enableInheritance = false; +StageMorph.prototype.enableSublistIDs = false; // StageMorph instance creation @@ -4670,6 +5123,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(); @@ -4690,17 +5144,19 @@ StageMorph.prototype.init = function (globals) { this.trailsCanvas = null; this.isThreadSafe = false; - this.graphicsValues = { 'negative': 0, - 'fisheye': 0, - 'whirl': 0, - 'pixelate': 0, - 'mosaic': 0, - 'brightness': 0, - 'color': 0, - 'comic': 0, - 'duplicate': 0, - 'confetti': 0 - }; + this.graphicsValues = { + 'color': 0, + 'fisheye': 0, + 'whirl': 0, + 'pixelate': 0, + 'mosaic': 0, + 'duplicate': 0, + 'negative': 0, + 'comic': 0, + 'confetti': 0, + 'saturation': 0, + 'brightness': 0 + }; StageMorph.uber.init.call(this); @@ -4768,6 +5224,7 @@ StageMorph.prototype.drawNew = function () { ); this.image = this.applyGraphicsEffects(this.image); } + this.version = Date.now(); // for observer optimization }; StageMorph.prototype.drawOn = function (aCanvas, aRect) { @@ -4777,10 +5234,10 @@ StageMorph.prototype.drawOn = function (aCanvas, aRect) { return null; } rectangle = aRect || this.bounds; - area = rectangle.intersect(this.bounds).round(); + area = rectangle.intersect(this.bounds); if (area.extent().gt(new Point(0, 0))) { delta = this.position().neg(); - src = area.copy().translateBy(delta).round(); + src = area.copy().translateBy(delta); context = aCanvas.getContext('2d'); context.globalAlpha = this.alpha; @@ -4794,8 +5251,8 @@ StageMorph.prototype.drawOn = function (aCanvas, aRect) { } context.drawImage( this.image, - src.left(), - src.top(), + sl, + st, w, h, area.left(), @@ -4812,8 +5269,8 @@ StageMorph.prototype.drawOn = function (aCanvas, aRect) { try { context.drawImage( this.penTrails(), - src.left() / this.scale, - src.top() / this.scale, + sl / this.scale, + st / this.scale, ws, hs, area.left() / this.scale, @@ -4887,9 +5344,14 @@ StageMorph.prototype.colorFiltered = function (aColor, excludedSprite) { i, dta; - src = img.getContext('2d').getImageData(0, 0, ext.x, ext.y); + src = normalizeCanvas(img, true).getContext('2d').getImageData( + 0, + 0, + ext.x, + ext.y + ); morph.bounds = this.bounds.copy(); - morph.image = newCanvas(ext); + morph.image = newCanvas(ext, true); ctx = morph.image.getContext('2d'); dta = ctx.createImageData(ext.x, ext.y); for (i = 0; i < ext.x * ext.y * 4; i += 4) { @@ -4960,7 +5422,7 @@ StageMorph.prototype.getLastMessage = function () { return this.lastMessage || ''; }; -// StageMorph Mouse Corridnates +// StageMorph Mouse Coordinates StageMorph.prototype.reportMouseX = function () { var world = this.world(); @@ -5000,7 +5462,7 @@ StageMorph.prototype.reactToDropOf = function (morph, hand) { // StageMorph stepping StageMorph.prototype.step = function () { - var current, elapsed, leftover, world = this.world(); + var current, elapsed, leftover, ide, world = this.world(); // handle keyboard events if (world.keyboardReceiver === null) { @@ -5011,6 +5473,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) { @@ -5033,6 +5498,14 @@ StageMorph.prototype.step = function () { this.changed(); } else { this.threads.step(); + + // single-stepping hook: + if (this.threads.wantsToPause) { + ide = this.parentThatIsA(IDE_Morph); + if (ide) { + ide.controlBar.pauseButton.refresh(); + } + } } // update watchers @@ -5047,6 +5520,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); @@ -5154,7 +5649,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)); } }); @@ -5182,7 +5677,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__')); } }); @@ -5225,6 +5720,8 @@ StageMorph.prototype.removeAllClones = function () { ); clones.forEach(function (clone) { myself.threads.stopAllForReceiver(clone); + clone.detachFromAnchor(); + clone.corpsify(); clone.destroy(); }); this.cloneCount = 0; @@ -5405,6 +5902,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')); @@ -5472,7 +5970,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')); @@ -5525,9 +6028,7 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push(block('reportAnd')); blocks.push(block('reportOr')); blocks.push(block('reportNot')); - blocks.push('-'); - blocks.push(block('reportTrue')); - blocks.push(block('reportFalse')); + blocks.push(block('reportBoolean')); blocks.push('-'); blocks.push(block('reportJoinWords')); blocks.push(block('reportTextSplit')); @@ -5642,6 +6143,7 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push(block('reportMap')); blocks.push('-'); blocks.push(block('doForEach')); + blocks.push(block('doShowTable')); } ///////////////////////////////// @@ -5712,7 +6214,11 @@ StageMorph.prototype.userMenu = function () { menu.addItem( "pic...", function () { - window.open(myself.fullImageClassic().toDataURL()); + ide.saveCanvasAs( + myself.fullImageClassic(), + myself.name, + true // open as new window + ); }, 'open a new window\nwith a picture of the stage' ); @@ -5740,9 +6246,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(); } + } }); }; @@ -5959,6 +6472,9 @@ StageMorph.prototype.allHatBlocksForKey StageMorph.prototype.allHatBlocksForInteraction = SpriteMorph.prototype.allHatBlocksForInteraction; +StageMorph.prototype.allGenericHatBlocks + = SpriteMorph.prototype.allGenericHatBlocks; + // StageMorph events StageMorph.prototype.mouseClickLeft @@ -6030,18 +6546,19 @@ SpriteBubbleMorph.uber = SpeechBubbleMorph.prototype; // SpriteBubbleMorph instance creation: -function SpriteBubbleMorph(data, scale, isThought, isQuestion) { - this.init(data, scale, isThought, isQuestion); +function SpriteBubbleMorph(data, stage, isThought, isQuestion) { + this.init(data, stage, isThought, isQuestion); } SpriteBubbleMorph.prototype.init = function ( data, - scale, + stage, isThought, isQuestion ) { var sprite = SpriteMorph.prototype; - this.scale = scale || 1; + this.stage = stage; + this.scale = stage ? stage.scale : 1; this.data = data; this.isQuestion = isQuestion; @@ -6059,16 +6576,33 @@ SpriteBubbleMorph.prototype.init = function ( // SpriteBubbleMorph contents formatting -SpriteBubbleMorph.prototype.dataAsMorph = function (data) { +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( @@ -6097,10 +6631,30 @@ SpriteBubbleMorph.prototype.dataAsMorph = function (data) { contents.silentSetHeight(data.height); contents.image = data; } else if (data instanceof List) { - contents = new ListWatcherMorph(data); + 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( + -2 * (this.edge + this.border + this.padding) + )); + } + } else { + contents = new ListWatcherMorph(data); + contents.update(true); + contents.step = contents.update; + if (this.stage) { + contents.expand(this.stage.extent().translateBy( + -2 * (this.edge + this.border + this.padding) + )); + } + } contents.isDraggable = false; - contents.update(true); - contents.step = contents.update; } else if (data instanceof Context) { img = data.image(); contents = new Morph(); @@ -6154,7 +6708,7 @@ SpriteBubbleMorph.prototype.setScale = function (scale) { // SpriteBubbleMorph drawing: -SpriteBubbleMorph.prototype.drawNew = function () { +SpriteBubbleMorph.prototype.drawNew = function (toggle) { var sprite = SpriteMorph.prototype; // scale my settings @@ -6166,7 +6720,7 @@ SpriteBubbleMorph.prototype.drawNew = function () { if (this.contentsMorph) { this.contentsMorph.destroy(); } - this.contentsMorph = this.dataAsMorph(this.data); + this.contentsMorph = this.dataAsMorph(this.data, toggle); this.add(this.contentsMorph); // adjust my layout @@ -6236,7 +6790,8 @@ SpriteBubbleMorph.prototype.fixLayout = function () { // Costume instance creation function Costume(canvas, name, rotationCenter) { - this.contents = canvas || newCanvas(); + this.contents = canvas ? normalizeCanvas(canvas, true) + : newCanvas(null, true); this.shrinkToFit(this.maxExtent()); this.name = name || null; this.rotationCenter = rotationCenter || this.center(); @@ -6280,7 +6835,7 @@ Costume.prototype.shrinkWrap = function () { // adjust my contents' bounds to my visible bounding box var bb = this.boundingBox(), ext = bb.extent(), - pic = newCanvas(ext), + pic = newCanvas(ext, true), ctx = pic.getContext('2d'); ctx.drawImage( @@ -6299,11 +6854,10 @@ Costume.prototype.shrinkWrap = function () { this.version = Date.now(); }; -Costume.prototype.boundingBox = function () { +Costume.prototype.canvasBoundingBox = function (pic) { // answer the rectangle surrounding my contents' non-transparent pixels var row, col, - pic = this.contents, w = pic.width, h = pic.height, ctx = pic.getContext('2d'), @@ -6360,13 +6914,16 @@ Costume.prototype.boundingBox = function () { return new Rectangle(getLeft(), getTop(), getRight(), getBottom()); }; +Costume.prototype.boundingBox = function () { + return this.canvasBoundingBox(this.contents); +}; + // Costume duplication Costume.prototype.copy = function () { - var canvas = newCanvas(this.extent()), + var canvas = newCanvas(this.extent(), true), cpy, ctx; - ctx = canvas.getContext('2d'); ctx.drawImage(this.contents, 0, 0); cpy = new Costume(canvas, this.name ? copy(this.name) : null); @@ -6382,7 +6939,7 @@ Costume.prototype.flipped = function () { (mirrored along a vertical axis), used for SpriteMorph's rotation style type 2 */ - var canvas = newCanvas(this.extent()), + var canvas = newCanvas(this.extent(), true), ctx = canvas.getContext('2d'), flipped; @@ -6408,21 +6965,21 @@ Costume.prototype.edit = function (aWorld, anIDE, isnew, oncancel, onsubmit) { editor.openIn( aWorld, isnew ? - newCanvas(StageMorph.prototype.dimensions) : + newCanvas(StageMorph.prototype.dimensions, true) : this.contents, isnew ? - new Point(240, 180) : + null : this.rotationCenter, function (img, rc) { myself.contents = img; myself.rotationCenter = rc; - if (anIDE.currentSprite instanceof SpriteMorph) { - // don't shrinkwrap stage costumes - myself.shrinkWrap(); - } myself.version = Date.now(); aWorld.changed(); if (anIDE) { + if (anIDE.currentSprite instanceof SpriteMorph) { + // don't shrinkwrap stage costumes + myself.shrinkWrap(); + } anIDE.currentSprite.wearCostume(myself); anIDE.hasChangedMedia = true; } @@ -6834,6 +7391,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(); }; @@ -6883,26 +7441,48 @@ CellMorph.prototype.fixLayout = function () { // CellMorph drawing: -CellMorph.prototype.drawNew = function () { +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, img, fontSize = SyntaxElementMorph.prototype.fontSize, isSameList = this.contentsMorph instanceof ListWatcherMorph - && (this.contentsMorph.list === this.contents); + && (this.contentsMorph.list === this.contents), + isSameTable = this.contentsMorph instanceof TableFrameMorph + && (this.contentsMorph.tableMorph.table === this.contents); if (this.isBig) { fontSize = fontSize * 1.5; } // re-build my contents - if (this.contentsMorph && !isSameList) { + if (toggle || (this.contentsMorph && !isSameList && !isSameTable)) { this.contentsMorph.destroy(); + this.version = null; } - if (!isSameList) { + 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; @@ -6946,23 +7526,31 @@ CellMorph.prototype.drawNew = function () { this.contentsMorph.silentSetHeight(img.height); this.contentsMorph.image = img; } else if (this.contents instanceof List) { - if (this.isCircular()) { - this.contentsMorph = new TextMorph( - '(...)', - fontSize, - null, - false, // bold - true, // italic - 'center' - ); - this.contentsMorph.setColor(new Color(255, 255, 255)); - } else { - this.contentsMorph = new ListWatcherMorph( + if ('table' === type || (!toggle && this.contents.isTable())) { + this.contentsMorph = new TableFrameMorph(new TableMorph( this.contents, - this - ); - this.contentsMorph.isDraggable = false; + 10 + )); + this.contentsMorph.expand(new Point(200, 150)); + } else { + if (this.isCircular()) { + this.contentsMorph = new TextMorph( + '(...)', + fontSize, + null, + false, // bold + true, // italic + 'center' + ); + this.contentsMorph.setColor(new Color(255, 255, 255)); + } else { + this.contentsMorph = new ListWatcherMorph( + this.contents, + this + ); + } } + this.contentsMorph.isDraggable = false; } else { this.contentsMorph = new TextMorph( !isNil(this.contents) ? this.contents.toString() : '', @@ -7024,7 +7612,7 @@ CellMorph.prototype.drawNew = function () { } // position my contents - if (!isSameList) { + if (toggle || (!isSameList && !isSameTable)) { this.contentsMorph.setCenter(this.center()); } }; @@ -7138,6 +7726,15 @@ CellMorph.prototype.mouseClickLeft = function (pos) { } }; +CellMorph.prototype.mouseDoubleClick = function (pos) { + if (List.prototype.enableTables && + this.currentValue instanceof List) { + new TableDialogMorph(this.contents).popUp(this.world()); + } else { + this.escalateEvent('mouseDoubleClick', pos); + } +}; + // WatcherMorph ////////////////////////////////////////////////////////// /* @@ -7296,6 +7893,8 @@ WatcherMorph.prototype.update = function () { } if (this.cellMorph.contentsMorph instanceof ListWatcherMorph) { this.cellMorph.contentsMorph.update(); + } else if (isSnapObject(this.cellMorph.contents)) { + this.cellMorph.update(); } }; @@ -7433,6 +8032,15 @@ WatcherMorph.prototype.fixLayout = function () { // WatcherMorph events: +WatcherMorph.prototype.mouseDoubleClick = function (pos) { + if (List.prototype.enableTables && + this.currentValue instanceof List) { + new TableDialogMorph(this.currentValue).popUp(this.world()); + } else { + this.escalateEvent('mouseDoubleClick', pos); + } +}; + /* // Scratch-like watcher-toggling, commented out b/c we have a drop-down menu @@ -7590,9 +8198,11 @@ WatcherMorph.prototype.userMenu = function () { menu.addItem( 'export...', function () { - window.open( - 'data:text/plain;charset=utf-8,' + - encodeURIComponent(this.currentValue.toString()) + var ide = myself.parentThatIsA(IDE_Morph); + ide.saveFileAs( + myself.currentValue.toString(), + 'text/plain;charset=utf-8', + myself.getter // variable name ); } ); diff --git a/optimizations.txt b/optimizations.txt new file mode 100755 index 00000000..77e7ef13 --- /dev/null +++ b/optimizations.txt @@ -0,0 +1,18 @@ +Ideas for optimizing Snap! +-------------------------- + + +Graphics engine +--------------- + +* reuse (and cache) icons, e.g. for input slot types, in the prototype. Make sure to update when changing display settings (flat design, zoom blocks etc.) + +* generate state-images for buttons (push buttons, toggles etc.) just-in-time (and possibly cache them only then), instead of always creating them up-front. + + +Evaluator +---------- + +* Get rid of blockSequences, instead evaluate blocks directly. +* Get rid of modifying blocks to evaluate them (solve implicit parameter bindings another way) +* Cache variables (not values) in accessor-blocks (this should speed things up a lot) \ No newline at end of file diff --git a/paint.js b/paint.js index 6d9b09c8..b116c105 100644 --- a/paint.js +++ b/paint.js @@ -5,7 +5,7 @@ inspired by the Scratch paint editor. written by Kartik Chandra - Copyright (C) 2015 by Kartik Chandra + Copyright (C) 2016 by Kartik Chandra This file is part of Snap!. @@ -57,19 +57,23 @@ June 4 - tweaks (Jens) Aug 24 - floodfill alpha-integer issue (Kartik) Sep 29 - tweaks (Jens) - */ + Sep 28 [of the following year :)] - Try to prevent antialiasing (Kartik) + 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) + May 10 - retina display support adjustments (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 = '2015-June-25'; +modules.paint = '2016-July-14'; // Declarations @@ -249,7 +253,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 () { @@ -264,9 +267,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 @@ -305,6 +309,7 @@ PaintEditorMorph.prototype.refreshToolButtons = function () { }; PaintEditorMorph.prototype.ok = function () { + this.paper.updateAutomaticCenter(); this.callback( this.paper.paper, this.paper.rotationCenter @@ -568,9 +573,9 @@ PaintCanvasMorph.prototype.init = function (shift) { this.dragRect = new Rectangle(); // rectangle with origin being the starting drag position and // corner being the current drag position - this.mask = newCanvas(this.extent()); // Temporary canvas - this.paper = newCanvas(this.extent()); // Actual canvas - this.erasermask = newCanvas(this.extent()); // eraser memory + this.mask = newCanvas(this.extent(), true); // Temporary canvas + this.paper = newCanvas(this.extent(), true); // Actual canvas + this.erasermask = newCanvas(this.extent(), true); // eraser memory this.background = newCanvas(this.extent()); // checkers this.settings = { "primarycolor": new Color(0, 0, 0, 255), // usually fill color @@ -583,12 +588,38 @@ PaintCanvasMorph.prototype.init = function (shift) { var key = this.world().currentKey; return (key === 16); }; + // should we calculate the center of the image ourselves, + // or use the user position + this.automaticCrosshairs = true; + this.noticesTransparentClick = true; // optimization this.buildContents(); }; +// Calculate the center of all the non-transparent pixels on the canvas. +PaintCanvasMorph.prototype.calculateCanvasCenter = function(canvas) { + var canvasBounds = Costume.prototype.canvasBoundingBox(canvas); + if (canvasBounds === null) { + return null; + } + // Can't use canvasBounds.center(), it rounds down. + return new Point((canvasBounds.origin.x + canvasBounds.corner.x) / 2, (canvasBounds.origin.y + canvasBounds.corner.y) / 2); +}; + +// If we are in automaticCrosshairs mode, recalculate the rotationCenter. +PaintCanvasMorph.prototype.updateAutomaticCenter = function () { + if (this.automaticCrosshairs) { + // Calculate this.rotationCenter from this.paper + var rotationCenter = this.calculateCanvasCenter(this.paper); + if (rotationCenter !== null) { + this.rotationCenter = rotationCenter; + } + } +}; + PaintCanvasMorph.prototype.scale = function (x, y) { - this.mask = newCanvas(this.extent()); - var c = newCanvas(this.extent()); + this.updateAutomaticCenter(); + this.mask = newCanvas(this.extent(), true); + var c = newCanvas(this.extent(), true); c.getContext("2d").save(); c.getContext("2d").translate( this.rotationCenter.x, @@ -607,14 +638,14 @@ PaintCanvasMorph.prototype.scale = function (x, y) { }; PaintCanvasMorph.prototype.cacheUndo = function () { - var cachecan = newCanvas(this.extent()); + var cachecan = newCanvas(this.extent(), true); this.merge(this.paper, cachecan); this.undoBuffer.push(cachecan); }; PaintCanvasMorph.prototype.undo = function () { if (this.undoBuffer.length > 0) { - this.paper = newCanvas(this.extent()); + this.paper = newCanvas(this.extent(), true); this.mask.width = this.mask.width + 1 - 1; this.merge(this.undoBuffer.pop(), this.paper); this.drawNew(); @@ -641,8 +672,9 @@ PaintCanvasMorph.prototype.clearCanvas = function () { }; PaintCanvasMorph.prototype.toolChanged = function (tool) { - this.mask = newCanvas(this.extent()); + this.mask = newCanvas(this.extent(), true); if (tool === "crosshairs") { + this.updateAutomaticCenter(); this.drawcrosshair(); } this.drawNew(); @@ -781,7 +813,7 @@ PaintCanvasMorph.prototype.mouseDownLeft = function (pos) { } if (this.settings.primarycolor === "transparent" && this.currentTool !== "crosshairs") { - this.erasermask = newCanvas(this.extent()); + this.erasermask = newCanvas(this.extent(), true); this.merge(this.paper, this.erasermask); } }; @@ -906,6 +938,8 @@ PaintCanvasMorph.prototype.mouseMove = function (pos) { } break; case "crosshairs": + // Disable automatic crosshairs: user has now chosen where they should be. + this.automaticCrosshairs = false; this.rotationCenter = relpos.copy(); this.drawcrosshair(mctx); break; @@ -920,7 +954,7 @@ PaintCanvasMorph.prototype.mouseMove = function (pos) { } mctx.stroke(); mctx.restore(); - this.paper = newCanvas(this.extent()); + this.paper = newCanvas(this.extent(), true); this.merge(this.mask, this.paper); break; default: @@ -944,9 +978,9 @@ PaintCanvasMorph.prototype.mouseLeaveDragging PaintCanvasMorph.prototype.buildContents = function () { this.background = newCanvas(this.extent()); - this.paper = newCanvas(this.extent()); - this.mask = newCanvas(this.extent()); - this.erasermask = newCanvas(this.extent()); + this.paper = newCanvas(this.extent(), true); + this.mask = newCanvas(this.extent(), true); + this.erasermask = newCanvas(this.extent(), true); var i, j, bkctx = this.background.getContext("2d"); for (i = 0; i < this.background.width; i += 5) { for (j = 0; j < this.background.height; j += 5) { @@ -961,7 +995,7 @@ PaintCanvasMorph.prototype.buildContents = function () { }; PaintCanvasMorph.prototype.drawNew = function () { - var can = newCanvas(this.extent()); + var can = newCanvas(this.extent(), true); this.merge(this.background, can); this.merge(this.paper, can); this.merge(this.mask, can); diff --git a/snap.html b/snap.html index 976bcd63..3aadab53 100755 --- a/snap.html +++ b/snap.html @@ -13,20 +13,23 @@ + + diff --git a/snap_fast.html b/snap_fast.html new file mode 100755 index 00000000..c3d68dfd --- /dev/null +++ b/snap_fast.html @@ -0,0 +1,39 @@ + + + + + Snap! Build Your Own Blocks + + + + + + + + + + + + + + + + + + + + + + + diff --git a/stitchcode/gui.js b/stitchcode/gui.js new file mode 100644 index 00000000..16be6001 --- /dev/null +++ b/stitchcode/gui.js @@ -0,0 +1,2336 @@ + +// Force flat design +IDE_Morph.prototype.setDefaultDesign = IDE_Morph.prototype.setFlatDesign; + +// ad some padding +IDE_Morph.prototype.originalInit = IDE_Morph.prototype.init; +IDE_Morph.prototype.init = function(isAutoFill) { + this.originalInit(); + // Borders are actually just paddings, so we set the bg white to get them to be white + //this.backgroundColor = new Color(255,255,255); + //this.setColor(this.backgroundColor); + this.padding = 1; + //this.frameColor = new Color(220,220,220); +}; + +// change logo +IDE_Morph.prototype.originalCreateLogo = IDE_Morph.prototype.createLogo; +IDE_Morph.prototype.createLogo = function () { + this.originalCreateLogo(); + if (MorphicPreferences.isFlat) { + this.logo.texture = 'stitchcode/stitchcode_logo_small.png'; + } else { + this.logo.texture = 'stitchcode/stitchcode_logo_small_black.png'; + } + this.logo.color = new Color(230, 230, 230); + this.logo.drawNew(); +}; + + +// Single Sprite mode, no corral and no tabs in the scripting area +IDE_Morph.prototype.createCorralBar = nop; +IDE_Morph.prototype.createCorral = nop; + +// build panes (do not add all) +IDE_Morph.prototype.buildPanes = function () { + this.createLogo(); + this.createControlBar(); + this.createCategories(); + this.createPalette(); + this.createStage(); + this.createSpriteBar(); + this.createSpriteEditor(); + //this.createCorralBar(); + //this.createCorral(); + this.createStatusDisplay(); + this.createStageHandle(); + this.createPaletteHandle(); +}; + + +// Create contol bar - (and add custom buttons) +IDE_Morph.prototype.createControlBar = function () { + // assumes the logo has already been created + var padding = 4, + button, + stopButton, + pauseButton, + startButton, + projectButton, + settingsButton, + stageSizeButton, + //largeStageSizeButton, + appModeButton, + cloudButton, + upstitchButton, + x, + colors = [ + this.groupColor, + this.frameColor.darker(50), + this.frameColor.darker(50) + ], + myself = this; + + if (this.controlBar) { + this.controlBar.destroy(); + } + + this.controlBar = new Morph(); + this.controlBar.color = this.frameColor; + this.controlBar.color = new Color(250, 250, 250); + this.controlBar.setHeight(this.logo.height()); // height is fixed + this.controlBar.mouseClickLeft = function () { + this.world().fillPage(); + }; + this.add(this.controlBar); + + //smallStageButton + button = new ToggleButtonMorph( + null, //colors, + myself, // the IDE is the target + 'toggleStageSize', + [ + new SymbolMorph('smallStage', 14), + new SymbolMorph('normalStage', 14) + ], + function () { // query + return myself.isSmallStage; + } + ); + + button.corner = 12; + button.color = colors[0]; + button.highlightColor = colors[1]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = this.buttonLabelColor; + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'stage size\nsmall & normal'; + button.fixLayout(); + button.refresh(); + stageSizeButton = button; + this.controlBar.add(stageSizeButton); + this.controlBar.stageSizeButton = button; // for refreshing + + //appModeButton + button = new ToggleButtonMorph( + null, //colors, + myself, // the IDE is the target + 'toggleAppMode', + [ + new SymbolMorph('fullScreen', 14), + new SymbolMorph('normalScreen', 14) + ], + function () { // query + return myself.isAppMode; + } + ); + + button.corner = 12; + button.color = colors[0]; + button.highlightColor = colors[1]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = this.buttonLabelColor; + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'app & edit\nmodes'; + button.fixLayout(); + button.refresh(); + appModeButton = button; + this.controlBar.add(appModeButton); + this.controlBar.appModeButton = appModeButton; // for refreshing + + // upload StitchButton + button = new PushButtonMorph( + this, + 'uploadMe', + new SymbolMorph('arrowUp', 14) + ); + button.corner = 12; + button.color = colors[0]; + button.highlightColor = colors[1]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = this.buttonLabelColor; + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'stop\nevery-\nthing'; + button.fixLayout(); + upstitchButton = button; + this.controlBar.add(upstitchButton); + + + // stopButton + button = new ToggleButtonMorph( + null, // colors + this, // the IDE is the target + 'stopAllScripts', + [ + 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]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = new Color(200, 0, 0); + button.contrast = this.buttonContrast; + 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 + 'togglePauseResume', + [ + new SymbolMorph('pause', 12), + new SymbolMorph('pointRight', 14) + ], + function () { // query + return myself.isPaused(); + } + ); + + button.corner = 12; + button.color = colors[0]; + button.highlightColor = colors[1]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = new Color(255, 220, 0); + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'pause/resume\nall scripts'; + button.fixLayout(); + button.refresh(); + pauseButton = button; + this.controlBar.add(pauseButton); + this.controlBar.pauseButton = pauseButton; // for refreshing + + // startButton + button = new PushButtonMorph( + this, + 'pressStart', + new SymbolMorph('flag', 14) + ); + button.corner = 12; + button.color = colors[0]; + button.highlightColor = colors[1]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = new Color(0, 200, 0); + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'start green\nflag scripts'; + button.fixLayout(); + startButton = button; + this.controlBar.add(startButton); + this.controlBar.startButton = startButton; + + // steppingSlider + slider = new SliderMorph( + 61, + 1, + Process.prototype.flashTime * 100 + 1, + 6, + 'horizontal' + ); + slider.action = function (num) { + Process.prototype.flashTime = (num - 1) / 100; + myself.controlBar.refreshResumeSymbol(); + }; + slider.alpha = MorphicPreferences.isFlat ? 0.1 : 0.3; + slider.setExtent(new Point(50, 14)); + this.controlBar.add(slider); + this.controlBar.steppingSlider = slider; + + // projectButton + button = new PushButtonMorph( + this, + 'projectMenu', + new SymbolMorph('file', 14) + //'\u270E' + ); + button.corner = 12; + button.color = colors[0]; + button.highlightColor = colors[1]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = this.buttonLabelColor; + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'open, save, & annotate project'; + button.fixLayout(); + projectButton = button; + this.controlBar.add(projectButton); + this.controlBar.projectButton = projectButton; // for menu positioning + + // settingsButton + button = new PushButtonMorph( + this, + 'settingsMenu', + new SymbolMorph('gears', 14) + //'\u2699' + ); + button.corner = 12; + button.color = colors[0]; + button.highlightColor = colors[1]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = this.buttonLabelColor; + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'edit settings'; + button.fixLayout(); + settingsButton = button; + this.controlBar.add(settingsButton); + this.controlBar.settingsButton = settingsButton; // for menu positioning + + // cloudButton + button = new PushButtonMorph( + this, + 'cloudMenu', + new SymbolMorph('cloud', 11) + ); + button.corner = 12; + button.color = colors[0]; + button.highlightColor = colors[1]; + button.pressColor = colors[2]; + button.labelMinExtent = new Point(36, 18); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = this.buttonLabelColor; + button.contrast = this.buttonContrast; + button.drawNew(); + // button.hint = 'cloud operations'; + button.fixLayout(); + cloudButton = button; + this.controlBar.add(cloudButton); + this.controlBar.cloudButton = cloudButton; // for menu positioning + + this.controlBar.fixLayout = function () { + x = this.right() - padding; + [stopButton, pauseButton, startButton].forEach( + function (button) { + button.setCenter(myself.controlBar.center()); + button.setRight(x); + x -= button.width(); + x -= padding; + } + ); + + x = Math.min( + startButton.left() - (3 * padding + 2 * stageSizeButton.width()), + myself.right() - StageMorph.prototype.dimensions.x * + (myself.isSmallStage ? myself.stageRatio : 1) + ); + [upstitchButton, stageSizeButton, appModeButton].forEach( + function (button) { + x += padding; + button.setCenter(myself.controlBar.center()); + button.setLeft(x); + x += button.width(); + } + ); + + slider.setCenter(myself.controlBar.center()); + slider.setRight(stageSizeButton.left() - padding); + + settingsButton.setCenter(myself.controlBar.center()); + settingsButton.setLeft(this.left()); + + cloudButton.setCenter(myself.controlBar.center()); + cloudButton.setRight(settingsButton.left() - padding); + + projectButton.setCenter(myself.controlBar.center()); + projectButton.setRight(cloudButton.left() - padding); + + this.refreshSlider(); + this.updateLabel(); + }; + + this.controlBar.refreshSlider = function () { + if (Process.prototype.enableSingleStepping && !myself.isAppMode) { + slider.drawNew(); + slider.show(); + } else { + slider.hide(); + } + this.refreshResumeSymbol(); + }; + + this.controlBar.refreshResumeSymbol = function () { + var pauseSymbols; + if (Process.prototype.enableSingleStepping && + Process.prototype.flashTime > 0.5) { + myself.stage.threads.pauseAll(myself.stage); + pauseSymbols = [ + new SymbolMorph('pause', 12), + new SymbolMorph('stepForward', 14) + ]; + } else { + pauseSymbols = [ + new SymbolMorph('pause', 12), + new SymbolMorph('pointRight', 14) + ]; + } + pauseButton.labelString = pauseSymbols; + pauseButton.createLabel(); + pauseButton.fixLayout(); + pauseButton.refresh(); + }; + + this.controlBar.updateLabel = function () { + var suffix = myself.world().isDevMode ? + ' - ' + localize('development mode') : ''; + + if (this.label) { + this.label.destroy(); + } + if (myself.isAppMode) { + return; + } + + this.label = new StringMorph( + (myself.projectName || localize('untitled')) + suffix, + 14, + 'sans-serif', + true, + false, + false, + MorphicPreferences.isFlat ? null : new Point(2, 1), + myself.frameColor.darker(myself.buttonContrast) + ); + this.label.color = myself.buttonLabelColor; + this.label.drawNew(); + this.add(this.label); + this.label.setCenter(this.center()); + this.label.setLeft(this.settingsButton.right() + padding); + }; +}; + +// Get Example Projet list + +ProjectDialogMorph.prototype.getExamplesProjectList = function () { + var dir, + projects = []; + + //dir = this.ide.getURL('http://snap.berkeley.edu/snapsource/Examples/'); + dir = this.ide.getURL(tStitch.getBaseURL() + '/stitchcode/examples/'); + dir.split('\n').forEach( + function (line) { + var startIdx = line.search(new RegExp('href=".*xml"')), + endIdx, + name, + dta; + if (startIdx > 0) { + endIdx = line.search(new RegExp('.xml')); + name = line.substring(startIdx + 6, endIdx); + dta = { + name: name, + thumb: null, + notes: null + }; + projects.push(dta); + console.log(dta); + } + } + ); + projects.sort(function (x, y) { + return x.name < y.name ? -1 : 1; + }); + return projects; +}; + + +// Get Source +ProjectDialogMorph.prototype.setSource = function (source) { + var myself = this, + msg; + + this.source = source; //this.task === 'save' ? 'local' : source; + this.srcBar.children.forEach(function (button) { + button.refresh(); + }); + switch (this.source) { + case 'cloud': + msg = myself.ide.showMessage('Updating\nproject list...'); + this.projectList = []; + SnapCloud.getProjectList( + function (projectList) { + myself.installCloudProjectList(projectList); + msg.destroy(); + }, + function (err, lbl) { + msg.destroy(); + myself.ide.cloudError().call(null, err, lbl); + } + ); + return; + case 'examples': + this.projectList = this.getExamplesProjectList(); + break; + case 'local': + this.projectList = this.getLocalProjectList(); + break; + } + + this.listField.destroy(); + this.listField = new ListMorph( + this.projectList, + this.projectList.length > 0 ? + function (element) { + return element.name; + } : null, + null, + function () {myself.ok(); } + ); + + this.fixListFieldItemColors(); + this.listField.fixLayout = nop; + this.listField.edge = InputFieldMorph.prototype.edge; + this.listField.fontSize = InputFieldMorph.prototype.fontSize; + this.listField.typeInPadding = InputFieldMorph.prototype.typeInPadding; + this.listField.contrast = InputFieldMorph.prototype.contrast; + this.listField.drawNew = InputFieldMorph.prototype.drawNew; + this.listField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + + if (this.source === 'local') { + this.listField.action = function (item) { + var src, xml; + + if (item === undefined) {return; } + if (myself.nameField) { + myself.nameField.setContents(item.name || ''); + } + if (myself.task === 'open') { + + src = localStorage['-snap-project-' + item.name]; + xml = myself.ide.serializer.parse(src); + + myself.notesText.text = xml.childNamed('notes').contents + || ''; + myself.notesText.drawNew(); + myself.notesField.contents.adjustBounds(); + myself.preview.texture = xml.childNamed('thumbnail').contents + || null; + myself.preview.cachedTexture = null; + myself.preview.drawNew(); + } + myself.edit(); + }; + } else { // 'examples', 'cloud' is initialized elsewhere + this.listField.action = function (item) { + var src, xml; + if (item === undefined) {return; } + if (myself.nameField) { + myself.nameField.setContents(item.name || ''); + } + src = myself.ide.getURL( + // 'http://snap.berkeley.edu/snapsource/Examples/' + + tStitch.getBaseURL() + 'stitchcode/examples/' + + item.name + '.xml' + ); + + xml = myself.ide.serializer.parse(src); + myself.notesText.text = xml.childNamed('notes').contents + || ''; + myself.notesText.drawNew(); + myself.notesField.contents.adjustBounds(); + myself.preview.texture = xml.childNamed('thumbnail').contents + || null; + myself.preview.cachedTexture = null; + myself.preview.drawNew(); + myself.edit(); + }; + } + this.body.add(this.listField); + this.shareButton.hide(); + this.unshareButton.hide(); + if (this.source === 'local') { + this.deleteButton.show(); + } else { // examples + this.deleteButton.hide(); + } + this.buttons.fixLayout(); + this.fixLayout(); + if (this.task === 'open') { + this.clearDetails(); + } +}; + +// open project +ProjectDialogMorph.prototype.openProject = function () { + var proj = this.listField.selected, src; + if (!proj) {return; } + this.ide.source = this.source; + if (this.source === 'cloud') { + this.openCloudProject(proj); + } else if (this.source === 'examples') { + src = this.ide.getURL(tStitch.getBaseURL() + 'stitchcode/examples/' + proj.name + '.xml'); + this.ide.openProjectString(src); + this.destroy(); + } else { // 'local' + this.ide.openProject(proj.name); + this.destroy(); + } +}; + +IDE_Morph.prototype.toggleAppMode = function (appMode) { + var world = this.world(), + elements = [ + this.logo, + this.controlBar.projectButton, + this.controlBar.cloudButton, + this.controlBar.settingsButton, + this.controlBar.stageSizeButton, + //this.controlBar.largeStageSizeButton, + this.spriteEditor, + //this.paletteHandle, + this.stageHandle, + this.palette, + this.categories ]; + + this.isAppMode = isNil(appMode) ? !this.isAppMode : appMode; + + Morph.prototype.trackChanges = false; + if (this.isAppMode) { + this.setColor(this.appModeColor); + this.controlBar.setColor(this.color); + this.controlBar.appModeButton.refresh(); + elements.forEach(function (e) { + e.hide(); + }); + world.children.forEach(function (morph) { + if (morph instanceof DialogBoxMorph) { + morph.hide(); + } + }); + } else { + this.setColor(this.backgroundColor); + this.controlBar.setColor(this.frameColor); + elements.forEach(function (e) { + e.show(); + }); + this.stage.setScale(1); + // show all hidden dialogs + world.children.forEach(function (morph) { + if (morph instanceof DialogBoxMorph) { + morph.show(); + } + }); + // prevent scrollbars from showing when morph appears + world.allChildren().filter(function (c) { + return c instanceof ScrollFrameMorph; + }).forEach(function (s) { + s.adjustScrollBars(); + }); + } + this.setExtent(this.world().extent()); // resume trackChanges +}; + +// create status display (inspired by beetleblocks) +IDE_Morph.prototype.createStatusDisplay = function () { + var frame, + padding = 1, + myself = this, + elements = [], + beetle = this.currentSprite.beetle, + stage = this.stage; + + if (this.statusDisplay) { + this.statusDisplay.destroy(); + } + + this.statusDisplay = new Morph(); + this.statusDisplay.color = this.groupColor; + this.add(this.statusDisplay); + + frame = new ScrollFrameMorph(null, null, this.sliderColor); + frame.acceptsDrops = false; + frame.contents.acceptsDrops = false; + + frame.alpha = 0; + + this.statusDisplay.frame = frame; + this.statusDisplay.add(frame); + + this.statusDisplay.fixLayout = function () { + this.setLeft(myself.stage.left()); + this.setTop(myself.stage.bottom() + padding); + this.setWidth(myself.stage.width()); + this.setHeight(myself.height() - myself.stage.height() - myself.controlBar.height() - padding); + this.frame.setExtent(this.extent()); + this.arrangeContents(); + this.refresh(); + }; + + this.statusDisplay.arrangeContents = function () { + var x = this.left() + padding, + y = this.top() + padding, + max = this.right() - padding, + start = x, + middle = (max - start) / 2 + start; + + this.frame.contents.children.forEach(function (element) { + element.setPosition(new Point(x, y)); + x += element.width(); + + if (element instanceof ToggleMorph) + { x+= element.label.width() + 2; } + + if (element.newLines) { + y += 14 * element.newLines; + x = start; + } + + if (element.newColumn) { + if (element.columns) { + x = ((max - start) / element.columns) * element.newColumn + start; + } else { + x = middle; + } + } + }); + + this.frame.contents.adjustBounds(); + }; + + this.statusDisplay.addElement = function (element) { + + if (typeof element == 'string') { + element = new StringMorph(localize(element), 12, null, true); + } + + this.frame.contents.add(element); + this.fixLayout(); + }; + + this.statusDisplay.refresh = function () { + this.frame.contents.children.forEach(function (element) { + if (element.hasOwnProperty('update')) { + element.update(); + element.changed(); + element.drawNew(); + element.changed(); + } + }); + }; + + this.statusDisplay.acceptsDrops = function () { + return false; + }; + + this.statusDisplay.watchers = function (leftPos) { + /* answer an array of all currently visible watchers. + If leftPos is specified, filter the list for all + shown or hidden watchers whose left side equals + the given border (for automatic positioning) */ + + return this.children.filter(function (morph) { + if (morph instanceof WatcherMorph) { + if (leftPos) { + return morph.left() === leftPos; + } + return morph.isVisible; + } + return false; + }); + }; + + this.statusDisplay.step = function() { + // update watchers + current = Date.now(); + elapsed = current - this.lastWatcherUpdate; + leftover = (1000 / this.watcherUpdateFrequency) - elapsed; + if (leftover < 1) { + this.watchers().forEach(function (w) { + w.update(); + }); + this.lastWatcherUpdate = Date.now(); + } + }; + + this.statusDisplay.lastWatcherUpdate = Date.now(); + this.statusDisplay.watcherUpdateFrequency = 250; + + // Buttons and toggles + + + var toogleShowStitchButton = new ToggleMorph( + 'checkbox', + null, + function () { + tStitch.toogleShowStitches(); + }, + 'Show Stitches', + function () { + return tStitch.getShowStitches(); + }); + toogleShowStitchButton.columns = 2; + toogleShowStitchButton.newLines = 1; + + elements.push(toogleShowStitchButton); + + var toogleShowJumpsButton = new ToggleMorph( + 'checkbox', + null, + function () { + tStitch.toogleShowJumpStitches(); + }, + 'Show Jump Stitches', + function () { + return tStitch.getShowJumpStitches(); + }); + toogleShowJumpsButton.columns = 1; + + elements.push(toogleShowJumpsButton); + + + elements.forEach(function(each) { myself.statusDisplay.addElement(each) }); +}; + +// fix layout custom function +IDE_Morph.prototype.fixLayout = function (situation) { + // situation is a string, i.e. + // 'selectSprite' or 'refreshPalette' or 'tabEditor' + var padding = this.padding, + maxPaletteWidth; + + Morph.prototype.trackChanges = false; + + if (situation !== 'refreshPalette') { + // controlBar + this.controlBar.setPosition(this.logo.topRight()); + this.controlBar.setWidth(this.right() - this.controlBar.left()); + this.controlBar.fixLayout(); + + // categories + this.categories.setLeft(this.logo.left()); + this.categories.setTop(this.logo.bottom()); + this.categories.setWidth(this.paletteWidth); + } + + // palette + this.palette.setLeft(this.logo.left()); + this.palette.setTop(this.categories.bottom()); + this.palette.setHeight(this.bottom() - this.palette.top()); + this.palette.setWidth(this.paletteWidth); + + if (situation !== 'refreshPalette') { + // stage + if (this.isAppMode) { + this.stage.setScale(Math.floor(Math.min( + (this.width() - padding * 2) / this.stage.dimensions.x, + (this.height() - this.controlBar.height() * 2 - padding * 2) / + this.stage.dimensions.y ) * 10) / 10); + this.stage.setCenter(this.center()); + } else { + this.stage.setScale(this.isSmallStage ? this.stageRatio : 1); + this.stage.setTop(this.logo.bottom() + padding); + this.stage.setRight(this.right()); + maxPaletteWidth = this.width() - + this.stage.width() - + this.spriteBar.tabBar.width() - + (this.padding * 2); + if (this.paletteWidth > maxPaletteWidth) { + this.paletteWidth = maxPaletteWidth; + this.fixLayout(); + } + this.stageHandle.fixLayout(); + this.paletteHandle.fixLayout(); + } + + if (this.spriteEditor.isVisible) { + this.spriteEditor.setLeft(this.paletteWidth + padding); + this.spriteEditor.setTop(this.logo.bottom() + padding); + this.spriteEditor.setExtent(new Point( + //this.spriteBar.width(), + Math.max(0, this.stage.left() - padding - this.spriteEditor.left()), + this.bottom() - this.spriteEditor.top() + )); + } + this.statusDisplay.fixLayout(); + + } + + + Morph.prototype.trackChanges = true; + this.changed(); +}; + + + +// SVG export +IDE_Morph.prototype.downloadSVG = function() { + + var minX=999999999, maxX=0, minY=999999999, maxY=0; + for (var i=0; imaxX) maxX = x1; + + if (y1maxY) maxY = y1; + } + + var svgStr = "\n"; + svgStr += "\n"; + svgStr += "\n"; + svgStr += "Embroidery export\n"; + svgStr += "\n"; + } + } else { + if (tStitch.stitches.jump[i-1]&& i>1) { + svgStr +=" \n\n"; + + blob = new Blob([svgStr], {type: 'text/plain;charset=utf-8'}); + saveAs(blob, (this.projectName ? this.projectName : 'turtlestitch') + '.svg'); +}; + +// EXP export +IDE_Morph.prototype.downloadEXP = function() { + var expArr = []; + + pixels_per_millimeter = 5; + scale = 10 / pixels_per_millimeter; + + function move(x, y) { + y *= -1; + if (x<0) x = x + 256; + expArr.push(x); + if (y<0) y = y + 256; + expArr.push(y); + + } + + for (var i=1; i 0) { + libMenu.addItem( + line, + function () {loadCostume(line); } + ); + } + }); + libMenu.popup(world, pos); + }, + 'Select a costume from the media library' + ); + +/* graphicsName = 'Costumes'; + menu.addItem( + localize(graphicsName) + '...', + function () { + var dir = graphicsName, + names = myself.getCostumesList(dir), + libMenu = new MenuMorph( + myself, + localize('Import') + ' ' + localize(dir) + ); + + function loadCostume(name) { + var url = dir + '/' + name, + img = new Image(); + img.onload = function () { + var canvas = newCanvas(new Point(img.width, img.height)); + canvas.getContext('2d').drawImage(img, 0, 0); + myself.droppedImage(canvas, name); + }; + img.src = url; + } + + names.forEach(function (line) { + if (line.length > 0) { + libMenu.addItem( + line, + function () {loadCostume(line); } + ); + } + }); + libMenu.popup(world, pos); + }, + 'Select a costume from the media library' + ); + */ + menu.addItem( + 'Libraries...', + function () { + // read a list of libraries from an external file, + var libMenu = new MenuMorph(this, 'Import library'), + libUrl = 'http://snap.berkeley.edu/snapsource/libraries/' + + 'LIBRARIES'; + + function loadLib(name) { + var url = 'http://snap.berkeley.edu/snapsource/libraries/' + + name + + '.xml'; + myself.droppedText(myself.getURL(url), name); + } + + myself.getURL(libUrl).split('\n').forEach(function (line) { + if (line.length > 0) { + libMenu.addItem( + line.substring(line.indexOf('\t') + 1), + function () { + loadLib( + line.substring(0, line.indexOf('\t')) + ); + } + ); + } + }); + + libMenu.popup(world, pos); + }, + 'Select categories of additional blocks to add to this project.' + ); + + menu.popup(world, pos); +}; + +IDE_Morph.prototype.snapMenu = function () { + var menu, + world = this.world(); + + menu = new MenuMorph(this); + menu.addItem('About...', 'aboutSnap'); + menu.addLine(); + menu.addItem( + 'Reference manual', + function () { + window.open('help/SnapManual.pdf', 'SnapReferenceManual'); + } + ); + menu.addItem( + 'TurtleStich! website', + function () { + window.open('http://'+window.location.hostname, 'SnapWebsite'); + } + ); + menu.addItem( + 'Snap! website', + function () { + window.open('http://snap.berkeley.edu/', 'SnapWebsite'); + } + ); + menu.addItem( + 'Download source', + function () { + window.open( + 'http://snap.berkeley.edu/snapsource/snap.zip', + 'SnapSource' + ); + } + ); + if (world.isDevMode) { + menu.addLine(); + menu.addItem( + 'Switch back to user mode', + 'switchToUserMode', + 'disable deep-Morphic\ncontext menus' + + '\nand show user-friendly ones', + new Color(0, 100, 0) + ); + } else if (world.currentKey === 16) { // shift-click + menu.addLine(); + menu.addItem( + 'Switch to dev mode', + 'switchToDevMode', + 'enable Morphic\ncontext menus\nand inspectors,' + + '\nnot user-friendly!', + new Color(100, 0, 0) + ); + } + menu.popup(world, this.logo.bottomLeft()); +}; + + +IDE_Morph.prototype.originalCreateSpriteEditor = IDE_Morph.prototype.createSpriteEditor; +IDE_Morph.prototype.createSpriteEditor = function(){ + this.originalCreateSpriteEditor(); + this.spriteEditor.color = new Color(240, 240, 240); + this.currentSprite.scripts.color = new Color(240, 240, 240); +} + + +/* Sprite BAR + +IDE_Morph.prototype.createSpriteBar = function () { + // assumes that the categories pane has already been created + var rotationStyleButtons = [], + thumbSize = new Point(45, 45), + nameField, + padlock, + thumbnail, + tabCorner = 15, + tabColors = this.tabColors, + tabBar = new AlignmentMorph('row', -tabCorner * 2), + tab, + symbols = ['\u2192', '\u21BB', '\u2194'], + labels = ['don\'t rotate', 'can rotate', 'only face left/right'], + myself = this; + + if (this.spriteBar) { + this.spriteBar.destroy(); + } + + this.spriteBar = new Morph(); + this.spriteBar.color = this.frameColor; + //this.add(this.spriteBar); + + function addRotationStyleButton(rotationStyle) { + var colors = myself.rotationStyleColors, + button; + + button = new ToggleButtonMorph( + colors, + myself, // the IDE is the target + function () { + if (myself.currentSprite instanceof SpriteMorph) { + myself.currentSprite.rotationStyle = rotationStyle; + myself.currentSprite.changed(); + myself.currentSprite.drawNew(); + myself.currentSprite.changed(); + } + rotationStyleButtons.forEach(function (each) { + each.refresh(); + }); + }, + symbols[rotationStyle], // label + function () { // query + return myself.currentSprite instanceof SpriteMorph + && myself.currentSprite.rotationStyle === rotationStyle; + }, + null, // environment + localize(labels[rotationStyle]) + ); + + button.corner = 8; + button.labelMinExtent = new Point(11, 11); + button.padding = 0; + button.labelShadowOffset = new Point(-1, -1); + button.labelShadowColor = colors[1]; + button.labelColor = myself.buttonLabelColor; + button.fixLayout(); + button.refresh(); + rotationStyleButtons.push(button); + button.setPosition(myself.spriteBar.position().add(2)); + button.setTop(button.top() + + ((rotationStyleButtons.length - 1) * (button.height() + 2)) + ); + myself.spriteBar.add(button); + if (myself.currentSprite instanceof StageMorph) { + button.hide(); + } + return button; + } + + addRotationStyleButton(1); + addRotationStyleButton(2); + addRotationStyleButton(0); + this.rotationStyleButtons = rotationStyleButtons; + + thumbnail = new Morph(); + thumbnail.setExtent(thumbSize); + thumbnail.image = this.currentSprite.thumbnail(thumbSize); + thumbnail.setPosition( + rotationStyleButtons[0].topRight().add(new Point(5, 3)) + ); + this.spriteBar.add(thumbnail); + + thumbnail.fps = 3; + + thumbnail.step = function () { + if (thumbnail.version !== myself.currentSprite.version) { + thumbnail.image = myself.currentSprite.thumbnail(thumbSize); + thumbnail.changed(); + thumbnail.version = myself.currentSprite.version; + } + }; + + nameField = new InputFieldMorph(this.currentSprite.name); + nameField.setWidth(100); // fixed dimensions + nameField.contrast = 90; + nameField.setPosition(thumbnail.topRight().add(new Point(10, 3))); + this.spriteBar.add(nameField); + nameField.drawNew(); + nameField.accept = function () { + var newName = nameField.getValue(); + myself.currentSprite.setName( + myself.newSpriteName(newName, myself.currentSprite) + ); + nameField.setContents(myself.currentSprite.name); + }; + this.spriteBar.reactToEdit = nameField.accept; + + // padlock + padlock = new ToggleMorph( + 'checkbox', + null, + function () { + myself.currentSprite.isDraggable = + !myself.currentSprite.isDraggable; + }, + localize('draggable'), + function () { + return myself.currentSprite.isDraggable; + } + ); + padlock.label.isBold = false; + padlock.label.setColor(this.buttonLabelColor); + padlock.color = tabColors[2]; + padlock.highlightColor = tabColors[0]; + padlock.pressColor = tabColors[1]; + + padlock.tick.shadowOffset = MorphicPreferences.isFlat ? + new Point() : new Point(-1, -1); + padlock.tick.shadowColor = new Color(); // black + padlock.tick.color = this.buttonLabelColor; + padlock.tick.isBold = false; + padlock.tick.drawNew(); + + padlock.setPosition(nameField.bottomLeft().add(2)); + padlock.drawNew(); + this.spriteBar.add(padlock); + if (this.currentSprite instanceof StageMorph) { + padlock.hide(); + } + + // tab bar + tabBar.tabTo = function (tabString) { + var active; + myself.currentTab = tabString; + this.children.forEach(function (each) { + each.refresh(); + if (each.state) {active = each; } + }); + active.refresh(); // needed when programmatically tabbing + myself.createSpriteEditor(); + myself.fixLayout('tabEditor'); + }; + + tab = new TabMorph( + tabColors, + null, // target + function () {tabBar.tabTo('scripts'); }, + localize('Scripts'), // label + function () { // query + return myself.currentTab === 'scripts'; + } + ); + tab.padding = 3; + tab.corner = tabCorner; + tab.edge = 1; + tab.labelShadowOffset = new Point(-1, -1); + tab.labelShadowColor = tabColors[1]; + tab.labelColor = this.buttonLabelColor; + tab.drawNew(); + tab.fixLayout(); + tabBar.add(tab); + + tab = new TabMorph( + tabColors, + null, // target + function () {tabBar.tabTo('costumes'); }, + localize('Costumes'), // label + function () { // query + return myself.currentTab === 'costumes'; + } + ); + tab.padding = 3; + tab.corner = tabCorner; + tab.edge = 1; + tab.labelShadowOffset = new Point(-1, -1); + tab.labelShadowColor = tabColors[1]; + tab.labelColor = this.buttonLabelColor; + tab.drawNew(); + tab.fixLayout(); + tabBar.add(tab); + + tab = new TabMorph( + tabColors, + null, // target + function () {tabBar.tabTo('sounds'); }, + localize('Sounds'), // label + function () { // query + return myself.currentTab === 'sounds'; + } + ); + tab.padding = 3; + tab.corner = tabCorner; + tab.edge = 1; + tab.labelShadowOffset = new Point(-1, -1); + tab.labelShadowColor = tabColors[1]; + tab.labelColor = this.buttonLabelColor; + tab.drawNew(); + tab.fixLayout(); + tabBar.add(tab); + + tabBar.fixLayout(); + tabBar.children.forEach(function (each) { + each.refresh(); + }); + this.spriteBar.tabBar = tabBar; + this.spriteBar.add(this.spriteBar.tabBar); + + this.spriteBar.fixLayout = function () { + this.tabBar.setLeft(this.left()); + this.tabBar.setBottom(this.bottom()); + }; +}; +*/ + +/* CORRAL BAR + +IDE_Morph.prototype.createCorralBar = function () { + // assumes the stage has already been created + var padding = 5, + newbutton, + paintbutton, + colors = [ + this.groupColor, + this.frameColor.darker(50), + this.frameColor.darker(50) + ]; + + if (this.corralBar) { + this.corralBar.destroy(); + } + + this.corralBar = new Morph(); + this.corralBar.color = this.frameColor; + this.corralBar.setHeight(this.logo.height()); // height is fixed + this.add(this.corralBar); + +}; + + +IDE_Morph.prototype.createCorral = function () { + // assumes the corral bar has already been created + var frame, template, padding = 5, myself = this; + + if (this.corral) { + this.corral.destroy(); + } + + this.corral = new Morph(); + this.corral.color = this.groupColor; + //this.add(this.corral); + + this.corral.stageIcon = new SpriteIconMorph(this.stage); + this.corral.stageIcon.isDraggable = false; + this.corral.add(this.corral.stageIcon); + + frame = new ScrollFrameMorph(null, null, this.sliderColor); + frame.acceptsDrops = false; + frame.contents.acceptsDrops = false; + + frame.contents.wantsDropOf = function (morph) { + return morph instanceof SpriteIconMorph; + }; + + frame.contents.reactToDropOf = function (spriteIcon) { + myself.corral.reactToDropOf(spriteIcon); + }; + + frame.alpha = 0; + + this.sprites.asArray().forEach(function (morph) { + template = new SpriteIconMorph(morph, template); + frame.contents.add(template); + }); + + this.corral.frame = frame; + this.corral.add(frame); + + this.corral.fixLayout = function () { + this.stageIcon.setCenter(this.center()); + this.stageIcon.setLeft(this.left() + padding); + this.frame.setLeft(this.stageIcon.right() + padding); + this.frame.setExtent(new Point( + this.right() - this.frame.left(), + this.height() + )); + this.arrangeIcons(); + this.refresh(); + }; + + this.corral.arrangeIcons = function () { + var x = this.frame.left(), + y = this.frame.top(), + max = this.frame.right(), + start = this.frame.left(); + + this.frame.contents.children.forEach(function (icon) { + var w = icon.width(); + + if (x + w > max) { + x = start; + y += icon.height(); // they're all the same + } + icon.setPosition(new Point(x, y)); + x += w; + }); + this.frame.contents.adjustBounds(); + }; + + this.corral.addSprite = function (sprite) { + this.frame.contents.add(new SpriteIconMorph(sprite)); + this.fixLayout(); + }; + + this.corral.refresh = function () { + this.stageIcon.refresh(); + this.frame.contents.children.forEach(function (icon) { + icon.refresh(); + }); + }; + + this.corral.wantsDropOf = function (morph) { + return morph instanceof SpriteIconMorph; + }; + + this.corral.reactToDropOf = function (spriteIcon) { + var idx = 1, + pos = spriteIcon.position(); + spriteIcon.destroy(); + this.frame.contents.children.forEach(function (icon) { + if (pos.gt(icon.position()) || pos.y > icon.bottom()) { + idx += 1; + } + }); + myself.sprites.add(spriteIcon.object, idx); + myself.createCorral(); + myself.fixLayout(); + }; + +}; + +*/ + +// turtlestitch project dialog +ProjectDialogMorph.prototype.buildContents = function () { + var thumbnail, notification; + + this.addBody(new Morph()); + this.body.color = this.color; + + this.srcBar = new AlignmentMorph('column', this.padding / 2); + + if (this.ide.cloudMsg) { + notification = new TextMorph( + this.ide.cloudMsg, + 10, + null, // style + false, // bold + null, // italic + null, // alignment + null, // width + null, // font name + new Point(1, 1), // shadow offset + new Color(255, 255, 255) // shadowColor + ); + notification.refresh = nop; + this.srcBar.add(notification); + } + + //disable cloud for now + // this.addSourceButton('cloud', localize('Cloud'), 'cloud'); + this.addSourceButton('local', localize('Browser'), 'storage'); + if (this.task === 'open') { + this.addSourceButton('examples', localize('Examples'), 'poster'); + } + this.srcBar.fixLayout(); + this.body.add(this.srcBar); + + if (this.task === 'save') { + this.nameField = new InputFieldMorph(this.ide.projectName); + this.body.add(this.nameField); + } + + this.listField = new ListMorph([]); + this.fixListFieldItemColors(); + this.listField.fixLayout = nop; + this.listField.edge = InputFieldMorph.prototype.edge; + this.listField.fontSize = InputFieldMorph.prototype.fontSize; + this.listField.typeInPadding = InputFieldMorph.prototype.typeInPadding; + this.listField.contrast = InputFieldMorph.prototype.contrast; + this.listField.drawNew = InputFieldMorph.prototype.drawNew; + this.listField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + + this.body.add(this.listField); + + this.preview = new Morph(); + this.preview.fixLayout = nop; + this.preview.edge = InputFieldMorph.prototype.edge; + this.preview.fontSize = InputFieldMorph.prototype.fontSize; + this.preview.typeInPadding = InputFieldMorph.prototype.typeInPadding; + this.preview.contrast = InputFieldMorph.prototype.contrast; + this.preview.drawNew = function () { + InputFieldMorph.prototype.drawNew.call(this); + if (this.texture) { + this.drawTexture(this.texture); + } + }; + this.preview.drawCachedTexture = function () { + var context = this.image.getContext('2d'); + context.drawImage(this.cachedTexture, this.edge, this.edge); + this.changed(); + }; + this.preview.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + this.preview.setExtent( + this.ide.serializer.thumbnailSize.add(this.preview.edge * 2) + ); + + this.body.add(this.preview); + this.preview.drawNew(); + if (this.task === 'save') { + thumbnail = this.ide.stage.thumbnail( + SnapSerializer.prototype.thumbnailSize + ); + this.preview.texture = null; + this.preview.cachedTexture = thumbnail; + this.preview.drawCachedTexture(); + } + + this.notesField = new ScrollFrameMorph(); + this.notesField.fixLayout = nop; + + this.notesField.edge = InputFieldMorph.prototype.edge; + this.notesField.fontSize = InputFieldMorph.prototype.fontSize; + this.notesField.typeInPadding = InputFieldMorph.prototype.typeInPadding; + this.notesField.contrast = InputFieldMorph.prototype.contrast; + this.notesField.drawNew = InputFieldMorph.prototype.drawNew; + this.notesField.drawRectBorder = InputFieldMorph.prototype.drawRectBorder; + + this.notesField.acceptsDrops = false; + this.notesField.contents.acceptsDrops = false; + + if (this.task === 'open') { + this.notesText = new TextMorph(''); + } else { // 'save' + this.notesText = new TextMorph(this.ide.projectNotes); + this.notesText.isEditable = true; + this.notesText.enableSelecting(); + } + + this.notesField.isTextLineWrapping = true; + this.notesField.padding = 3; + this.notesField.setContents(this.notesText); + this.notesField.setWidth(this.preview.width()); + + this.body.add(this.notesField); + + if (this.task === 'open') { + this.addButton('openProject', 'Open'); + this.action = 'openProject'; + } else { // 'save' + this.addButton('saveProject', 'Save'); + this.action = 'saveProject'; + } + this.shareButton = this.addButton('shareProject', 'Share'); + this.unshareButton = this.addButton('unshareProject', 'Unshare'); + this.shareButton.hide(); + this.unshareButton.hide(); + this.deleteButton = this.addButton('deleteProject', 'Delete'); + this.addButton('cancel', 'Cancel'); + + if (notification) { + this.setExtent(new Point(455, 335).add(notification.extent())); + } else { + this.setExtent(new Point(455, 335)); + } + this.fixLayout(); + +}; + + +/* +IDE_Morph.prototype.droppedImageStage = function (aCanvas, name) { + var costume = new Costume( + aCanvas, + this.currentSprite.newCostumeName( + name ? name.split('.')[0] : '' // up to period + ) + ); + + if (costume.isTainted()) { + this.inform( + 'Unable to import this image', + 'The picture you wish to import has been\n' + + 'tainted by a restrictive cross-origin policy\n' + + 'making it unusable for costumes in Snap!. \n\n' + + 'Try downloading this picture first to your\n' + + 'computer, and import it from there.' + ); + return; + } + + this.stage.addCostume(costume); + this.stage.wearCostume(costume); + //this.spriteBar.tabBar.tabTo('costumes'); + this.hasChangedMedia = true; +}; + +*/ + +PaletteHandleMorph.prototype.mouseDownLeft = function (pos) { + var world = this.world(), + offset = this.right() - pos.x, + ide = this.target.parentThatIsA(IDE_Morph); + + if (!this.target) { + return null; + } + this.step = function () { + var newPos; + if (world.hand.mouseButton) { + newPos = world.hand.bounds.origin.x + offset; + ide.paletteWidth = Math.min( + Math.max(200, newPos), + ide.stageHandle.left() - ide.spriteBar.tabBar.width() + ); + ide.setExtent(world.extent()); + + } else { + this.step = null; + } + }; +}; diff --git a/stitchcode/objects.js b/stitchcode/objects.js new file mode 100644 index 00000000..4457efa2 --- /dev/null +++ b/stitchcode/objects.js @@ -0,0 +1,259 @@ + + +/* Sprite */ +// modified SpriteMorph turtle functions + +// SpriteMorph motion primitives + + +SpriteMorph.prototype.forward = function (steps) { + var dest, + dist = steps * this.parent.scale || 0; + + oldpos = this.position(); + + if (dist >= 0) { + dest = this.position().distanceAngle(dist, this.heading); + } else { + dest = this.position().distanceAngle( + Math.abs(dist), + (this.heading - 180) + ); + } + + this.setPosition(dest); + this.positionTalkBubble(); + //this.drawLineX(dest); + + tx = dest.x - this.parent.topLeft().x; + ty = dest.y - this.parent.topLeft().y; + tjump = !this.isDown; + + if (tStitch.isFirst()) { + origx = oldpos.x - this.parent.topLeft().x; + origy = oldpos.y - this.parent.topLeft().y; + tStitch.addPoint(origx , origy ,true); + //alert("first"); + //alert("orig: " + origx + "," + origy+ " tx/ty: " + tx + ", "+ ty + " - "+ this.position()); + } + + tStitch.addPoint(tx,ty,tjump); + //alert("move to: " + tx + "x" + ty + " - isJump = " + tjump); + +}; + +SpriteMorph.prototype.gotoXY = function (x, y, justMe) { + var stage = this.parentThatIsA(StageMorph), + newX, + newY, + dest; + + newX = stage.center().x + (+x || 0) * stage.scale; + newY = stage.center().y - (+y || 0) * stage.scale; + + oldX = this.position().x / stage.scale - stage.center().x + this.extent().x/2; + oldY = -(this.position().y / stage.scale - stage.center().y + this.extent().y/2); + + if (this.costume) { + dest = new Point(newX, newY).subtract(this.rotationOffset); + } else { + dest = new Point(newX, newY).subtract(this.extent().divideBy(2)); + } + + this.setPosition(dest, justMe); + this.positionTalkBubble(); + + tx = dest.x - this.parent.topLeft().x; + ty = dest.y - this.parent.topLeft().y; + tjump = !this.isDown; + + if ( Math.abs(x-oldX)<=1.1 && Math.abs(y-oldY)<=1.1 ) { + if (tStitch.debug) + console.log("jump in place - don't add."); + } else { + if (tStitch.debug) + console.log("gotoXY "+ x + "," + y + " from: + " + oldX + "," + oldY); + tStitch.addPoint(tx,ty,tjump); + } +}; +// SpriteMorph drawing: + +SpriteMorph.prototype.drawLine = function (start, dest) { + var stagePos = this.parent.bounds.origin, + stageScale = this.parent.scale, + context = this.parent.penTrails().getContext('2d'), + from = start.subtract(stagePos).divideBy(stageScale), + to = dest.subtract(stagePos).divideBy(stageScale), + damagedFrom = from.multiplyBy(stageScale).add(stagePos), + damagedTo = to.multiplyBy(stageScale).add(stagePos), + damaged = damagedFrom.rectangle(damagedTo).expandBy( + Math.max(this.size * stageScale / 2, 1) + ).intersect(this.parent.visibleBounds()).spread(); + + + + if (this.isDown) { + context.lineWidth = this.size; + context.strokeStyle = this.color.toString(); + if (this.useFlatLineEnds) { + context.lineCap = 'butt'; + context.lineJoin = 'miter'; + } else { + context.lineCap = 'round'; + context.lineJoin = 'round'; + } + context.beginPath(); + context.moveTo(from.x, from.y); + context.lineTo(to.x, to.y); + context.stroke(); + if (this.isWarped === false) { + this.world().broken.push(damaged); + } + } +}; + +SpriteMorph.prototype.drawJumpLine = function (start, dest) { + var stagePos = this.parent.bounds.origin, + stageScale = this.parent.scale, + context = this.parent.penTrails().getContext('2d'), + from = start.subtract(stagePos).divideBy(stageScale), + to = dest.subtract(stagePos).divideBy(stageScale), + damagedFrom = from.multiplyBy(stageScale).add(stagePos), + damagedTo = to.multiplyBy(stageScale).add(stagePos), + damaged = damagedFrom.rectangle(damagedTo).expandBy( + Math.max(this.size * stageScale / 2, 1) + ).intersect(this.parent.visibleBounds()).spread(); + + context.lineWidth = this.size; + context.strokeStyle = new Color(255, 0, 0).toString(); + context.lineCap = 'round'; + context.lineJoin = 'round'; + context.beginPath(); + context.moveTo(from.x, from.y); + context.lineTo(to.x, to.y); + context.stroke(); + if (this.isWarped === false) { + this.world().broken.push(damaged); + } + +}; + +SpriteMorph.prototype.drawStitch = function (dest) { + //dest = dest.subtract(this.topLeft()); + var s = tStitch.draw_stitch_len; + var stagePos = this.parent.bounds.origin, + stageScale = this.parent.scale, + context = this.parent.penTrails().getContext('2d'), + to = dest.subtract(stagePos).divideBy(stageScale), + damagedFrom = new Point(to.x-s,to.y-s).multiplyBy(stageScale).add(stagePos), + damagedTo = new Point(to.x+s,to.y+s).multiplyBy(stageScale).add(stagePos), + damaged = damagedFrom.rectangle(damagedTo).expandBy( + Math.max(this.size * stageScale / 2, 1) + ).intersect(this.parent.visibleBounds()).spread(); + + context.lineWidth = this.size; + context.strokeStyle = new Color(0, 0, 255).toString(); + context.lineCap = 'round'; + context.lineJoin = 'round'; + + context.beginPath(); + context.moveTo(to.x - s, to.y - s ); + context.lineTo(to.x + s, to.y + s); + context.stroke(); + + context.beginPath(); + context.moveTo(to.x - s, to.y + s); + context.lineTo(to.x + s, to.y - s); + context.stroke(); + if (this.isWarped === false) { + this.world().broken.push(damaged); + } +}; + + + +SpriteMorph.prototype.clear = function () { + this.parent.clearPenTrails(); + tStitch.clearPoints(); + if (tStitch.debug) { + tStitch.debug_msg("",true); + } + +}; + + + + +// SpriteMorph motion - adjustments due to nesting + +SpriteMorph.prototype.moveBy = function (delta, justMe) { + // override the inherited default to make sure my parts follow + // unless it's justMe (a correction) + var start = this.isDown && !justMe && this.parent ? + this.rotationCenter() : null; + + // add stitch controls + if (this.parent) { + if (this.parent.penTrails()) { + origin = this.rotationCenter(); + } + } + + SpriteMorph.uber.moveBy.call(this, delta); + + // add stitch controls + if (this.parent) { + if (this.parent.penTrails() && origin.x > 100) { + //alert(origin.x); + if (tStitch.draw_stitches) { + this.drawStitch(this.rotationCenter()); + } + if (tStitch.draw_jumps && !this.isDown) { + this.drawJumpLine(origin,this.rotationCenter()); + } + } + } + + + if (start) { + this.drawLine(start, this.rotationCenter()); + } + if (!justMe) { + this.parts.forEach(function (part) { + part.moveBy(delta); + }); + } +}; + +/* +// Definition of new block categories +SpriteMorph.prototype.categories = + [ + 'motion', + 'control', + 'shapes', + 'colors', + 'sensing', + 'operators', + 'variables', + 'lists', + 'my blocks' + ]; + +SpriteMorph.prototype.blockColor = { + motion : new Color(74, 108, 212), + shapes : new Color(143, 86, 227), + colors : new Color(207, 74, 217), + sound : new Color(207, 74, 217), // we need to keep this color for the zoom blocks dialog + control : new Color(230, 168, 34), + sensing : new Color(4, 148, 220), + operators : new Color(98, 194, 19), + variables : new Color(243, 118, 29), + lists : new Color(217, 77, 17), + other : new Color(150, 150, 150), + 'my blocks': new Color(150, 150, 60), +}; + +// now move also "make a block to 'my blocks' + +*/ diff --git a/stitchcode/tstitch.js b/stitchcode/tstitch.js new file mode 100644 index 00000000..1e279c9d --- /dev/null +++ b/stitchcode/tstitch.js @@ -0,0 +1,146 @@ +// Stitchode's main changes and addtions to snap! go in here +// sorry it lacks proper documentation + + +var tStitch = {}; + +tStitch.debug = true; +tStitch.draw_jumps = true; +tStitch.draw_stitches = true; +tStitch.draw_stitch_len = 2; + +tStitch.debug_msg = function (st,clear) { + o = new String(); + if (!clear) { + o = document.getElementById("bug").innerHTML; + } else { + o = ""; + } + o += st; + document.getElementById("bug").innerHTML = o; +}; + +tStitch.getBaseURL = function () { + var url = location.href; // entire url including querystring - also: window.location.href; + if (url.lastIndexOf('#') > 0) { + url = url.substring(0, url.lastIndexOf('#')); + } + url = url.substring(0, url.lastIndexOf('/')); + return url + "/"; +}; + + +tStitch.stitches = {}; +tStitch.stitches.x = []; +tStitch.stitches.y = []; +tStitch.stitches.jump = []; + +tStitch.isFirst = function() { + if (tStitch.stitches.x.length > 0) + return false; + else + return true; +}; + +tStitch.clearPoints = function() { + tStitch.stitches.x = []; + tStitch.stitches.y = []; + tStitch.stitches.jump = []; +}; + +tStitch.addPoint = function (x,y,jump) { + if (tStitch.debug) { + s = new String(); + s = s + "adding Point (" + x + "," + y; + if (jump) s = s + ",jump"; + s+= ")"; + console.log(s); + } + + if (tStitch.stitches.x[tStitch.stitches.x.length-1] == x && + tStitch.stitches.y[tStitch.stitches.y.length-1] == y + ) { + //alert("pint exist"); + } else { + tStitch.stitches.x.push(x); + tStitch.stitches.y.push(y); + tStitch.stitches.jump.push(jump); + } +}; + +tStitch.toogleShowStitches = function() { + tStitch.draw_stitches = !tStitch.draw_stitches; +}; + +tStitch.getShowStitches = function() { + return tStitch.draw_stitches; +}; + +tStitch.toogleShowJumpStitches = function() { + tStitch.draw_jumps = !tStitch.draw_jumps; +}; + +tStitch.getShowJumpStitches = function() { + return tStitch.draw_jumps; +}; + +tStitch.signup = function() { + window.open('http://' + window.location.hostname + '/signup'); +}; + + +tStitch.upload = function(name) { + + tStitch.debug_msg("uploading points... sending SAVE with num points= " + tStitch.stitches.x.length, true); + params = { "x[]": tStitch.stitches.x, "y[]":tStitch.stitches.y, "j[]":tStitch.stitches.jump, "name":name }; + + if (tStitch.stitches.x.length <= 1 || tStitch.stitches.y <= 1) { + new DialogBoxMorph().inform( + 'Upload Error', + 'No stitches to upload, please (re)generate a drawing first!', + world); + + } else { + $.post( + "/upload", + data = params, + successCallback = function (data) { + if (data!="ERROR") { + /*new DialogBoxMorph().inform( + 'Upload Success', + 'Your embroidery file is ready and will be available at this url:\n' + + window.location.hostname + '/view/'+data,'\n', + world);*/ + window.open('http://' + window.location.hostname + '/view/'+data, 'TurtleStitch file preview'); + } else { + new DialogBoxMorph().inform( + 'Upload Error', + 'Sorry! Upload failed for an unknown reason', + world); + } + }); + } + + /* + $.fileDownload(tStitch.getBaseURL() +"stitchcode/backend/save.py", { + successCallback: function (html, url) { + alert("DSD"); + }, + failCallback: function (html, url) { + alert( + 'Your file download just failed for this URL:' + url + + '\r\n' + 'Here was the resulting error HTML: \r\n' + + html + ); + }, + + httpMethod: "POST", + data: params + }); */ +}; + + + +IDE_Morph.prototype.uploadStitches = function () { + tStitch.upload(); +}; diff --git a/store.js b/store.js index 6112c224..344ed8cb 100644 --- a/store.js +++ b/store.js @@ -7,7 +7,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -49,19 +49,19 @@ */ -/*global modules, XML_Element, VariableFrame, StageMorph, -SpriteMorph, WatcherMorph, Point, CustomBlockDefinition, Context, -ReporterBlockMorph, CommandBlockMorph, HatBlockMorph, RingMorph, contains, -detect, CustomCommandBlockMorph, CustomReporterBlockMorph, Color, List, -newCanvas, Costume, Sound, Audio, IDE_Morph, ScriptsMorph, BlockMorph, -ArgMorph, InputSlotMorph, TemplateSlotMorph, CommandSlotMorph, +/*global modules, XML_Element, VariableFrame, StageMorph, SpriteMorph, +WatcherMorph, Point, CustomBlockDefinition, Context, ReporterBlockMorph, +CommandBlockMorph, detect, CustomCommandBlockMorph, CustomReporterBlockMorph, +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, BooleanSlotMorph, +normalizeCanvas*/ // Global stuff //////////////////////////////////////////////////////// -modules.store = '2015-July-27'; +modules.store = '2016-October-27'; // XML_Serializer /////////////////////////////////////////////////////// @@ -390,6 +390,7 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { if (model.pentrails) { project.pentrails = new Image(); project.pentrails.onload = function () { + normalizeCanvas(project.stage.trailsCanvas); var context = project.stage.trailsCanvas.getContext('2d'); context.drawImage(project.pentrails, 0, 0); project.stage.changed(); @@ -415,6 +416,8 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { model.stage.attributes.codify === 'true'; StageMorph.prototype.enableInheritance = model.stage.attributes.inheritance === 'true'; + StageMorph.prototype.enableSublistIDs = + model.stage.attributes.sublistIDs === 'true'; model.hiddenPrimitives = model.project.childNamed('hidden'); if (model.hiddenPrimitives) { @@ -627,8 +630,8 @@ SnapSerializer.prototype.loadSprites = function (xmlString, ide) { myself.objects[model.attributes.id] = sprite; } if (model.attributes.name) { - sprite.name = model.attributes.name; - project.sprites[model.attributes.name] = sprite; + sprite.name = ide.newSpriteName(model.attributes.name); + project.sprites[sprite.name] = sprite; } if (model.attributes.color) { sprite.color = myself.loadColor(model.attributes.color); @@ -710,6 +713,7 @@ SnapSerializer.prototype.loadMediaModel = function (xmlNode) { SnapSerializer.prototype.loadObject = function (object, model) { // private var blocks = model.require('blocks'); + this.loadInheritanceInfo(object, model); this.loadNestingInfo(object, model); this.loadCostumes(object, model); this.loadSounds(object, model); @@ -773,13 +777,15 @@ SnapSerializer.prototype.loadVariables = function (varFrame, element) { var myself = this; element.children.forEach(function (child) { - var value; + var v, value; if (child.tag !== 'variable') { return; } value = child.children[0]; - varFrame.vars[child.attributes.name] = new Variable(value ? - myself.loadValue(value) : 0); + v = new Variable(); + v.isTransient = (child.attributes.transient === 'true'); + v.value = (v.isTransient || !value ) ? 0 : myself.loadValue(value); + varFrame.vars[child.attributes.name] = v; }); }; @@ -791,7 +797,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; } @@ -835,6 +841,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; @@ -963,7 +976,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; } @@ -1046,7 +1068,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') { @@ -1063,7 +1087,7 @@ SnapSerializer.prototype.obsoleteBlock = function (isReporter) { // private var block = isReporter ? new ReporterBlockMorph() : new CommandBlockMorph(); - block.selector = 'nop'; + block.selector = 'errorObsolete'; block.color = new Color(200, 0, 20); block.setSpec('Obsolete!'); block.isDraggable = true; @@ -1112,7 +1136,7 @@ SnapSerializer.prototype.loadInput = function (model, input, block) { SnapSerializer.prototype.loadValue = function (model) { // private - var v, items, el, center, image, name, audio, option, + var v, i, lst, items, el, center, image, name, audio, option, bool, myself = this; function record() { @@ -1143,33 +1167,43 @@ SnapSerializer.prototype.loadValue = function (model) { throw new Error('expecting a reference id'); case 'l': option = model.childNamed('option'); - return option ? [option.contents] : model.contents; + if (option) { + return [option.contents]; + } + bool = model.childNamed('bool'); + if (bool) { + return this.loadValue(bool); + } + return model.contents; case 'bool': return model.contents === 'true'; case 'list': if (model.attributes.hasOwnProperty('linked')) { + v = new List(); + v.isLinked = true; + record(); + lst = v; items = model.childrenNamed('item'); - if (items.length === 0) { - v = new List(); - record(); - return v; - } - items.forEach(function (item) { + items.forEach(function (item, i) { var value = item.children[0]; - if (v === undefined) { - v = new List(); - record(); - } else { - v = v.rest = new List(); - } - v.isLinked = true; if (!value) { v.first = 0; } else { v.first = myself.loadValue(value); } + var tail = model.childNamed('list') || + model.childNamed('ref'); + if (tail) { + v.rest = myself.loadValue(tail); + } else { + if (i < (items.length - 1)) { + v.rest = new List(); + v = v.rest; + v.isLinked = true; + } + } }); - return v; + return lst; } v = new List(); record(); @@ -1225,10 +1259,31 @@ SnapSerializer.prototype.loadValue = function (model) { } else { el = model.childNamed('l'); if (el) { - v.expression = new InputSlotMorph(el.contents); + bool = el.childNamed('bool'); + if (bool) { + v.expression = new BooleanSlotMorph( + this.loadValue(bool) + ); + } else { + v.expression = new InputSlotMorph(el.contents); + } } } } + if (v.expression instanceof BlockMorph) { + // bind empty slots to implicit formal parameters + i = 0; + v.expression.allEmptySlots().forEach(function (slot) { + i += 1; + if (slot instanceof MultiArgMorph) { + slot.bindingID = ['arguments']; + } else { + slot.bindingID = i; + } + }); + // and remember the number of detected empty slots + v.emptySlots = i; + } el = model.childNamed('receiver'); if (el) { el = el.childNamed('ref') || el.childNamed('sprite'); @@ -1298,7 +1353,8 @@ SnapSerializer.prototype.loadValue = function (model) { v = new Costume(null, name, center); image.onload = function () { var canvas = newCanvas( - new Point(image.width, image.height) + new Point(image.width, image.height), + true // nonRetina ), context = canvas.getContext('2d'); context.drawImage(image, 0, 0); @@ -1400,7 +1456,10 @@ Array.prototype.toXML = function (serializer) { // Sprites StageMorph.prototype.toXML = function (serializer) { - var thumbnail = this.thumbnail(SnapSerializer.prototype.thumbnailSize), + var thumbnail = normalizeCanvas( + this.thumbnail(SnapSerializer.prototype.thumbnailSize), + true + ), thumbdata, ide = this.parentThatIsA(IDE_Morph); @@ -1437,6 +1496,7 @@ StageMorph.prototype.toXML = function (serializer) { 'lines="@" ' + 'codify="@" ' + 'inheritance="@" ' + + 'sublistIDs="@" ' + 'scheduled="@" ~>' + '$' + '%' + @@ -1465,8 +1525,9 @@ StageMorph.prototype.toXML = function (serializer) { SpriteMorph.prototype.useFlatLineEnds ? 'flat' : 'round', this.enableCodeMapping, this.enableInheritance, + this.enableSublistIDs, StageMorph.prototype.frameRate !== 0, - this.trailsCanvas.toDataURL('image/png'), + normalizeCanvas(this.trailsCanvas, true).toDataURL('image/png'), serializer.store(this.costumes, this.name + '_cst'), serializer.store(this.sounds, this.name + '_snd'), serializer.store(this.variables), @@ -1558,8 +1619,8 @@ Costume.prototype.toXML = function (serializer) { this.name, this.rotationCenter.x, this.rotationCenter.y, - this instanceof SVG_Costume ? - this.contents.src : this.contents.toDataURL('image/png') + this instanceof SVG_Costume ? this.contents.src + : normalizeCanvas(this.contents).toDataURL('image/png') ); }; @@ -1578,16 +1639,25 @@ VariableFrame.prototype.toXML = function (serializer) { return Object.keys(this.vars).reduce(function (vars, v) { var val = myself.vars[v].value, dta; - if (val === undefined || val === null) { + if (myself.vars[v].isTransient) { + dta = serializer.format( + '', + v) + ; + } else if (val === undefined || val === null) { dta = serializer.format('', v); } else { dta = serializer.format( '%', v, - typeof val === 'object' ? serializer.store(val) - : typeof val === 'boolean' ? - serializer.format('$', val) - : serializer.format('$', val) + typeof val === 'object' ? + (isSnapObject(val) ? '' + : serializer.store(val)) + : typeof val === 'boolean' ? + serializer.format( + '$', val + ) + : serializer.format('$', val) ); } return vars + dta; @@ -1604,6 +1674,10 @@ WatcherMorph.prototype.toXML = function (serializer) { this.topLeft().subtract(this.parent.topLeft()) : this.topLeft(); + if (this.isTemporary()) { + // do not save watchers on temporary variables + return ''; + } return serializer.format( '', (isVar && this.target.owner) || (!isVar && this.target) ? @@ -1727,11 +1801,16 @@ CustomCommandBlockMorph.prototype.toBlockXML = function (serializer) { var scope = this.definition.isGlobal ? undefined : this.definition.receiver.name; return serializer.format( - '%%%', + '%%%%', this.blockSpec, this.definition.isGlobal ? '' : serializer.format(' scope="@"', scope), serializer.store(this.inputs()), + this.definition.variableNames.length ? + '' + + this.variables.toXML(serializer) + + '' + : '', this.comment ? this.comment.toXML(serializer) : '', scope && !this.definition.receiver[serializer.idProperty] ? '' + @@ -1747,6 +1826,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) { @@ -1762,6 +1842,7 @@ CustomBlockDefinition.prototype.toXML = function (serializer) { return serializer.format( '' + '%' + + (this.variableNames.length ? '%' : '@') + '
@
' + '@' + '%%%' + @@ -1770,6 +1851,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) { @@ -1798,6 +1881,13 @@ ArgMorph.prototype.toXML = function () { return ''; // empty by default }; +BooleanSlotMorph.prototype.toXML = function () { + return (typeof this.value === 'boolean') ? + '' + this.value + '' + : ''; + +}; + InputSlotMorph.prototype.toXML = function (serializer) { if (this.constant) { return serializer.format( @@ -1857,26 +1947,56 @@ ColorSlotMorph.prototype.toXML = function (serializer) { List.prototype.toXML = function (serializer, mediaContext) { // mediaContext is an optional name-stub // when collecting media into a separate module - var xml, item; + var xml, value, item; if (this.isLinked) { xml = ''; + if (StageMorph.prototype.enableSublistIDs) { + // recursively nest tails: + value = this.first; + if (!isNil(value)) { + xml += serializer.format( + '%', + typeof value === 'object' ? + (isSnapObject(value) ? '' + : serializer.store(value, mediaContext)) + : typeof value === 'boolean' ? + serializer.format('$', value) + : serializer.format('$', value) + ); + } + if (!isNil(this.rest)) { + xml += serializer.store(this.rest, mediaContext); + } + return xml + ''; + } + // else sequentially serialize tails: item = this; do { - xml += serializer.format( - '%', - serializer.store(item.first) - ); + value = item.first; + if (!isNil(value)) { + xml += serializer.format( + '%', + typeof value === 'object' ? + (isSnapObject(value) ? '' + : serializer.store(value, mediaContext)) + : typeof value === 'boolean' ? + serializer.format('$', value) + : serializer.format('$', value) + ); + } item = item.rest; - } while (item !== undefined && (item !== null)); + } while (!isNil(item)); return xml + ''; } + // dynamic array: return serializer.format( '%', this.contents.reduce(function (xml, item) { return xml + serializer.format( '%', typeof item === 'object' ? - serializer.store(item, mediaContext) + (isSnapObject(item) ? '' + : serializer.store(item, mediaContext)) : typeof item === 'boolean' ? serializer.format('$', item) : serializer.format('$', item) @@ -1885,7 +2005,6 @@ List.prototype.toXML = function (serializer, mediaContext) { ); }; - Context.prototype.toXML = function (serializer) { if (this.isContinuation) { // continuations are transient in Snap! return ''; diff --git a/tables.js b/tables.js new file mode 100644 index 00000000..2abd5646 --- /dev/null +++ b/tables.js @@ -0,0 +1,1241 @@ +/* + + tables.js + + basic spreadsheet elements for Snap! + + written by Jens Mönig + jens@moenig.org + + Copyright (C) 2016 by Jens Mönig + + This file is part of Snap!. + + Snap! is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as + published by the Free Software Foundation, either version 3 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + + prerequisites: + -------------- + needs morphic.js, list.js, widgets.js, byob.js, threads + + + I. hierarchy + ------------- + the following tree lists all constructors hierarchically, + indentation indicating inheritance. Refer to this list to get a + contextual overview: + + DialogBoxMorph** + TableDialogMorph + Morph* + FrameMorph* + TableMorph + TableCellMorph + TableFrameMorph + Table + + * from morphic.js + ** from widgets.js + + + II. toc + ------- + the following list shows the order in which all constructors are + defined. Use this list to locate code in this document: + + Table + TableCellMorph + TableMorph + TableFrameMorph + TableDialogMorph + +*/ + +// Global settings ///////////////////////////////////////////////////// + +/*global modules, Point, newCanvas, Morph, fontHeight, SliderMorph, List, +MorphicPreferences, FrameMorph, HandleMorph, DialogBoxMorph, isString, +SpriteMorph, Context, Costume, ArgMorph, BlockEditorMorph, +SyntaxElementMorph, MenuMorph, SpriteBubbleMorph, SpeechBubbleMorph, +CellMorph, ListWatcherMorph, isNil, BoxMorph, Variable, isSnapObject*/ + +modules.tables = '2016-May-02'; + +var Table; +var TableCellMorph; +var TableMorph; +var TableFrameMorph; + +// Table ///////////////////////////////////////////////////////////// + +/* + Observable 2D data collections accessible by rows, columns and cells + with indices starting at 1. + currently only used for testing TableViews in Snap, because Snap + automatically displays 2D lists as tables. +*/ + +function Table(cols, rows) { + this.colCount = +cols; + this.rowCount = +rows; + this.colNames = []; + this.rowNames = []; + this.contents = new Array(+rows); + for (var i = 0; i < rows; i += 1) { + this.contents[i] = new Array(+cols); + } + this.lastChanged = Date.now(); +} + +// Table testing: + +Table.prototype.demo = function(aWorld) { + // new Table(50, 10000).demo(world) + var dlg; + this.fillWithTestData(); + dlg = new TableDialogMorph(this); + dlg.popUp (aWorld); +}; + +// Table updating: + +Table.prototype.changed = function () { + this.lastChanged = Date.now(); +}; + +// Table querying: + +Table.prototype.get = function (col, row) { + if (!col) { + if (!row) {return [this.rowCount]; } + return this.rowName(row); + } else if (!row) { + return this.colName(col); + } + if (col > this.colCount || row > this.rowCount) {return null; } + return (this.contents[row - 1] || [])[col - 1]; +}; + +Table.prototype.row = function(row) { + return this.contents[row - 1]; +}; + +Table.prototype.col = function(col) { + var dta = [], + c = col - 1, + i; + for (i = 0; i < this.rowCount; i += 1) { + dta.push(this.contents[i][c]); + } + return dta; +}; + +Table.prototype.colName = function (col) { + // answer the specified name or a capital letter A-Z + // repeated accordingly + if (col > this.colCount) {return null; } + var name = this.colNames[col - 1]; + if (name !== undefined) {return name; } + return String.fromCharCode(64 + ((col % 26) || 26)).repeat( + Math.floor((col - 1) / 26) + 1 + ); +}; + +Table.prototype.rowName = function (row) { + // answer the specified name or row number + if (row > this.rowCount) {return null; } + return this.rowNames[row - 1] || row; +}; + +Table.prototype.rows = function () { + return this.rowCount; +}; + +Table.prototype.cols = function () { + return this.colCount; +}; + +Table.prototype.columnNames = function () { + return this.colNames; +}; + +// Table setting: + +Table.prototype.set = function (data, col, row) { + this.contents[row - 1][col - 1] = data; + this.changed(); +}; + +Table.prototype.setRows = function (rowsArray, colNames, rowNames) { + this.contents = rowsArray; + if (colNames) {this.colNames = colNames; } + if (rowNames) {this.rowNames = rowNames; } + this.changed(); +}; + +Table.prototype.setCols = function (colsArray, colNames, rowNames) { + var r, c; + for (c = 0; c < this.colCount; c += 1) { + for (r = 0; r < this.rowCount; r += 1) { + this.contents[r][c] = colsArray[c][r]; + } + } + if (colNames) {this.colNames = colNames; } + if (rowNames) {this.rowNames = rowNames; } + this.changed(); +}; + +Table.prototype.setColNames = function (array) { + this.colNames = array || []; + this.changed(); +}; + +Table.prototype.setRowNames = function (array) { + this.rowNames = array || []; + this.changed(); +}; + +Table.prototype.setColName = function (col, name) { + this.colNames[col + 1] = name; + this.changed(); +}; + +Table.prototype.setRowName = function (row, name) { + this.rowNames[row + 1] = name; + this.changed(); +}; + +// Table growing: + +Table.prototype.addRow = function (array, name) { + if (array) { + this.contents[this.rowCount] = array; + } else { + this.contents[this.rowCount] = new Array(this.rowCount); + } + this.rowNames[this.rowCount] = name; + this.rowCount += 1; + this.changed(); +}; + +Table.prototype.addCol = function (array, name) { + var i; + if (array) { + for (i = 0; i < this.col; i += 1) { + this.contents[i][this.colCount] = array[i]; + } + } + this.colNames[this.colCount] = name; + this.colCount += 1; + this.changed(); +}; + +// Table converting: + +Table.prototype.toList = function () { + return new List( + this.contents.map(function (eachRow) { + return new List(eachRow); + }) + ); +}; + +// Table testing + +Table.prototype.fillWithTestData = function () { + var c, r; + for (c = 1; c <= this.colCount; c += 1) { + for (r = 1; r <= this.rowCount; r += 1) { + this.set (this.colName(c) + this.rowName(r), c, r); + } + } +}; + +// TableCellMorph ///////////////////////////////////////////////////////// + +// basic fast data view, currently constrained to a single line of text + +// TableCellMorph inherits from Morph: + +TableCellMorph.prototype = new Morph(); +TableCellMorph.prototype.constructor = TableCellMorph; +TableCellMorph.uber = Morph.prototype; + +// TableCellMorph global setting: + +TableCellMorph.prototype.listSymbol = ArgMorph.prototype.listIcon(); + +// TableCellMorph instance creation: + +function TableCellMorph(data, extent, isLabel) { + this.init(data, extent, isLabel); +} + +TableCellMorph.prototype.init = function (data, extent, isLabel) { + // additional properties: + this.data = data; + this.isLabel = isLabel || false; + + // initialize inherited properties: + TableCellMorph.uber.init.call(this, true); + + // override inherited properites: + this.noticesTransparentClick = true; + if (extent) {this.silentSetExtent(extent); } + this.drawNew(); +}; + +TableCellMorph.prototype.setData = function (data, extent) { + this.data = data; + if (extent && (!extent.eq(this.extent()))) { + this.silentSetExtent(extent); + this.drawNew(); + } else { + this.drawData(); + } + // 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(); +}; + +TableCellMorph.prototype.drawData = function (lbl, bg) { + var dta = lbl || this.dataRepresentation(this.data), + context = this.image.getContext('2d'), + fontSize = SyntaxElementMorph.prototype.fontSize, + empty = TableMorph.prototype.highContrast ? 'rgb(220, 220, 220)' + : 'transparent', + orphaned = 'rgb(217, 77, 17)', + fontStyle = this.isLabel ? + (this.data instanceof Array ? 'italic' : '') + : this.shouldBeList() ? 'bold' : '', + font = fontStyle + ' ' + fontSize + 'px Helvetica, Arial, sans-serif', + background = bg || (this.isLabel ? empty + : (this.shouldBeList() ? orphaned + : (this.isOvershooting() ? 'white' + : (isNil(this.data) ? empty : 'white')))), + foreground = !this.isLabel && this.shouldBeList()? 'white' : 'black', + width = this.width(), + height = this.height(), + txtWidth, + txtHeight, + x, + y; + + context.clearRect(0, 0, width, height); + context.fillStyle = background; + if (this.shouldBeList()) { + BoxMorph.prototype.outlinePath.call( + this, context, SyntaxElementMorph.prototype.corner + 1, 0 + ); + context.fill(); + } else if (this.isOvershooting()) { + this.raggedBoxPath(context); + context.fill(); + } else { + context.fillRect(0, 0, width, height); + } + + if (!dta) {return; } + if (dta instanceof HTMLCanvasElement) { + x = Math.max((width - dta.width) / 2, 0); + y = Math.max((height - dta.height) / 2, 0); + context.shadowOffsetX = 4; + context.shadowOffsetY = 4; + context.shadowBlur = 4; + context.shadowColor = 'lightgray'; + context.drawImage(dta, x, y); + } else { // text + context.font = font; + context.textAlign = 'left'; + context.textBaseline = 'bottom'; + txtWidth = context.measureText(dta).width; + txtHeight = fontHeight(fontSize); + context.fillStyle = foreground; + x = Math.max((width - txtWidth) / 2, 0); + y = Math.max((height - txtHeight) / 2, 0); + context.fillText(dta, x, txtHeight + y); + } +}; + +TableCellMorph.prototype.dataRepresentation = function (dta) { + if (dta instanceof Morph) { + 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') { + return dta.toString(); + } else if (typeof dta === 'boolean') { + return SpriteMorph.prototype.booleanMorph.call( + null, + dta + ).fullImage(); + } else if (dta instanceof Array) { + return this.dataRepresentation(dta[0]); + } else if (dta instanceof Variable) { + return this.dataRepresentation(dta.value); + } else if (dta instanceof HTMLCanvasElement) { + return dta; + } else if (dta instanceof Context) { + return dta.image(); + } else if (dta instanceof Costume) { + return dta.thumbnail(new Point(40, 40)); + } else if (dta instanceof List) { + return this.listSymbol; + // return new ListWatcherMorph(dta).fullImageClassic(); + } else { + return dta ? dta.toString() : (dta === 0 ? '0' : null); + } +}; + +TableCellMorph.prototype.raggedBoxPath = function (context) { + var width = this.width(), + height = this.height(), + x = width * 0.75, + step = height / 6, + y = 0; + context.beginPath(); + context.moveTo(0, 0); + context.lineTo(width, 0); + for (y = 0; y < height; y += (step * 2)) { + context.lineTo(x, y + step); + context.lineTo(width, y + (step * 2)); + } + context.lineTo(width, height); + context.lineTo(0, height); + context.closePath(); +}; + +TableCellMorph.prototype.shouldBeList = function () { + return this.data instanceof Array; +}; + +TableCellMorph.prototype.isOvershooting = function () { + return this.data instanceof Variable; +}; + +// TableCellMorph events: + +TableCellMorph.prototype.mouseDoubleClick = function (pos) { + if (this.data instanceof Table || this.data instanceof List) { + new TableDialogMorph(this.data).popUp(this.world()); + } else if (this.data instanceof Array && this.data[0] instanceof List) { + new TableDialogMorph(this.data[0]).popUp(this.world()); + } else { + this.escalateEvent('mouseDoubleClick', pos); + } +}; + +TableCellMorph.prototype.mouseEnter = function () { + var tm, x, c; + if (this.isLabel) { + tm = this.parentThatIsA(TableMorph); + x = tm.world().hand.left() - tm.left(); + c = tm.columnAt(x); + if (c > 0) { + this.drawData(c, 'rgb(220, 220, 250)'); + this.changed(); + } + } +}; + +TableCellMorph.prototype.mouseLeave = function () { + if (this.isLabel) { + this.drawData(); + this.changed(); + } +}; + +// TableMorph ////////////////////////////////////////////////////////// + +// TableMorph inherits from FrameMorph: + +TableMorph.prototype = new FrameMorph(); +TableMorph.prototype.constructor = TableMorph; +TableMorph.uber = FrameMorph.prototype; + +// TableMorph preferences settings: + +TableMorph.prototype.highContrast = false; + +// TableMorph instance creation: + +function TableMorph( + table, + // optional parameters below this line + scrollBarSize, + extent, + startRow, + startCol, + globalColWidth, + colWidths, + rowHeight, + colLabelHeight, + padding +) { + this.init( + table, + scrollBarSize, + extent, + startRow, + startCol, + globalColWidth, + colWidths, + rowHeight, + colLabelHeight, + padding + ); +} + +TableMorph.prototype.init = function ( + table, + scrollBarSize, + extent, + startRow, + startCol, + globalColWidth, + colWidths, + rowHeight, + colLabelHeight, + padding +) { + // additional properties: + this.table = table; + this.scrollBarSize = scrollBarSize || MorphicPreferences.scrollBarSize; + this.startRow = startRow || 1; + this.startCol = startCol || 1; + this.textHeight = Math.ceil( + fontHeight(SyntaxElementMorph.prototype.fontSize) * 1.3 + ); + this.rowHeight = rowHeight || this.textHeight; + this.colWidths = colWidths || []; + this.globalColWidth = globalColWidth || Math.ceil(this.textHeight * 3.5); + this.colLabelHeight = colLabelHeight || this.textHeight; + this.padding = padding || SyntaxElementMorph.prototype.scale; //1; + this.tableVersion = this.table.lastChanged; + + // scroll bars: + this.hBar = null; + this.vBar = null; + + // cached properties (do not persist): + this.rowLabelWidth = 0; + this.columns = []; // relative left positions + this.rows = 0; + + // cached properties for scrolling and resizing (do not persist): + this.maxStartRow = null; + this.maxStartCol = null; + this.dragAnchor = null; + this.resizeAnchor = null; + 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) + // and only initialize properties inherited from Morph: + Morph.prototype.init.call(this, true); + + // override inherited properites: + // this.fps = 3; // this will slow down the sliders (!) + if (extent) {this.silentSetExtent(extent); } + this.initScrollBars(); + this.drawNew(); +}; + +TableMorph.prototype.initScrollBars = function () { + var myself = this; + + // horizontal scroll bar - scrolls columns + this.hBar = new SliderMorph( + 1, // start + null, // stop + null, // value + null, // size + 'horizontal' + ); + this.hBar.setHeight(this.scrollBarSize); + this.hBar.action = function (num) { + myself.showData(num, null, true); + }; + this.hBar.isDraggable = false; + this.add(this.hBar); + + // vertical scroll bar - scrolls rows + this.vBar = new SliderMorph( + 1, // start + null, // stop + null, // value + null, // size + 'vertical' + ); + this.vBar.setWidth(this.scrollBarSize); + this.vBar.action = function (num) { + myself.showData(null, num, true); + }; + this.vBar.isDraggable = false; + this.add(this.vBar); +}; + +TableMorph.prototype.updateScrollBars = function () { + if (this.maxStartCol === 1) { + this.hBar.hide(); + } else { + this.hBar.show(); + this.hBar.stop = this.maxStartCol; + this.hBar.value = this.startCol; + this.hBar.size = Math.max( + this.hBar.rangeSize() * this.columns.length / this.table.cols(), + this.hBar.rangeSize() / 10 + ); + this.hBar.drawNew(); + } + + this.vBar.stop = this.maxStartRow; + this.vBar.value = this.startRow; + if (this.maxStartRow === 1) { + this.vBar.hide(); + } else { + this.vBar.show(); + this.vBar.size = Math.max( + this.vBar.rangeSize() * this.rows / this.table.rows(), + this.vBar.rangeSize() / 10 + ); + this.vBar.drawNew(); + } +}; + +TableMorph.prototype.drawNew = function () { + var context, w, i; + this.image = newCanvas(this.extent()); + context = this.image.getContext('2d'); + context.fillStyle = 'rgb(220, 220, 220)'; + BoxMorph.prototype.outlinePath.call( + this, context, SyntaxElementMorph.prototype.corner + 1, 0 + ); + context.fill(); + + // determine and cache layout information + this.rowLabelWidth = this.rowLabelsWidth(); + this.columns = this.columnsLayout(); + this.rows = this.visibleRows(); + + // optionally draw grid + if (this.highContrast && this.table.cols() > 1) { + w = this.padding; + for (i = this.startCol; i <= this.table.cols(); i += 1) { + w += (this.colWidth(i) + this.padding); + } + context.fillStyle = 'darkGray'; + context.fillRect( + this.padding + this.rowLabelWidth, + this.padding + this.colLabelHeight, + w, + (this.rowHeight + this.padding) * + (this.table.rows() + 1 - this.startRow) + + this.padding + ); + } + + this.buildCells(); + + // fix scroll bars layout + this.hBar.setWidth(this.width() - this.vBar.width()); + this.hBar.setLeft(this.left()); + this.hBar.setBottom(this.bottom()); + this.vBar.setHeight(this.height() - this.hBar.height()); + this.vBar.setRight(this.right()); + this.vBar.setTop(this.top()); +}; + +TableMorph.prototype.buildCells = function () { + // also populate cells with the correct data and + // arrange the layout of cells all in one pass + var cell, r, c, + pos = this.position(); + + // delete all existing cells + this.children = []; + + // create cells + for (c = 0; c <= this.columns.length; c += 1) { + for (r = 0; r <= this.rows; r += 1) { + cell = new TableCellMorph( + this.table.get( + !c ? c : c + this.startCol - 1, + !r ? r : r + this.startRow - 1 + ), + new Point( + !c ? this.rowLabelWidth + : this.colWidth(c + this.startCol - 1), + !r ? this.colLabelHeight : this.rowHeight + ), + !(r && c), // isLabel + false // should be list + ); + cell.setPosition( + new Point( + !c ? this.padding + : this.columns[c - 1], + !r ? this.padding : + this.padding * 2 + this.colLabelHeight + + ((r - 1) * (this.rowHeight + this.padding)) + ).add(pos) + ); + this.add(cell); + if (isSnapObject(cell.getData())) { + this.wantsUpdate = true; + } + } + } + this.add(this.hBar); + this.add(this.vBar); + this.updateScrollBars(); + this.changed(); +}; + +TableMorph.prototype.drawData = function (noScrollUpdate) { + // redraw all cells with their current data or label + var cell, cellIdx = 0, r, c; + for (c = 0; c <= this.columns.length; c += 1) { + for (r = 0; r <= this.rows; r += 1) { + cell = this.children[cellIdx]; + cellIdx += 1; + cell.setData( + this.table.get( + !c ? c : c + this.startCol - 1, + !r ? r : r + this.startRow - 1 + ) + ); + if (isSnapObject(cell.getData())) { + this.wantsUpdate = true; + } + } + } + if (!noScrollUpdate) {this.updateScrollBars(); } + this.changed(); +}; + +// TableMorph scrolling + +TableMorph.prototype.scroll = function (xSteps, ySteps) { + this.showData( + Math.min( + this.maxStartCol, + Math.max(1, this.startCol + Math.round(xSteps)) + ), + Math.min( + this.maxStartRow, + Math.max(1, this.startRow + Math.round(ySteps)) + ) + ); + this.updateScrollBars(); +}; + +TableMorph.prototype.showData = function (startCol, startRow, noScrollUpdate) { + var c = startCol || this.startCol, + r = startRow || this.startRow; + if (c === this.startCol) { + if (r === this.startRow) {return; } // no change + this.startRow = r; + this.rows = this.visibleRows(); + this.drawData(noScrollUpdate); + } else { + this.startCol = c; + this.startRow = r; + this.rows = this.visibleRows(); + if (this.colWidths.length) { + this.columns = this.columnsLayout(); + this.buildCells(); + } else { + this.drawData(noScrollUpdate); + } + } +}; + +// TableMorph stepping + +TableMorph.prototype.step = function () { + if (this.dragAnchor) { + this.shiftCells(this.world().hand.position()); + } else if (this.resizeAnchor) { + this.resizeCells(this.world().hand.position()); + } + this.update(); +}; + +TableMorph.prototype.update = function () { + var oldCols, oldRows, + version = this.table instanceof List ? + this.table.version(this.startRow, this.rows) + : this.table.lastChanged; + if (this.tableVersion === version && !this.wantsUpdate) { + return; + } + this.wantsUpdate = false; + if (this.table instanceof List) { + oldCols = this.columns.length; + oldRows = this.rows; + this.rowLabelWidth = this.rowLabelsWidth(); + this.columns = this.columnsLayout(); + this.rows = this.visibleRows(); + if (this.columns.length !== oldCols || (this.rows !== oldRows)) { + this.buildCells(); + } else { + this.drawData(); + } + } else { // Table + this.drawData(); + } + this.tableVersion = version; +}; + +// TableMorph layout helpers (all private): + +TableMorph.prototype.rowLabelsWidth = function () { + var ctx = newCanvas().getContext('2d'); + ctx.font = 'italic ' + SyntaxElementMorph.prototype.fontSize + + 'px Helvetica, Arial, sans-serif'; + return Math.max( + 0, + Math.max.apply( + null, + this.table.columnNames().map(function (name) { + return name ? ctx.measureText(name).width : 0; + }) + ) + ) || ctx.measureText(this.table.rows().toString()).width + + (6 * SyntaxElementMorph.prototype.scale); +}; + +TableMorph.prototype.columnsLayout = function () { + // determines and maxStartCol and + // modifies startCol if needed + var c = [], + x = this.padding * 2 + this.rowLabelWidth, + colNum, + w; + + // determine maxStartCol + colNum = this.table.cols(); + w = x; + while (w < this.width() && colNum > 0) { + w += this.colWidth(colNum); + colNum -= 1; + } + if (colNum === 0 && (w < this.width())) { + this.maxStartCol = 1; + } else { + this.maxStartCol = Math.min(colNum + 2, this.table.cols()); + } + + // determine the left position of every shown column + this.startCol = Math.min(this.startCol, this.maxStartCol); + colNum = this.startCol; + while (x < this.width() && + (colNum < (this.table.cols() + this.startCol)) + ) { + w = this.colWidth(colNum); + c.push(x); + x += w; + x += this.padding; + colNum += 1; + } + return c; +}; + +TableMorph.prototype.colWidth = function (col) { + return this.colWidths[col - 1] || this.globalColWidth; +}; + +TableMorph.prototype.visibleRows = function () { + // determines maxStartRow and + // modifies startRow if needed + var rest = this.height() - this.colLabelHeight - this.padding, + possible; + if (rest < 0) {return 0; } + possible = Math.ceil(rest / (this.rowHeight + this.padding)); + this.maxStartRow = Math.max(1, this.table.rows() - possible + 2); + this.startRow = Math.min(this.startRow, this.maxStartRow); + return Math.min(this.table.rows(), possible); +}; + +TableMorph.prototype.globalExtent = function () { + var i, + w = this.rowLabelsWidth() + 2, + cols = this.table.cols(); + for (i = 0; i < cols; i += 1) { + w += this.colWidth(i + 1); + w += this.padding; + } + if (cols === 1) { + w += this.scrollBarSize; + w += this.padding * 2; + } + return new Point( + w + this.padding, + this.colLabelHeight + (this.padding * 2) + + ((this.rowHeight + this.padding) * this.table.rows()) + ); +}; + +// TableMorph events: + +TableMorph.prototype.mouseScroll = function (y, x) { + this.scroll( + -(+x * MorphicPreferences.mouseScrollAmount / 4), + -(+y * MorphicPreferences.mouseScrollAmount) + ); +}; + +TableMorph.prototype.mouseDownLeft = function (pos) { + var rel = pos.subtract(this.position()); + if (rel.x <= this.rowLabelWidth || (rel.y <= this.colLabelHeight)) { + // resize cells + if (this.world().currentKey === 16) { // shiftClicked + this.resizeCol = 0; + } else { + this.resizeCol = this.columnAt(rel.x); + } + this.resizeRow = (rel.y > (this.colLabelHeight)); + this.resizeAnchor = pos; + } else { + // shift the viewed portion + this.resizeRow = null; + this.dragAnchor = pos; + } +}; + +TableMorph.prototype.mouseClickLeft = function (pos) { + this.dragAnchor = null; + this.resizeAnchor = null; + this.resizeRow = null; +}; + +TableMorph.prototype.mouseLeaveDragging = function (pos) { + this.dragAnchor = null; + this.resizeAnchor = null; + this.resizeRow = null; +}; + +TableMorph.prototype.mouseDoubleClick = function (pos) { + if (this.parentThatIsA(TableDialogMorph)) { + this.escalateEvent('mouseDoubleClick', pos); + } else { + new TableDialogMorph( + this.table, + this.globalColWidth, + this.colWidths, + this.rowHeight + ).popUp(this.world()); + } +}; + +// TableMorph scrolling and resizing cells by "hand" + +TableMorph.prototype.shiftCells = function (pos) { + var delta = this.dragAnchor.subtract(pos), + scrollX = Math.round(delta.x / this.globalColWidth), + scrollY = Math.round(delta.y / this.rowHeight); + if (scrollX || scrollY) { + this.scroll(scrollX, scrollY); + this.dragAnchor = pos; + } +}; + +TableMorph.prototype.resizeCells = function (pos) { + var delta = pos.subtract(this.resizeAnchor), + i; + + if (this.resizeCol) { + this.colWidths[this.resizeCol - 1] = Math.max( + 16, + (this.colWidths[this.resizeCol - 1] || this.globalColWidth) + + delta.x + ); + } else if (this.resizeRow) { + this.rowHeight = Math.max(16, this.rowHeight + delta.y); + } else { + this.globalColWidth = Math.max(16, this.globalColWidth + delta.x); + for (i = 0; i < this.colWidths.length; i += 1) { + if (this.colWidths[i]) { + this.colWidths[i] = Math.max( + 16, + this.colWidths[i] + delta.x + ); + } + } + } + if (this.highContrast) { + this.drawNew(); + } else { + this.rowLabelWidth = this.rowLabelsWidth(); + this.columns = this.columnsLayout(); + this.rows = this.visibleRows(); + this.buildCells(); + } + this.resizeAnchor = pos; +}; + +TableMorph.prototype.columnAt = function (relativeX) { + var c = 0; + if (relativeX < (this.columns[0])) { + return 0; + } + while (relativeX > this.columns[c]) { + c += 1; + } + return c + this.startCol - 1; +}; + +// TableMorph context menu + +TableMorph.prototype.userMenu = function () { + var menu = new MenuMorph(this); + if (this.parentThatIsA(TableDialogMorph)) { + if (this.colWidths.length) { + menu.addItem('reset columns', 'resetColumns'); + menu.addLine(); + } + menu.addItem('open in another dialog...', 'openInDialog'); + return menu; + } + + if (this.colWidths.length) { + menu.addItem('reset columns', 'resetColumns'); + } + menu.addItem('list view...', 'showListView'); + menu.addLine(); + menu.addItem('open in dialog...', 'openInDialog'); + return menu; +}; + +TableMorph.prototype.resetColumns = function () { + this.colWidths = []; + if (this.highContrast) { + this.drawNew(); + } else { + this.rowLabelWidth = this.rowLabelsWidth(); + this.columns = this.columnsLayout(); + this.rows = this.visibleRows(); + this.buildCells(); + } +}; + +TableMorph.prototype.openInDialog = function () { + new TableDialogMorph( + this.table, + this.globalColWidth, + this.colWidths, + this.rowHeight + ).popUp(this.world()); +}; + +TableMorph.prototype.showListView = function () { + var view = this.parentThatIsAnyOf([ + SpriteBubbleMorph, + SpeechBubbleMorph, + CellMorph + ]); + if (!view) {return; } + if (view instanceof SpriteBubbleMorph) { + view.changed(); + 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 + view.drawNew(true); + view.contentsMorph.expand(this.extent()); + } + view.fixLayout(); +}; + +// TableMorph updating: + +TableMorph.prototype.show = function () { + TableMorph.uber.show.call(this); + this.updateScrollBars(); +}; + +// TableFrameMorph ///////////////////////////////////////////////////////// + +// a UI for table morphs, for re-sizing tables and their columns + +// TableFrameMorph inherits from Morph: + +TableFrameMorph.prototype = new Morph(); +TableFrameMorph.prototype.constructor = TableFrameMorph; +TableFrameMorph.uber = Morph.prototype; + +// TableFrameMorph instance creation: + +function TableFrameMorph(tableMorph, noResize) { + this.init(tableMorph, noResize); +} + +TableFrameMorph.prototype.init = function (tableMorph, noResize) { + // additional properties: + this.tableMorph = tableMorph; + this.handle = null; + + // initialize inherited properties: + TableFrameMorph.uber.init.call(this, true); + + // override inherited properites: + this.color = 'transparent'; + this.noticesTransparentClick = false; + this.bounds = this.tableMorph.bounds.copy(); + this.add(this.tableMorph); + + if (!noResize) { + this.handle = new HandleMorph( + this, // target + 80, // minX + 25, // minY + null, // insetX + null // insetY + ); + } + + this.drawNew(); +}; + +TableFrameMorph.prototype.fixLayout = function () { + var ext = this.extent(); + if (this.tableMorph.extent().eq(ext)) {return; } + this.tableMorph.setExtent(this.extent()); + if (this.parent && this.parent.fixLayout) { + this.parent.fixLayout(); + } +}; + +TableFrameMorph.prototype.setExtent = function (aPoint, silently) { + TableFrameMorph.uber.setExtent.call(this, aPoint, silently); + this.fixLayout(); +}; + +// TableFrameMorph result / speech balloon support: + +TableFrameMorph.prototype.expand = function (maxExtent) { + var ext = this.tableMorph.globalExtent(); + if (maxExtent) { + ext = ext.min(maxExtent); + } + this.setExtent(ext); + this.handle.setRight(this.right()); + this.handle.setBottom(this.bottom()); +}; + +// TableDialogMorph inherits from DialogBoxMorph: + +TableDialogMorph.prototype = new DialogBoxMorph(); +TableDialogMorph.prototype.constructor = TableDialogMorph; +TableDialogMorph.uber = DialogBoxMorph.prototype; + +// TableDialogMorph instance creation: + +function TableDialogMorph(data, globalColWidth, colWidths, rowHeight) { + this.init(data, globalColWidth, colWidths, rowHeight); +} + +TableDialogMorph.prototype.init = function ( + data, + globalColWidth, + colWidths, + rowHeight +) { + // additional properties: + this.handle = null; + this.data = data; + this.tableView = null; + + // initialize inherited properties: + TableDialogMorph.uber.init.call(this); + + // override inherited properites: + this.labelString = 'Table view'; + this.createLabel(); + + // build contents + this.buildContents(data, globalColWidth, colWidths, rowHeight); +}; + +TableDialogMorph.prototype.buildContents = function ( + data, + globalColWidth, + colWidths, + rowHeight +) { + this.tableView = new TableMorph( + data, + null, // scrollBarSize + null, // extent + null, // startRow + null, // startCol + globalColWidth, + colWidths, + rowHeight, + null, // colLabelHeight + null // padding + ); + this.addBody(new TableFrameMorph(this.tableView, true)); + this.addButton('ok', 'OK'); +}; + +TableDialogMorph.prototype.setInitialDimensions = function () { + var world = this.world(), + mex = world.extent().subtract(new Point(this.padding, this.padding)), + th = fontHeight(this.titleFontSize) + this.titlePadding * 3, // hm... + bh = this.buttons.height(); + this.setExtent( + this.tableView.globalExtent().add( + new Point(this.padding * 2, this.padding * 2 + th + bh) + ).min(mex).max(new Point(100, 100)) + ); + this.setCenter(this.world().center()); +}; + +TableDialogMorph.prototype.popUp = function (world) { + if (world) { + TableDialogMorph.uber.popUp.call(this, world); + this.setInitialDimensions(); + this.handle = new HandleMorph( + this, + 100, + 100, + this.corner, + this.corner + ); + } +}; + +TableDialogMorph.prototype.fixLayout = + BlockEditorMorph.prototype.fixLayout; diff --git a/threads.js b/threads.js index ee0c8b3b..f19773f8 100644 --- a/threads.js +++ b/threads.js @@ -9,7 +9,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2015 by Jens Mönig + Copyright (C) 2016 by Jens Mönig This file is part of Snap!. @@ -51,39 +51,17 @@ */ -// globals from blocks.js: - -/*global ArgMorph, ArrowMorph, BlockHighlightMorph, BlockMorph, -BooleanSlotMorph, BoxMorph, Color, ColorPaletteMorph, ColorSlotMorph, -CommandBlockMorph, CommandSlotMorph, FrameMorph, HatBlockMorph, -InputSlotMorph, MenuMorph, Morph, MultiArgMorph, Point, -ReporterBlockMorph, ScriptsMorph, ShadowMorph, StringMorph, -SyntaxElementMorph, TextMorph, WorldMorph, blocksVersion, contains, -degrees, detect, getDocumentPositionOf, newCanvas, nop, radians, -useBlurredShadows, ReporterSlotMorph, CSlotMorph, RingMorph, IDE_Morph, -ArgLabelMorph, localize, XML_Element, hex_sha512*/ - -// globals from objects.js: - -/*global StageMorph, SpriteMorph, StagePrompterMorph, Note*/ - -// globals from morphic.js: - -/*global modules, isString, copy, isNil*/ - -// globals from gui.js: - -/*global WatcherMorph*/ - -// globals from lists.js: - -/*global List, ListWatcherMorph*/ - -/*global alert, console*/ - // Global stuff //////////////////////////////////////////////////////// -modules.threads = '2015-July-27'; +/*global ArgMorph, BlockMorph, CommandBlockMorph, CommandSlotMorph, Morph, +MultiArgMorph, Point, ReporterBlockMorph, SyntaxElementMorph, contains, +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, isSnapObject*/ + +modules.threads = '2016-October-27'; var ThreadManager; var Process; @@ -128,18 +106,86 @@ function snapEquals(a, b) { return x === y; } +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. + // 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 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.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 if (action.evaluate) { + return action.evaluate(); + } else { + throw new Error('expecting a block or ring but getting ' + action); + } + 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 /////////////////////////////////////////////////////// function ThreadManager() { this.processes = []; + this.wantsToPause = false; // single stepping support } +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); } }; @@ -147,7 +193,9 @@ ThreadManager.prototype.startProcess = function ( block, isThreadSafe, exportResult, - callback + callback, + isClicked, + rightAway ) { var active = this.findProcess(block), top = block.topBlock(), @@ -159,12 +207,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; }; @@ -224,6 +276,25 @@ ThreadManager.prototype.step = function () { // for sprites that are currently picked up, then filter out any // processes that have been terminated + var isInterrupted; + if (Process.prototype.enableSingleStepping) { + this.processes.forEach(function (proc) { + if (proc.isInterrupted) { + proc.runStep(); + isInterrupted = true; + } else { + proc.lastYield = Date.now(); + } + }); + this.wantsToPause = (Process.prototype.flashTime > 0.5); + if (isInterrupted) { + if (this.wantsToPause) { + this.pauseAll(); + } + return; + } + } + this.processes.forEach(function (proc) { if (!proc.homeContext.receiver.isPickedUp() && !proc.isDead) { proc.runStep(); @@ -236,8 +307,10 @@ ThreadManager.prototype.removeTerminatedProcesses = function () { // and un-highlight their scripts var remaining = []; this.processes.forEach(function (proc) { + var result; if ((!proc.isRunning() && !proc.errorFlag) || proc.isDead) { if (proc.topBlock instanceof BlockMorph) { + proc.unflash(); proc.topBlock.removeHighlight(); } if (proc.prompter) { @@ -246,21 +319,24 @@ ThreadManager.prototype.removeTerminatedProcesses = function () { proc.homeContext.receiver.stopTalking(); } } - - if (proc.topBlock instanceof ReporterBlockMorph) { + if (proc.topBlock instanceof ReporterBlockMorph || + proc.isShowingResult) { + result = proc.homeContext.inputs[0]; if (proc.onComplete instanceof Function) { - proc.onComplete(proc.homeContext.inputs[0]); + proc.onComplete(result); } else { - if (proc.homeContext.inputs[0] instanceof List) { + if (result instanceof List) { proc.topBlock.showBubble( - new ListWatcherMorph( - proc.homeContext.inputs[0] - ), + result.isTable() ? + new TableFrameMorph( + new TableMorph(result, 10) + ) + : new ListWatcherMorph(result), proc.exportResult ); } else { proc.topBlock.showBubble( - proc.homeContext.inputs[0], + result, proc.exportResult ); } @@ -283,6 +359,51 @@ ThreadManager.prototype.findProcess = function (block) { ); }; +ThreadManager.prototype.doWhen = function (block, stopIt) { + if (this.pauseCustomHatBlocks) {return; } + var pred = block.inputs()[0], world; + if (block.removeHighlight()) { + world = block.world(); + if (world) { + world.hand.destroyTemporaries(); + } + } + if (stopIt) {return; } + if ((!block) || this.findProcess(block) + ) {return; } + try { + if (invoke( + pred, + null, + block.receiver(), // needed for shallow copied clones - was null + 50, + '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 + ); + } +}; + +ThreadManager.prototype.toggleSingleStepping = function () { + Process.prototype.enableSingleStepping = + !Process.prototype.enableSingleStepping; + if (!Process.prototype.enableSingleStepping) { + this.processes.forEach(function (proc) { + if (!proc.isPaused) { + proc.unflash(); + } + }); + } +}; + // Process ///////////////////////////////////////////////////////////// /* @@ -324,11 +445,16 @@ ThreadManager.prototype.findProcess = function (block) { isDead boolean indicating a terminated clone process timeout msecs after which to force yield lastYield msecs when the process last yielded + isFirstStep boolean indicating whether on first step - for clones errorFlag boolean indicating whether an error was encountered prompter active instance of StagePrompterMorph 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 @@ -336,23 +462,31 @@ ThreadManager.prototype.findProcess = function (block) { procedureCount number counting procedure call entries, used to tag custom block calls, so "stop block" invocations can catch them + flashingContext for single stepping + isInterrupted boolean, indicates intra-step flashing of blocks */ 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 +Process.prototype.enableSingleStepping = false; // experimental +Process.prototype.flashTime = 0; // experimental -function Process(topBlock, onComplete) { +function Process(topBlock, onComplete, rightAway) { this.topBlock = topBlock || null; 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(); - this.lastYield = Date.now(); + this.lastYield = Date.now(); + this.isFirstStep = true; this.isAtomic = false; this.prompter = null; this.httpRequest = null; @@ -362,6 +496,8 @@ function Process(topBlock, onComplete) { this.exportResult = false; this.onComplete = onComplete || null; this.procedureCount = 0; + this.flashingContext = null; // experimental, for single-stepping + this.isInterrupted = false; // experimental, for single-stepping if (topBlock) { this.homeContext.receiver = topBlock.receiver(); @@ -372,7 +508,9 @@ function Process(topBlock, onComplete) { topBlock.blockSequence(), this.homeContext ); - this.pushContext('doYield'); // highlight top block + if (!rightAway) { + this.pushContext('doYield'); // highlight top block + } } } @@ -384,27 +522,37 @@ 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 if (this.isPaused) { // allow pausing in between atomic steps: return this.pauseStep(); } - this.readyToYield = false; - while (!this.readyToYield + this.isInterrupted = false; + + while (!this.readyToYield && !this.isInterrupted && this.context - && (this.isAtomic ? - (Date.now() - this.lastYield < this.timeout) : true) - ) { + && (Date.now() - this.lastYield < this.timeout) + ) { // also allow pausing inside atomic steps - for PAUSE block primitive: 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(); + this.isFirstStep = false; // make sure to redraw atomic things if (this.isAtomic && @@ -437,13 +585,20 @@ Process.prototype.stop = function () { }; Process.prototype.pause = function () { + if (this.readyToTerminate) { + return; + } this.isPaused = true; + this.flashPausedContext(); if (this.context && this.context.startTime) { this.pauseOffset = Date.now() - this.context.startTime; } }; Process.prototype.resume = function () { + if (!this.enableSingleStepping) { + this.unflash(); + } this.isPaused = false; this.pauseOffset = null; }; @@ -479,15 +634,18 @@ Process.prototype.evaluateContext = function () { return this.evaluateBlock(exp, exp.inputs().length); } if (isString(exp)) { - return this[exp](); + return this[exp].apply(this, this.context.inputs); } this.popContext(); // default: just ignore it }; 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 @@ -497,13 +655,14 @@ Process.prototype.evaluateBlock = function (block, argCount) { if (argCount > inputs.length) { this.evaluateNextInput(block); } else { - if (this[block.selector]) { + if (this.flashContext()) {return; } // yield to flash the block + 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) { @@ -511,7 +670,7 @@ Process.prototype.evaluateBlock = function (block, argCount) { } } else { this.returnValueToParentContext( - rcvr[block.selector].apply(rcvr, inputs) + rcvr[selector].apply(rcvr, inputs) ); this.popContext(); } @@ -526,11 +685,13 @@ Process.prototype.reportOr = function (block) { if (inputs.length < 1) { this.evaluateNextInput(block); } else if (inputs[0]) { + if (this.flashContext()) {return; } this.returnValueToParentContext(true); this.popContext(); } else if (inputs.length < 2) { this.evaluateNextInput(block); } else { + if (this.flashContext()) {return; } this.returnValueToParentContext(inputs[1] === true); this.popContext(); } @@ -542,11 +703,13 @@ Process.prototype.reportAnd = function (block) { if (inputs.length < 1) { this.evaluateNextInput(block); } else if (!inputs[0]) { + if (this.flashContext()) {return; } this.returnValueToParentContext(false); this.popContext(); } else if (inputs.length < 2) { this.evaluateNextInput(block); } else { + if (this.flashContext()) {return; } this.returnValueToParentContext(inputs[1] === true); this.popContext(); } @@ -554,6 +717,10 @@ Process.prototype.reportAnd = function (block) { Process.prototype.doReport = function (block) { var outer = this.context.outerContext; + if (this.flashContext()) {return; } // flash the block here, special form + if (this.isClicked && (block.topBlock() === this.topBlock)) { + this.isShowingResult = true; + } if (this.context.expression.partOfCustomCommand) { this.doStopCustomBlock(); this.popContext(); @@ -608,6 +775,7 @@ Process.prototype.evaluateMultiSlot = function (multiSlot, argCount) { } } }; + Process.prototype.evaluateArgLabel = function (argLabel) { // perform the ID function on an ArgLabelMorph element var inputs = this.context.inputs; @@ -622,6 +790,7 @@ Process.prototype.evaluateArgLabel = function (argLabel) { Process.prototype.evaluateInput = function (input) { // evaluate the input unless it is bound to an implicit parameter var ans; + if (this.flashContext()) {return; } // yield to flash the current argMorph if (input.bindingID) { if (this.isCatchingErrors) { try { @@ -635,10 +804,10 @@ Process.prototype.evaluateInput = function (input) { } else { ans = input.evaluate(); if (ans) { - if (contains( - [CommandSlotMorph, ReporterSlotMorph], - input.constructor - ) || (input instanceof CSlotMorph && !input.isStatic)) { + 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()); } @@ -697,6 +866,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) { @@ -708,8 +878,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())); @@ -745,10 +914,15 @@ Process.prototype.handleError = function (error, element) { (m === element ? '' : 'Inside: ') + error.name + '\n' - + error.message + + error.message, + this.exportResult ); }; +Process.prototype.errorObsolete = function () { + throw new Error('a custom block definition is missing'); +}; + // Process Lambda primitives Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) { @@ -760,7 +934,9 @@ Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) { i = 0; if (topBlock) { - context.expression = topBlock.fullCopy(); + context.expression = this.enableLiveCoding || + this.enableSingleStepping ? + topBlock : topBlock.fullCopy(); context.expression.show(); // be sure to make visible if in app mode if (!isCustomBlock) { @@ -778,7 +954,9 @@ Process.prototype.reify = function (topBlock, parameterNames, isCustomBlock) { } } else { - context.expression = [this.context.expression.fullCopy()]; + context.expression = this.enableLiveCoding || + this.enableSingleStepping ? [this.context.expression] + : [this.context.expression.fullCopy()]; } context.inputs = parameterNames.asArray(); @@ -923,6 +1101,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' @@ -940,8 +1127,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) { @@ -987,13 +1173,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 @@ -1042,6 +1239,15 @@ Process.prototype.reportCallCC = function (aContext) { Process.prototype.runContinuation = function (aContext, args) { var parms = args.asArray(); + + // determine whether the continuations is to show the result + // in a value-balloon becuse the user has directly clicked on a reporter + if (aContext.expression === 'expectReport' && parms.length) { + this.stop(); + this.homeContext.inputs[0] = parms[0]; + return; + } + this.context.parentContext = aContext.copyForContinuationCall(); // passing parameter if any was passed if (parms.length === 1) { @@ -1070,8 +1276,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, @@ -1133,8 +1352,9 @@ Process.prototype.evaluateCustomBlock = function () { if (caller && !caller.tag) { caller.tag = this.procedureCount; } - // yield commands unless explicitly "warped" - if (!this.isAtomic) { + // yield commands unless explicitly "warped" or directly recursive + if (!this.isAtomic && + this.context.expression.definition.isDirectlyRecursive()) { this.readyToYield = true; } } @@ -1152,16 +1372,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()); }; @@ -1329,19 +1553,23 @@ Process.prototype.reportNewList = function (elements) { }; Process.prototype.reportCONS = function (car, cdr) { + // this.assertType(cdr, 'list'); return new List().cons(car, cdr); }; Process.prototype.reportCDR = function (list) { + // this.assertType(list, 'list'); return list.cdr(); }; Process.prototype.doAddToList = function (element, list) { + // this.assertType(list, 'list'); list.add(element); }; Process.prototype.doDeleteFromList = function (index, list) { var idx = index; + // this.assertType(list, 'list'); if (this.inputOption(index) === 'all') { return list.clear(); } @@ -1358,6 +1586,7 @@ Process.prototype.doDeleteFromList = function (index, list) { Process.prototype.doInsertInList = function (element, index, list) { var idx = index; + // this.assertType(list, 'list'); if (index === '') { return null; } @@ -1372,6 +1601,7 @@ Process.prototype.doInsertInList = function (element, index, list) { Process.prototype.doReplaceInList = function (index, list, element) { var idx = index; + // this.assertType(list, 'list'); if (index === '') { return null; } @@ -1386,6 +1616,7 @@ Process.prototype.doReplaceInList = function (index, list, element) { Process.prototype.reportListItem = function (index, list) { var idx = index; + // this.assertType(list, 'list'); if (index === '') { return ''; } @@ -1399,13 +1630,21 @@ Process.prototype.reportListItem = function (index, list) { }; Process.prototype.reportListLength = function (list) { + // this.assertType(list, 'list'); return list.length(); }; Process.prototype.reportListContainsItem = function (list, element) { + // this.assertType(list, 'list'); return list.contains(element); }; +Process.prototype.doShowTable = function (list) { + // experimental + this.assertType(list, 'list'); + new TableDialogMorph(list).popUp(this.blockReceiver().world()); +}; + // Process conditionals primitives Process.prototype.doIf = function () { @@ -1601,6 +1840,7 @@ Process.prototype.doPauseAll = function () { // Process loop primitives Process.prototype.doForever = function (body) { + this.context.inputs = []; // force re-evaluation of C-slot this.pushContext('doYield'); if (body) { this.pushContext(body.blockSequence()); @@ -1738,6 +1978,11 @@ Process.prototype.doWait = function (secs) { this.context.startTime = Date.now(); } if ((Date.now() - this.context.startTime) >= (secs * 1000)) { + if (!this.isAtomic && (secs === 0)) { + // "wait 0 secs" is a plain "yield" + // that can be overridden by "warp" + this.readyToYield = true; + } return null; } this.pushContext('doYield'); @@ -1802,7 +2047,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); } @@ -1833,7 +2078,9 @@ Process.prototype.doStopAllSounds = function () { Process.prototype.doAsk = function (data) { var stage = this.homeContext.receiver.parentThatIsA(StageMorph), - isStage = this.blockReceiver() instanceof StageMorph, + rcvr = this.blockReceiver(), + isStage = rcvr instanceof StageMorph, + isHiddenSprite = rcvr instanceof SpriteMorph && !rcvr.isVisible, activePrompter; stage.keysPressed = {}; @@ -1843,10 +2090,12 @@ Process.prototype.doAsk = function (data) { function (morph) {return morph instanceof StagePrompterMorph; } ); if (!activePrompter) { - if (!isStage) { - this.blockReceiver().bubble(data, false, true); + if (!isStage && !isHiddenSprite) { + rcvr.bubble(data, false, true); } - this.prompter = new StagePrompterMorph(isStage ? data : null); + this.prompter = new StagePrompterMorph( + isStage || isHiddenSprite ? data : null + ); if (stage.scale < 1) { this.prompter.setWidth(stage.width() - 10); } else { @@ -1864,7 +2113,7 @@ Process.prototype.doAsk = function (data) { stage.lastAnswer = this.prompter.inputField.getValue(); this.prompter.destroy(); this.prompter = null; - if (!isStage) {this.blockReceiver().stopTalking(); } + if (!isStage) {rcvr.stopTalking(); } return null; } } @@ -1895,6 +2144,65 @@ Process.prototype.reportURL = function (url) { // Process event messages primitives +Process.prototype.doBroadcast = function (message) { + // messages are user-defined events, and by default global, same as in + // Scratch. An experimental feature, messages can be sent to a single + // sprite or to a list of sprites by using a 2-item list in the message + // slot, where the first slot is a message text, and the second slot + // its recipient(s), identified either by a single name or sprite, or by + // a list of names or sprites (can be a heterogeneous list). + + var stage = this.homeContext.receiver.parentThatIsA(StageMorph), + thisObj, + msg = message, + trg, + rcvrs, + myself = this, + hats = [], + procs = []; + + if (message instanceof List && (message.length() === 2)) { + thisObj = this.blockReceiver(); + msg = message.at(1); + trg = message.at(2); + if (isSnapObject(trg)) { + rcvrs = [trg]; + } else if (isString(trg)) { + // assume the string to be the name of a sprite or the stage + if (trg === stage.name) { + rcvrs = [stage]; + } else { + rcvrs = [this.getOtherObject(trg, thisObj, stage)]; + } + } else if (trg instanceof List) { + // assume all elements to be sprites or sprite names + rcvrs = trg.itemsArray().map(function (each) { + return myself.getOtherObject(each, thisObj, stage); + }); + } else { + return; // abort + } + } else { // global + rcvrs = stage.children.concat(stage); + } + if (msg !== '') { + stage.lastMessage = message; // the actual data structure + rcvrs.forEach(function (morph) { + if (isSnapObject(morph)) { + hats = hats.concat(morph.allHatBlocksFor(msg)); + } + }); + hats.forEach(function (block) { + procs.push(stage.threads.startProcess(block, stage.isThreadSafe)); + }); + } + return procs; +}; + +// old purely global broadcast code, commented out and retained in case +// we need to revert + +/* Process.prototype.doBroadcast = function (message) { var stage = this.homeContext.receiver.parentThatIsA(StageMorph), hats = [], @@ -1903,7 +2211,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)); } }); @@ -1913,6 +2221,7 @@ Process.prototype.doBroadcast = function (message) { } return procs; }; +*/ Process.prototype.doBroadcastAndWait = function (message) { if (!this.context.activeSends) { @@ -1947,6 +2256,24 @@ Process.prototype.reportIsA = function (thing, typeString) { return this.reportTypeOf(thing) === this.inputOption(typeString); }; +Process.prototype.assertType = function (thing, typeString) { + // make sure "thing" is a particular type or any of a number of types + // and raise an error if not + // unused as of now because of performance considerations + var thingType = this.reportTypeOf(thing); + if (thingType === typeString) {return true; } + if (typeString instanceof Array && contains(typeString, thingType)) { + return true; + } + 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; @@ -1965,6 +2292,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(); @@ -2089,18 +2422,16 @@ 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 () { - return true; -}; - -Process.prototype.reportFalse = function () { - return false; +Process.prototype.reportBoolean = function (bool) { + return bool; }; Process.prototype.reportRound = function (n) { @@ -2115,6 +2446,9 @@ Process.prototype.reportMonadic = function (fname, n) { case 'abs': result = Math.abs(x); break; + case 'ceiling': + result = Math.ceil(x); + break; case 'floor': result = Math.floor(x); break; @@ -2209,16 +2543,16 @@ Process.prototype.reportLetter = function (idx, string) { return ''; } var i = +(idx || 0), - str = (string || '').toString(); + str = isNil(string) ? '' : string.toString(); return str[i - 1] || ''; }; -Process.prototype.reportStringSize = function (string) { - if (string instanceof List) { // catch a common user error - return string.length(); +Process.prototype.reportStringSize = function (data) { + if (data instanceof List) { // catch a common user error + return data.length(); } - var str = (string || '').toString(); - return str.length; + + return isNil(data) ? 0 : data.toString().length; }; Process.prototype.reportUnicode = function (string) { @@ -2243,10 +2577,10 @@ Process.prototype.reportTextSplit = function (string, delimiter) { if (!contains(types, delType)) { throw new Error('expecting a text delimiter instead of a ' + delType); } - str = (string || '').toString(); + str = isNil(string) ? '' : string.toString(); switch (this.inputOption(delimiter)) { case 'line': - // Unicode Compliant Line Splitting (Platform independent) + // Unicode compliant line splitting (platform independent) // http://www.unicode.org/reports/tr18/#Line_Boundaries del = /\r\n|[\n\v\f\r\x85\u2028\u2029]/; break; @@ -2264,7 +2598,7 @@ Process.prototype.reportTextSplit = function (string, delimiter) { del = ''; break; default: - del = (delimiter || '').toString(); + del = isNil(delimiter) ? '' : delimiter.toString(); } return new List(str.split(del)); }; @@ -2299,6 +2633,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; @@ -2390,17 +2729,17 @@ 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; } if (thisObj) { if (this.inputOption(name) === 'myself') { - thisObj.createClone(); + thisObj.createClone(!this.isFirstStep); } else { thatObj = this.getOtherObject(name, thisObj); if (thatObj) { - thatObj.createClone(); + thatObj.createClone(!this.isFirstStep); } } } @@ -2449,7 +2788,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); })) { @@ -2466,7 +2812,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) { @@ -2487,7 +2833,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) { @@ -2539,6 +2885,7 @@ Process.prototype.reportAttributeOf = function (attribute, name) { stage; if (thisObj) { + this.assertAlive(thisObj); stage = thisObj.parentThatIsA(StageMorph); if (stage.name === name) { thatObj = stage; @@ -2546,6 +2893,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); } @@ -2573,6 +2921,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 @@ -2580,6 +3061,7 @@ Process.prototype.reportContextFor = function (context, otherObj) { result.receiver = otherObj; if (result.outerContext) { result.outerContext = copy(result.outerContext); + result.outerContext.variables = copy(result.outerContext.variables); result.outerContext.receiver = otherObj; result.outerContext.variables.parentFrame = otherObj.variables; } @@ -2632,6 +3114,9 @@ Process.prototype.reportKeyPressed = function (keyString) { if (this.homeContext.receiver) { stage = this.homeContext.receiver.parentThatIsA(StageMorph); if (stage) { + if (this.inputOption(keyString) === 'any key') { + return Object.keys(stage.keysPressed).length > 0; + } return stage.keysPressed[keyString] !== undefined; } } @@ -2863,6 +3348,67 @@ Process.prototype.reportFrameCount = function () { return this.frameCount; }; +// Process single-stepping + +Process.prototype.flashContext = function () { + var expr = this.context.expression; + if (this.enableSingleStepping && + !this.isAtomic && + expr instanceof SyntaxElementMorph && + !(expr instanceof CommandSlotMorph) && + !this.context.isFlashing && + expr.world()) { + this.unflash(); + expr.flash(); + this.context.isFlashing = true; + this.flashingContext = this.context; + if (this.flashTime > 0 && (this.flashTime <= 0.5)) { + this.pushContext('doIdle'); + this.context.addInput(this.flashTime); + } else { + this.pushContext('doInterrupt'); + } + return true; + } + return false; +}; + +Process.prototype.flashPausedContext = function () { + var flashable = this.context ? this.context.lastFlashable() : null; + if (flashable) { + this.unflash(); + flashable.expression.flash(); + flashable.isFlashing = true; + this.flashingContext = flashable; + } +}; + +Process.prototype.doInterrupt = function () { + this.popContext(); + if (!this.isAtomic) { + this.isInterrupted = true; + } +}; + +Process.prototype.doIdle = function (secs) { + if (!this.context.startTime) { + this.context.startTime = Date.now(); + } + if ((Date.now() - this.context.startTime) < (secs * 1000)) { + this.pushContext('doInterrupt'); + return; + } + this.popContext(); +}; + +Process.prototype.unflash = function () { + if (this.flashingContext) { + this.flashingContext.expression.unflash(); + this.flashingContext.isFlashing = false; + this.flashingContext = null; + } +}; + // Context ///////////////////////////////////////////////////////////// /* @@ -2885,6 +3431,7 @@ Process.prototype.reportFrameCount = function () { (if expression is a BlockMorph) pc the index of the next block to evaluate (if expression is an array) + isContinuation flag for marking a transient continuation context startTime time when the context was first evaluated startValue initial value for interpolated operations activeAudio audio buffer for interpolated operations, don't persist @@ -2893,6 +3440,7 @@ Process.prototype.reportFrameCount = function () { emptySlots caches the number of empty slots for reification tag string or number to optionally identify the Context, as a "return" target (for the "stop block" primitive) + isFlashing flag for single-stepping */ function Context( @@ -2912,12 +3460,14 @@ function Context( } this.inputs = []; this.pc = 0; + this.isContinuation = false; this.startTime = null; this.activeAudio = null; this.activeNote = null; this.isCustomBlock = false; // marks the end of a custom block's stack this.emptySlots = 0; // used for block reification this.tag = null; // lexical catch-tag for custom blocks + this.isFlashing = false; // for single-stepping } Context.prototype.toString = function () { @@ -2981,7 +3531,9 @@ Context.prototype.continuation = function () { } else if (this.parentContext) { cont = this.parentContext; } else { - return new Context(null, 'doYield'); + cont = new Context(null, 'expectReport'); + cont.isContinuation = true; + return cont; } cont = cont.copyForContinuation(); cont.tag = null; @@ -3051,6 +3603,19 @@ Context.prototype.stopMusic = function () { } }; +// Context single-stepping: + +Context.prototype.lastFlashable = function () { + // for experimental single-stepping when pausing + if (this.expression instanceof SyntaxElementMorph && + !(this.expression instanceof CommandSlotMorph)) { + return this; + } else if (this.parentContext) { + return this.parentContext.lastFlashable(); + } + return null; +}; + // Context debugging Context.prototype.stackSize = function () { @@ -3062,16 +3627,18 @@ Context.prototype.stackSize = function () { // Variable ///////////////////////////////////////////////////////////////// -function Variable(value) { +function Variable(value, isTransient) { this.value = value; + this.isTransient = isTransient || false; // prevent value serialization } Variable.prototype.toString = function () { - return 'a Variable [' + this.value + ']'; + return 'a ' + this.isTransient ? 'transient ' : '' + 'Variable [' + + this.value + ']'; }; Variable.prototype.copy = function () { - return new Variable(this.value); + return new Variable(this.value, this.isTransient); }; // VariableFrame /////////////////////////////////////////////////////// diff --git a/tools.xml b/tools.xml index 8d244cfd..b19af1e7 100644 --- a/tools.xml +++ b/tools.xml @@ -1 +1 @@ -
1datamapmany1data lists
1
1
110i
1
cont
catchtag
cont
catchtag
Sprite
Sprite
\ No newline at end of file +LABEL will stamp text on the stage at the given font size. The direction of the text is the direction the sprite is facing, and color will match the pen color.
Hello!12
1datamapmany1data lists
1
1
110i
121
cont
catchtag
cont
catchtag
Sprite
Sprite
\ No newline at end of file diff --git a/widgets.js b/widgets.js index 2bfb4ceb..57574ea7 100644 --- a/widgets.js +++ b/widgets.js @@ -74,7 +74,7 @@ HTMLCanvasElement, fontHeight, SymbolMorph, localize, SpeechBubbleMorph, ArrowMorph, MenuMorph, isString, isNil, SliderMorph, MorphicPreferences, ScrollFrameMorph*/ -modules.widgets = '2015-July-27'; +modules.widgets = '2016-July-19'; var PushButtonMorph; var ToggleButtonMorph; @@ -220,8 +220,8 @@ PushButtonMorph.prototype.drawOutline = function (context) { 0, this.height() ); - outlineStyle.addColorStop(1, 'white'); outlineStyle.addColorStop(0, this.outlineColor.darker().toString()); + outlineStyle.addColorStop(1, 'white'); } else { outlineStyle = this.outlineColor.toString(); } @@ -290,8 +290,8 @@ PushButtonMorph.prototype.drawEdges = function ( this.corner, Math.max(this.corner - this.outline, 0) ); - gradient.addColorStop(1, topColor.toString()); gradient.addColorStop(0, color.toString()); + gradient.addColorStop(1, topColor.toString()); context.strokeStyle = gradient; context.lineCap = 'round'; @@ -352,8 +352,8 @@ PushButtonMorph.prototype.drawEdges = function ( h - this.corner, Math.max(this.corner - this.outline, 0) ); - gradient.addColorStop(1, bottomColor.toString()); gradient.addColorStop(0, color.toString()); + gradient.addColorStop(1, bottomColor.toString()); context.strokeStyle = gradient; context.lineCap = 'round'; @@ -2513,6 +2513,12 @@ DialogBoxMorph.prototype.fixLayout = function () { + this.buttons.height() + this.padding ); + this.silentSetWidth(Math.max( + this.width(), + this.buttons.width() + + (2 * this.padding) + ) + ); this.buttons.setCenter(this.center()); this.buttons.setBottom(this.bottom() - this.padding); } @@ -2760,11 +2766,11 @@ DialogBoxMorph.prototype.drawNew = function () { this.corner, 0 ); - gradient.addColorStop(1, this.color.toString()); gradient.addColorStop( 0, this.color.lighter(this.contrast).toString() ); + gradient.addColorStop(1, this.color.toString()); context.lineCap = 'butt'; context.strokeStyle = gradient; @@ -2781,11 +2787,11 @@ DialogBoxMorph.prototype.drawNew = function () { this.corner, 0 ); - gradient.addColorStop(1, this.color.toString()); gradient.addColorStop( 0, this.color.lighter(this.contrast).toString() ); + gradient.addColorStop(1, this.color.toString()); context.lineCap = 'round'; context.strokeStyle = gradient; @@ -2927,6 +2933,7 @@ AlignmentMorph.prototype.fixLayout = function () { )) ); } + cfb = c.fullBounds(); newBounds = newBounds.merge(cfb); } else { newBounds = cfb;