diff --git a/blocks.js b/blocks.js index 0a4c5ed7..895a7125 100644 --- a/blocks.js +++ b/blocks.js @@ -65,6 +65,7 @@ RingMorph BoxMorph* CommentMorph + ScriptFocusMorph * from morphic.js @@ -155,7 +156,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2015-June-25'; +modules.blocks = '2015-July-30'; var SyntaxElementMorph; var BlockMorph; @@ -182,6 +183,7 @@ var SymbolMorph; var CommentMorph; var ArgLabelMorph; var TextSlotMorph; +var ScriptFocusMorph; WorldMorph.prototype.customMorphs = function () { // add examples to the world's demo menu @@ -624,6 +626,48 @@ SyntaxElementMorph.prototype.topBlock = function () { return this; }; +// SyntaxElementMorph reachable variables + +SyntaxElementMorph.prototype.getVarNamesDict = function () { + var block = this.parentThatIsA(BlockMorph), + rcvr, + tempVars = [], + dict; + + if (!block) { + return {}; + } + rcvr = block.receiver(); + block.allParents().forEach(function (morph) { + if (morph instanceof PrototypeHatBlockMorph) { + tempVars.push.apply( + tempVars, + morph.inputs()[0].inputFragmentNames() + ); + } else if (morph instanceof BlockMorph) { + morph.inputs().forEach(function (inp) { + if (inp instanceof TemplateSlotMorph) { + tempVars.push(inp.contents()); + } else if (inp instanceof MultiArgMorph) { + inp.children.forEach(function (m) { + if (m instanceof TemplateSlotMorph) { + tempVars.push(m.contents()); + } + }); + } + }); + } + }); + if (rcvr) { + dict = rcvr.variables.allNamesDict(); + tempVars.forEach(function (name) { + dict[name] = name; + }); + return dict; + } + return {}; +}; + // SyntaxElementMorph drag & drop: SyntaxElementMorph.prototype.reactToGrabOf = function (grabbedMorph) { @@ -1194,6 +1238,15 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { ); part.isStatic = true; break; + case '%shd': + part = new InputSlotMorph( + null, + false, + 'shadowedVariablesMenu', + true + ); + part.isStatic = true; + break; case '%lst': part = new InputSlotMorph( null, @@ -1829,7 +1882,6 @@ SyntaxElementMorph.prototype.endLayout = function () { this.topBlock().fullChanged(); }; - // BlockMorph ////////////////////////////////////////////////////////// /* @@ -1895,7 +1947,8 @@ SyntaxElementMorph.prototype.endLayout = function () { %att - chameleon colored rectangular drop-down for attributes %fun - chameleon colored rectangular drop-down for math functions %typ - chameleon colored rectangular drop-down for data types - %var - chameleon colored rectangular drop-down for variable names + %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 @@ -2132,6 +2185,8 @@ BlockMorph.prototype.userMenu = function () { world = this.world(), myself = this, shiftClicked = world.currentKey === 16, + proc = this.activeProcess(), + vNames = proc ? proc.context.outerContext.variables.names() : [], alternatives, top, blck; @@ -2232,9 +2287,9 @@ BlockMorph.prototype.userMenu = function () { ); if (this instanceof CommandBlockMorph && this.nextBlock()) { menu.addItem( - this.thumbnail(0.5, 60), + (proc ? this.fullCopy() : this).thumbnail(0.5, 60), function () { - var cpy = this.fullCopy(), + var cpy = myself.fullCopy(), nb = cpy.nextBlock(), ide = myself.parentThatIsA(IDE_Morph); if (nb) {nb.destroy(); } @@ -2260,6 +2315,20 @@ BlockMorph.prototype.userMenu = function () { }, 'open a new window\nwith a picture of this script' ); + if (proc) { + if (vNames.length) { + menu.addLine(); + vNames.forEach(function (vn) { + menu.addItem( + vn + '...', + function () { + proc.doShowVar(vn); + } + ); + }); + } + return menu; + } if (this.parentThatIsA(RingMorph)) { menu.addLine(); menu.addItem("unringify", 'unringify'); @@ -2950,6 +3019,17 @@ BlockMorph.prototype.getHighlight = function () { return null; }; +BlockMorph.prototype.outline = function (color, border) { + var highlight = new BlockHighlightMorph(), + fb = this.fullBounds(), + edge = border; + highlight.setExtent(fb.extent().add(edge * 2)); + highlight.color = color; + highlight.image = this.highlightImage(color, border); + highlight.setPosition(fb.origin.subtract(new Point(edge, edge))); + return highlight; +}; + // BlockMorph zebra coloring BlockMorph.prototype.fixBlockColor = function (nearestBlock, isForced) { @@ -3023,6 +3103,12 @@ BlockMorph.prototype.alternateBlockColor = function () { this.fixChildrensBlockColor(true); // has issues if not forced }; +BlockMorph.prototype.ghost = function () { + this.setColor( + SpriteMorph.prototype.blockColor[this.category].lighter(35) + ); +}; + BlockMorph.prototype.fixLabelColor = function () { if (this.zebraContrast > 0 && this.category) { var clr = SpriteMorph.prototype.blockColor[this.category]; @@ -3091,12 +3177,20 @@ BlockMorph.prototype.fullCopy = function () { return ans; }; +BlockMorph.prototype.reactToTemplateCopy = function () { + this.forceNormalColoring(); +}; + // BlockMorph events BlockMorph.prototype.mouseClickLeft = function () { var top = this.topBlock(), receiver = top.receiver(), + shiftClicked = this.world().currentKey === 16, stage; + if (shiftClicked && !this.isTemplate) { + return this.focus(); + } if (top instanceof PrototypeHatBlockMorph) { return top.mouseClickLeft(); } @@ -3108,6 +3202,37 @@ BlockMorph.prototype.mouseClickLeft = function () { } }; +BlockMorph.prototype.focus = function () { + var scripts = this.parentThatIsA(ScriptsMorph), + world = this.world(), + focus; + if (!scripts || !ScriptsMorph.prototype.enableKeyboard) {return; } + if (scripts.focus) {scripts.focus.stopEditing(); } + world.stopEditing(); + focus = new ScriptFocusMorph(scripts, this); + scripts.focus = focus; + focus.getFocus(world); + if (this instanceof HatBlockMorph) { + focus.nextCommand(); + } +}; + +BlockMorph.prototype.activeProcess = function () { + var top = this.topBlock(), + receiver = top.receiver(), + stage; + if (top instanceof PrototypeHatBlockMorph) { + return null; + } + if (receiver) { + stage = receiver.parentThatIsA(StageMorph); + if (stage) { + return stage.threads.findProcess(top); + } + } + return null; +}; + // BlockMorph thumbnail BlockMorph.prototype.thumbnail = function (scale, clipWidth) { @@ -4794,6 +4919,7 @@ ScriptsMorph.uber = FrameMorph.prototype; ScriptsMorph.prototype.cleanUpMargin = 20; ScriptsMorph.prototype.cleanUpSpacing = 15; ScriptsMorph.prototype.isPreferringEmptySlots = true; +ScriptsMorph.prototype.enableKeyboard = true; // ScriptsMorph instance creation: @@ -4813,6 +4939,9 @@ ScriptsMorph.prototype.init = function (owner) { this.lastPreservedBlocks = null; this.lastNextBlock = null; + // keyboard editing support: + this.focus = null; + ScriptsMorph.uber.init.call(this); this.setColor(new Color(70, 70, 70)); }; @@ -4823,6 +4952,9 @@ ScriptsMorph.prototype.fullCopy = function () { var cpy = new ScriptsMorph(), pos = this.position(), child; + if (this.focus) { + this.focus.stopEditing(); + } this.children.forEach(function (morph) { if (!morph.block) { // omit anchored comments child = morph.fullCopy(); @@ -4842,13 +4974,18 @@ ScriptsMorph.prototype.fullCopy = function () { // ScriptsMorph stepping: ScriptsMorph.prototype.step = function () { - var hand = this.world().hand, + var world = this.world(), + hand = world.hand, block; if (this.feedbackMorph.parent) { this.feedbackMorph.destroy(); this.feedbackMorph.parent = null; } + if (this.focus && (!world.keyboardReceiver || + world.keyboardReceiver instanceof StageMorph)) { + this.focus.getFocus(world); + } if (hand.children.length === 0) { return null; } @@ -5311,6 +5448,27 @@ ScriptsMorph.prototype.reactToDropOf = function (droppedMorph, hand) { this.adjustBounds(); }; +// ScriptsMorph events + +ScriptsMorph.prototype.mouseClickLeft = function (pos) { + var shiftClicked = this.world().currentKey === 16; + if (shiftClicked) { + return this.edit(pos); + } + if (this.focus) {this.focus.stopEditing(); } +}; + +// ScriptsMorph keyboard support + +ScriptsMorph.prototype.edit = function (pos) { + var world = this.world(); + if (this.focus) {this.focus.stopEditing(); } + world.stopEditing(); + if (!ScriptsMorph.prototype.enableKeyboard) {return; } + this.focus = new ScriptFocusMorph(this, this, pos); + this.focus.getFocus(world); +}; + // ArgMorph ////////////////////////////////////////////////////////// /* @@ -6575,7 +6733,7 @@ InputSlotMorph.prototype.setContents = function (aStringOrFloat) { // InputSlotMorph drop-down menu: -InputSlotMorph.prototype.dropDownMenu = function () { +InputSlotMorph.prototype.dropDownMenu = function (enableKeyboard) { var choices = this.choices, key, menu = new MenuMorph( @@ -6606,7 +6764,12 @@ InputSlotMorph.prototype.dropDownMenu = function () { } } if (menu.items.length > 0) { - menu.popUpAtHand(this.world()); + if (enableKeyboard) { + menu.popup(this.world(), this.bottomLeft()); + menu.getFocus(); + } else { + menu.popUpAtHand(this.world()); + } } else { return null; } @@ -6861,46 +7024,6 @@ InputSlotMorph.prototype.soundsMenu = function () { return dict; }; -InputSlotMorph.prototype.getVarNamesDict = function () { - var block = this.parentThatIsA(BlockMorph), - rcvr, - tempVars = [], - dict; - - if (!block) { - return {}; - } - rcvr = block.receiver(); - block.allParents().forEach(function (morph) { - if (morph instanceof PrototypeHatBlockMorph) { - tempVars.push.apply( - tempVars, - morph.inputs()[0].inputFragmentNames() - ); - } else if (morph instanceof BlockMorph) { - morph.inputs().forEach(function (inp) { - if (inp instanceof TemplateSlotMorph) { - tempVars.push(inp.contents()); - } else if (inp instanceof MultiArgMorph) { - inp.children.forEach(function (m) { - if (m instanceof TemplateSlotMorph) { - tempVars.push(m.contents()); - } - }); - } - }); - } - }); - if (rcvr) { - dict = rcvr.variables.allNamesDict(); - tempVars.forEach(function (name) { - dict[name] = name; - }); - return dict; - } - return {}; -}; - InputSlotMorph.prototype.setChoices = function (dict, readonly) { // externally specify choices and read-only status, // used for custom blocks @@ -6918,6 +7041,21 @@ InputSlotMorph.prototype.setChoices = function (dict, readonly) { this.fixLayout(); }; +InputSlotMorph.prototype.shadowedVariablesMenu = function () { + var block = this.parentThatIsA(BlockMorph), + rcvr, + dict = {}; + + if (!block) {return dict; } + rcvr = block.receiver(); + if (rcvr) { + rcvr.inheritedVariableNames(true).forEach(function (name) { + dict[name] = name; + }); + } + return dict; +}; + // InputSlotMorph layout: InputSlotMorph.prototype.fixLayout = function () { @@ -10981,3 +11119,738 @@ CommentMorph.prototype.destroy = function () { CommentMorph.prototype.stackHeight = function () { return this.height(); }; + +// ScriptFocusMorph ////////////////////////////////////////////////////////// + +/* + I offer keyboard navigation for syntax elements, blocks and scripts: + + activate: + - shift + click on a scripting pane's background + - shift + click on any block + - shift + enter in the IDE's edit mode + + stop editing: + - left-click on scripting pane's background + - esc + + navigate among scripts: + - tab: next script + - backtab (shift + tab): last script + + start editing a new script: + - shift + enter + + navigate among commands within a script: + - down arrow: next command + - up arrow: last command + + navigate among all elements within a script: + - right arrow: next element (block or input) + - left arrow: last element + + move the currently edited script (stack of blocks): + - shift + arrow keys (left, right, up, down) + + editing scripts: + + - backspace: + * delete currently focused reporter + * delete command above current insertion mark (blinking) + * collapse currently focused variadic input by one element + + - enter: + * edit currently focused input slot + * expand currently focused variadic input by one element + + - space: + * activate currently focused input slot's pull-down menu, if any + * show a menu of reachable variables for the focused input or reporter + + - any other key: + start searching for insertable matching blocks + + - in menus triggered by this feature: + * navigate with up / down arrow keys + * trigger selection with enter + * cancel menu with esc + + - in the search bar triggered b this feature: + * keep typing / deleting to narrow and update matches + * navigate among shown matches with up / down arrow keys + * insert selected match at the focus' position with enter + * cancel searching and inserting with esc + + running the currently edited script: + * shift+ctrl+enter simulates clicking the edited script with the mouse +*/ + +// ScriptFocusMorph inherits from BoxMorph: + +ScriptFocusMorph.prototype = new BoxMorph(); +ScriptFocusMorph.prototype.constructor = ScriptFocusMorph; +ScriptFocusMorph.uber = BoxMorph.prototype; + +// ScriptFocusMorph instance creation: + +function ScriptFocusMorph(editor, initialElement, position) { + this.init(editor, initialElement, position); +} + +ScriptFocusMorph.prototype.init = function ( + editor, + initialElement, + position +) { + this.editor = editor; // a ScriptsMorph + this.element = initialElement; + this.atEnd = false; + ScriptFocusMorph.uber.init.call(this); + if (this.element instanceof ScriptsMorph) { + this.setPosition(position); + } +}; + +// ScriptFocusMorph keyboard focus: + +ScriptFocusMorph.prototype.getFocus = function (world) { + if (!world) {world = this.world(); } + if (world && world.keyboardReceiver !== this) { + world.stopEditing(); + } + world.keyboardReceiver = this; + this.fixLayout(); +}; + +// ScriptFocusMorph layout: + +ScriptFocusMorph.prototype.fixLayout = function () { + this.changed(); + if (this.element instanceof CommandBlockMorph || + this.element instanceof CommandSlotMorph || + this.element instanceof ScriptsMorph) { + this.manifestStatement(); + } else { + this.manifestExpression(); + } + this.editor.add(this); // come to front + this.scrollIntoView(); + this.changed(); +}; + +ScriptFocusMorph.prototype.manifestStatement = function () { + var newScript = this.element instanceof ScriptsMorph, + y = this.element.top(); + this.border = 0; + this.edge = 0; + this.alpha = 1; + this.color = this.editor.feedbackColor; + this.setExtent(new Point( + newScript ? + SyntaxElementMorph.prototype.hatWidth : this.element.width(), + Math.max( + SyntaxElementMorph.prototype.corner, + SyntaxElementMorph.prototype.feedbackMinHeight + ) + )); + if (this.element instanceof CommandSlotMorph) { + y += SyntaxElementMorph.prototype.corner; + } else if (this.atEnd) { + y = this.element.bottom(); + } + if (!newScript) { + this.setPosition(new Point( + this.element.left(), + y + )); + } + this.fps = 2; + this.show(); + this.step = function () { + this.toggleVisibility(); + }; +}; + +ScriptFocusMorph.prototype.manifestExpression = function () { + this.edge = SyntaxElementMorph.prototype.rounding; + this.border = Math.max( + SyntaxElementMorph.prototype.edge, + 3 + ); + this.color = this.editor.feedbackColor.copy(); + this.color.a = 0.5; + this.borderColor = this.editor.feedbackColor; + + this.bounds = this.element.fullBounds() + .expandBy(Math.max( + SyntaxElementMorph.prototype.edge * 2, + SyntaxElementMorph.prototype.reporterDropFeedbackPadding + )); + this.drawNew(); + delete this.fps; + delete this.step; + this.show(); +}; + +// ScriptFocusMorph editing + +ScriptFocusMorph.prototype.trigger = function () { + var current = this.element; + if (current instanceof MultiArgMorph) { + if (current.arrows().children[1].isVisible) { + current.addInput(); + this.fixLayout(); + } + return; + } + if (current.parent instanceof TemplateSlotMorph) { + current.mouseClickLeft(); + return; + } + if (current instanceof InputSlotMorph) { + if (!current.isReadOnly) { + delete this.fps; + delete this.step; + this.hide(); + current.contents().edit(); + this.world().onNextStep = function () { + current.contents().selectAll(); + }; + } else if (current.choices) { + current.dropDownMenu(true); + delete this.fps; + delete this.step; + this.hide(); + } + } +}; + +ScriptFocusMorph.prototype.menu = function () { + var current = this.element; + if (current instanceof InputSlotMorph && current.choices) { + current.dropDownMenu(true); + delete this.fps; + delete this.step; + this.hide(); + } else { + this.insertVariableGetter(); + } +}; + +ScriptFocusMorph.prototype.deleteLastElement = function () { + var current = this.element; + if (current.parent instanceof ScriptsMorph) { + if (this.atEnd || current instanceof ReporterBlockMorph) { + current.destroy(); + this.element = this.editor; + this.atEnd = false; + } + } else if (current instanceof MultiArgMorph) { + if (current.arrows().children[0].isVisible) { + current.removeInput(); + } + } else if (current instanceof ReporterBlockMorph) { + if (!current.isTemplate) { + this.lastElement(); + current.prepareToBeGrabbed(); + current.destroy(); + } + } else if (current instanceof CommandBlockMorph) { + if (this.atEnd) { + this.element = current.parent; + current.userDestroy(); + } else { + if (current.parent instanceof CommandBlockMorph) { + current.parent.userDestroy(); + } + } + } + this.editor.adjustBounds(); + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.insertBlock = function (block) { + var pb; + block.isTemplate = false; + block.isDraggable = true; + + if (block.snapSound) { + block.snapSound.play(); + } + + if (this.element instanceof ScriptsMorph) { + this.editor.add(block); + this.element = block; + if (block instanceof CommandBlockMorph) { + block.setLeft(this.left()); + if (block.isStop()) { + block.setTop(this.top()); + } else { + block.setBottom(this.top()); + this.atEnd = true; + } + } else { + block.setCenter(this.center()); + block.setLeft(this.left()); + } + } else if (this.element instanceof CommandBlockMorph) { + if (this.atEnd) { + this.element.nextBlock(block); + this.element = block; + this.fixLayout(); + } else { + // to be done: special case if block.isStop() + pb = this.element.parent; + if (pb instanceof ScriptsMorph) { // top block + block.setLeft(this.element.left()); + block.setBottom(this.element.top() + this.element.corner); + this.editor.add(block); + block.nextBlock(this.element); + this.fixLayout(); + } else if (pb instanceof CommandSlotMorph) { + pb.nestedBlock(block); + } else if (pb instanceof CommandBlockMorph) { + pb.nextBlock(block); + } + } + } else if (this.element instanceof CommandSlotMorph) { + // to be done: special case if block.isStop() + this.element.nestedBlock(block); + this.element = block; + this.atEnd = true; + } else { + pb = this.element.parent; + if (pb instanceof ScriptsMorph) { + this.editor.add(block); + block.setPosition(this.element.position()); + this.element.destroy(); + } else { + pb.replaceInput(this.element, block); + } + this.element = block; + } + block.fixBlockColor(); + this.editor.adjustBounds(); + // block.scrollIntoView(); + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.insertVariableGetter = function () { + var types = this.blockTypes(), + vars, + myself = this, + menu = new MenuMorph(); + if (!types || !contains(types, 'reporter')) { + return; + } + vars = InputSlotMorph.prototype.getVarNamesDict.call(this.element); + Object.keys(vars).forEach(function (vName) { + var block = SpriteMorph.prototype.variableBlock(vName); + block.addShadow(new Point(3, 3)); + menu.addItem( + block, + function () { + block.removeShadow(); + myself.insertBlock(block); + } + ); + }); + if (menu.items.length > 0) { + menu.popup(this.world(), this.element.bottomLeft()); + menu.getFocus(); + } +}; + +ScriptFocusMorph.prototype.stopEditing = function () { + this.editor.focus = null; + this.world().keyboardReceiver = null; + this.destroy(); +}; + +// ScriptFocusMorph navigation + +ScriptFocusMorph.prototype.lastElement = function () { + var items = this.items(), + idx; + if (!items.length) { + this.shiftScript(new Point(-50, 0)); + return; + } + if (this.atEnd) { + this.element = items[items.length - 1]; + this.atEnd = false; + } else { + idx = items.indexOf(this.element) - 1; + if (idx < 0) {idx = items.length - 1; } + this.element = items[idx]; + } + if (this.element instanceof CommandSlotMorph && + this.element.nestedBlock()) { + this.lastElement(); + } else if (this.element instanceof HatBlockMorph) { + if (items.length > 1) { + this.lastElement(); + } else { + this.atEnd = true; + } + } + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.nextElement = function () { + var items = this.items(), idx, nb; + if (!items.length) { + this.shiftScript(new Point(50, 0)); + return; + } + idx = items.indexOf(this.element) + 1; + if (idx >= items.length) { + idx = 0; + } + this.atEnd = false; + this.element = items[idx]; + if (this.element instanceof CommandSlotMorph) { + nb = this.element.nestedBlock(); + if (nb) {this.element = nb; } + } else if (this.element instanceof HatBlockMorph) { + if (items.length === 1) { + this.atEnd = true; + } else { + this.nextElement(); + } + } + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.lastCommand = function () { + var cm = this.element.parentThatIsA(CommandBlockMorph), + pb; + if (!cm) { + if (this.element instanceof ScriptsMorph) { + this.shiftScript(new Point(0, -50)); + } + return; + } + if (this.element instanceof CommandBlockMorph) { + if (this.atEnd) { + this.atEnd = false; + } else { + pb = cm.parent.parentThatIsA(CommandBlockMorph); + if (pb) { + this.element = pb; + } else { + pb = cm.topBlock().bottomBlock(); + if (pb) { + this.element = pb; + this.atEnd = true; + } + } + } + } else { + this.element = cm; + this.atEnd = false; + } + if (this.element instanceof HatBlockMorph && !this.atEnd) { + this.lastCommand(); + } + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.nextCommand = function () { + var cm = this.element, + tb, + nb, + cs; + if (cm instanceof ScriptsMorph) { + this.shiftScript(new Point(0, 50)); + return; + } + while (!(cm instanceof CommandBlockMorph)) { + cm = cm.parent; + if (cm instanceof ScriptsMorph) { + return; + } + } + if (this.atEnd) { + cs = cm.parentThatIsA(CommandSlotMorph); + if (cs) { + this.element = cs.parentThatIsA(CommandBlockMorph); + this.atEnd = false; + this.nextCommand(); + } else { + tb = cm.topBlock().parentThatIsA(CommandBlockMorph); + if (tb) { + this.element = tb; + this.atEnd = false; + if (this.element instanceof HatBlockMorph) { + this.nextCommand(); + } + } + } + } else { + nb = cm.nextBlock(); + if (nb) { + this.element = nb; + } else { + this.element = cm; + this.atEnd = true; + } + } + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.nextScript = function () { + var scripts = this.sortedScripts(), + idx; + if (scripts.length < 1) {return; } + if (this.element instanceof ScriptsMorph) { + this.element = scripts[0]; + } + idx = scripts.indexOf(this.element.topBlock()) + 1; + if (idx >= scripts.length) {idx = 0; } + this.element = scripts[idx]; + this.element.scrollIntoView(); + this.atEnd = false; + if (this.element instanceof HatBlockMorph) { + return this.nextElement(); + } + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.lastScript = function () { + var scripts = this.sortedScripts(), + idx; + if (scripts.length < 1) {return; } + if (this.element instanceof ScriptsMorph) { + this.element = scripts[0]; + } + idx = scripts.indexOf(this.element.topBlock()) - 1; + if (idx < 0) {idx = scripts.length - 1; } + this.element = scripts[idx]; + this.element.scrollIntoView(); + this.atEnd = false; + if (this.element instanceof HatBlockMorph) { + return this.nextElement(); + } + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.shiftScript = function (deltaPoint) { + var tb; + if (this.element instanceof ScriptsMorph) { + this.moveBy(deltaPoint); + } else { + tb = this.element.topBlock(); + if (tb && !(tb instanceof PrototypeHatBlockMorph)) { + tb.moveBy(deltaPoint); + } + } + this.editor.adjustBounds(); + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.newScript = function () { + var pos = this.position(); + if (!(this.element instanceof ScriptsMorph)) { + pos = this.element.topBlock().fullBounds().bottomLeft().add( + new Point(0, 50) + ); + } + this.setPosition(pos); + this.element = this.editor; + this.editor.adjustBounds(); + this.fixLayout(); +}; + +ScriptFocusMorph.prototype.runScript = function () { + if (this.element instanceof ScriptsMorph) {return; } + this.element.topBlock().mouseClickLeft(); +}; + +ScriptFocusMorph.prototype.items = function () { + if (this.element instanceof ScriptsMorph) {return []; } + var script = this.element.topBlock(); + return script.allChildren().filter(function (each) { + return each instanceof SyntaxElementMorph && + !(each instanceof TemplateSlotMorph) && + (!each.isStatic || + each.choices || + each instanceof RingMorph || + each instanceof MultiArgMorph || + each instanceof CommandSlotMorph); + }); +}; + +ScriptFocusMorph.prototype.sortedScripts = function () { + var scripts = this.editor.children.filter(function (each) { + return each instanceof BlockMorph; + }); + scripts.sort(function (a, b) { + // make sure the prototype hat block always stays on top + return a instanceof PrototypeHatBlockMorph ? 0 : a.top() - b.top(); + }); + return scripts; +}; + +// ScriptFocusMorph block types + +ScriptFocusMorph.prototype.blockTypes = function () { + // answer an array of possible block types that fit into + // the current situation, NULL if no block can be inserted + + if (this.element.isTemplate) {return null; } + if (this.element instanceof ScriptsMorph) { + return ['hat', 'command', 'reporter', 'predicate', 'ring']; + } + if (this.element instanceof HatBlockMorph || + this.element instanceof CommandSlotMorph) { + return ['command']; + } + if (this.element instanceof CommandBlockMorph) { + if (this.atEnd && this.element.isStop()) { + return null; + } + if (this.element.parent instanceof ScriptsMorph) { + return ['hat', 'command']; + } + return ['command']; + } + if (this.element instanceof ReporterBlockMorph) { + if (this.element.getSlotSpec() === '%n') { + return ['reporter']; + } + return ['reporter', 'predicate', 'ring']; + } + if (this.element.getSpec() === '%n') { + return ['reporter']; + } + if (this.element.isStatic) { + return null; + } + return ['reporter', 'predicate', 'ring']; +}; + + +// ScriptFocusMorph keyboard events + +ScriptFocusMorph.prototype.processKeyDown = function (event) { + this.processKeyEvent( + event, + this.reactToKeyEvent + ); +}; + +ScriptFocusMorph.prototype.processKeyUp = function (event) { + nop(event); +}; + +ScriptFocusMorph.prototype.processKeyPress = function (event) { + nop(event); +}; + + +ScriptFocusMorph.prototype.processKeyEvent = function (event, action) { + var keyName, ctrl, shift; + + //console.log(event.keyCode); + this.world().hand.destroyTemporaries(); // remove result bubbles, if any + switch (event.keyCode) { + case 8: + keyName = 'backspace'; + break; + case 9: + keyName = 'tab'; + break; + case 13: + keyName = 'enter'; + break; + case 16: + case 17: + case 18: + return; + case 27: + keyName = 'esc'; + break; + case 32: + keyName = 'space'; + break; + case 37: + keyName = 'left arrow'; + break; + case 39: + keyName = 'right arrow'; + break; + case 38: + keyName = 'up arrow'; + break; + case 40: + keyName = 'down arrow'; + break; + default: + keyName = String.fromCharCode(event.keyCode || event.charCode); + } + ctrl = (event.ctrlKey || event.metaKey) ? 'ctrl ' : ''; + shift = event.shiftKey ? 'shift ' : ''; + keyName = ctrl + shift + keyName; + action.call(this, keyName); +}; + +ScriptFocusMorph.prototype.reactToKeyEvent = function (key) { + var evt = key.toLowerCase(), + shift = 50, + types, + vNames; + + // console.log(evt); + switch (evt) { + case 'esc': + return this.stopEditing(); + case 'enter': + return this.trigger(); + case 'shift enter': + return this.newScript(); + case 'ctrl shift enter': + return this.runScript(); + case 'space': + return this.menu(); + case 'left arrow': + return this.lastElement(); + case 'shift left arrow': + return this.shiftScript(new Point(-shift, 0)); + case 'right arrow': + return this.nextElement(); + case 'shift right arrow': + return this.shiftScript(new Point(shift, 0)); + case 'up arrow': + return this.lastCommand(); + case 'shift up arrow': + return this.shiftScript(new Point(0, -shift)); + case 'down arrow': + return this.nextCommand(); + case 'shift down arrow': + return this.shiftScript(new Point(0, shift)); + case 'tab': + return this.nextScript(); + case 'shift tab': + return this.lastScript(); + case 'backspace': + return this.deleteLastElement(); + default: + types = this.blockTypes(); + if (!(this.element instanceof ScriptsMorph) && + contains(types, 'reporter')) { + vNames = Object.keys(this.element.getVarNamesDict()); + } + if (types) { + delete this.fps; + delete this.step; + this.show(); + this.editor.owner.searchBlocks( + key, + types, + vNames, + this + ); + } + } +}; diff --git a/byob.js b/byob.js index 4c39872a..13392b5a 100644 --- a/byob.js +++ b/byob.js @@ -102,11 +102,11 @@ ArrowMorph, PushButtonMorph, contains, InputSlotMorph, ShadowMorph, ToggleButtonMorph, IDE_Morph, MenuMorph, copy, ToggleElementMorph, Morph, fontHeight, StageMorph, SyntaxElementMorph, SnapSerializer, CommentMorph, localize, CSlotMorph, SpeechBubbleMorph, MorphicPreferences, -SymbolMorph, isNil*/ +SymbolMorph, isNil, CursorMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.byob = '2015-June-25'; +modules.byob = '2015-July-28'; // Declarations @@ -1727,8 +1727,9 @@ BlockEditorMorph.prototype.popUp = function () { // BlockEditorMorph ops -BlockEditorMorph.prototype.accept = function () { +BlockEditorMorph.prototype.accept = function (origin) { // check DialogBoxMorph comment for accept() + if (origin instanceof CursorMorph) {return; } if (this.action) { if (typeof this.target === 'function') { if (typeof this.action === 'function') { @@ -1747,7 +1748,8 @@ BlockEditorMorph.prototype.accept = function () { this.close(); }; -BlockEditorMorph.prototype.cancel = function () { +BlockEditorMorph.prototype.cancel = function (origin) { + if (origin instanceof CursorMorph) {return; } //this.refreshAllBlockInstances(); this.close(); }; @@ -1966,8 +1968,13 @@ PrototypeHatBlockMorph.prototype.init = function (definition) { PrototypeHatBlockMorph.prototype.mouseClickLeft = function () { // relay the mouse click to my prototype block to - // pop-up a Block Dialog + // pop-up a Block Dialog, unless the shift key + // is pressed, in which case initiate keyboard + // editing support + if (this.world().currentKey === 16) { // shift-clicked + return this.focus(); + } this.children[0].mouseClickLeft(); }; diff --git a/gui.js b/gui.js index eee2bd51..07ea57f1 100644 --- a/gui.js +++ b/gui.js @@ -43,6 +43,7 @@ TurtleIconMorph CostumeIconMorph WardrobeMorph + StageHandleMorph; credits @@ -65,11 +66,11 @@ ScriptsMorph, isNil, SymbolMorph, BlockExportDialogMorph, BlockImportDialogMorph, SnapTranslator, localize, List, InputSlotMorph, SnapCloud, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512, sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio, -SpeechBubbleMorph*/ +SpeechBubbleMorph, ScriptFocusMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2015-June-25'; +modules.gui = '2015-July-28'; // Declarations @@ -81,11 +82,18 @@ var TurtleIconMorph; var WardrobeMorph; var SoundIconMorph; var JukeboxMorph; +var StageHandleMorph; // Get the full url without "snap.html" -var baseUrl = document.URL.split('/'); -baseUrl.pop(baseUrl.length - 1); -baseUrl = baseUrl.join('/') + '/'; +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)); + // IDE_Morph /////////////////////////////////////////////////////////// @@ -230,6 +238,7 @@ IDE_Morph.prototype.init = function (isAutoFill) { this.spriteBar = null; this.spriteEditor = null; this.stage = null; + this.stageHandle = null; this.corralBar = null; this.corral = null; @@ -975,9 +984,7 @@ IDE_Morph.prototype.createPalette = function (forSearching) { IDE_Morph.prototype.createStage = function () { // assumes that the logo pane has already been created - if (this.stage) { - this.stage.destroy(); - } + if (this.stage) {this.stage.destroy(); } StageMorph.prototype.frameRate = 0; this.stage = new StageMorph(this.globalVariables); this.stage.setExtent(this.stage.dimensions); // dimensions are fixed @@ -992,6 +999,13 @@ IDE_Morph.prototype.createStage = function () { this.add(this.stage); }; +IDE_Morph.prototype.createStageHandle = function () { + // assumes that the stage has already been created + if (this.stageHandle) {this.stageHandle.destroy(); } + this.stageHandle = new StageHandleMorph(this.stage); + this.add(this.stageHandle); +}; + IDE_Morph.prototype.createSpriteBar = function () { // assumes that the categories pane has already been created var rotationStyleButtons = [], @@ -1355,6 +1369,7 @@ IDE_Morph.prototype.createCorral = function () { // assumes the corral bar has already been created var frame, template, padding = 5, myself = this; + this.createStageHandle(); if (this.corral) { this.corral.destroy(); } @@ -1486,10 +1501,10 @@ IDE_Morph.prototype.fixLayout = function (situation) { ) * 10) / 10); this.stage.setCenter(this.center()); } else { -// this.stage.setScale(this.isSmallStage ? 0.5 : 1); this.stage.setScale(this.isSmallStage ? this.stageRatio : 1); this.stage.setTop(this.logo.bottom() + padding); this.stage.setRight(this.right()); + this.stageHandle.fixLayout(); } // spriteBar @@ -1538,7 +1553,12 @@ IDE_Morph.prototype.setProjectName = function (string) { IDE_Morph.prototype.setExtent = function (point) { var padding = new Point(430, 110), minExt, - ext; + ext, + maxWidth, + minWidth, + maxHeight, + minRatio, + maxRatio; // determine the minimum dimensions making sense for the current mode if (this.isAppMode) { @@ -1546,21 +1566,29 @@ IDE_Morph.prototype.setExtent = function (point) { this.controlBar.height() + 10 ); } else { - /* // auto-switches to small stage mode, commented out b/c I don't like it - if (point.x < 910) { - this.isSmallStage = true; - this.stageRatio = 0.5; + if (this.stageRatio > 1) { + minExt = padding.add(StageMorph.prototype.dimensions); + } else { + minExt = padding.add( + StageMorph.prototype.dimensions.multiplyBy(this.stageRatio) + ); } - */ - minExt = this.isSmallStage ? - padding.add(StageMorph.prototype.dimensions.divideBy(2)) - : padding.add(StageMorph.prototype.dimensions); -/* - minExt = this.isSmallStage ? - new Point(700, 350) : new Point(910, 490); -*/ } ext = point.max(minExt); + + // adjust stage ratio if necessary + maxWidth = ext.x - (this.spriteBar.tabBar.fullBounds().right() - + this.left()); + minWidth = SpriteIconMorph.prototype.thumbSize.x * 3; + maxHeight = (ext.y - SpriteIconMorph.prototype.thumbSize.y * 3.5); + minRatio = minWidth / this.stage.dimensions.x; + maxRatio = Math.min( + (maxWidth / this.stage.dimensions.x), + (maxHeight / this.stage.dimensions.y) + ); + this.stageRatio = Math.min(maxRatio, Math.max(minRatio, this.stageRatio)); + + // apply IDE_Morph.uber.setExtent.call(this, ext); this.fixLayout(); }; @@ -1746,6 +1774,9 @@ IDE_Morph.prototype.stopAllScripts = function () { }; IDE_Morph.prototype.selectSprite = function (sprite) { + if (this.currentSprite && this.currentSprite.scripts.focus) { + this.currentSprite.scripts.focus.stopEditing(); + } this.currentSprite = sprite; this.createPalette(); this.createSpriteBar(); @@ -1800,7 +1831,8 @@ IDE_Morph.prototype.applySavedSettings = function () { click = this.getSetting('click'), longform = this.getSetting('longform'), longurls = this.getSetting('longurls'), - plainprototype = this.getSetting('plainprototype'); + plainprototype = this.getSetting('plainprototype'), + keyboard = this.getSetting('keyboard'); // design if (design === 'flat') { @@ -1840,6 +1872,13 @@ IDE_Morph.prototype.applySavedSettings = function () { this.projectsInURLs = false; } + // keyboard editing + if (keyboard) { + ScriptsMorph.prototype.enableKeyboard = true; + } else { + ScriptsMorph.prototype.enableKeyboard = false; + } + // plain prototype labels if (plainprototype) { BlockLabelPlaceHolderMorph.prototype.plainLabel = true; @@ -2346,6 +2385,22 @@ IDE_Morph.prototype.settingsMenu = function () { 'check to enable\nsprite composition', true ); + addPreference( + 'Keyboard Editing', + function () { + ScriptsMorph.prototype.enableKeyboard = + !ScriptsMorph.prototype.enableKeyboard; + if (ScriptsMorph.prototype.enableKeyboard) { + myself.saveSetting('keyboard', true); + } else { + myself.removeSetting('keyboard'); + } + }, + ScriptsMorph.prototype.enableKeyboard, + 'uncheck to disable\nkeyboard editing support', + 'check to enable\nkeyboard editing support', + false + ); menu.addLine(); // everything below this line is stored in the project addPreference( 'Thread safe scripts', @@ -2385,6 +2440,20 @@ IDE_Morph.prototype.settingsMenu = function () { 'check for block\nto text mapping features', false ); + addPreference( + 'Inheritance support', + function () { + StageMorph.prototype.enableInheritance = + !StageMorph.prototype.enableInheritance; + myself.currentSprite.blocksCache.variables = null; + myself.currentSprite.paletteCache.variables = null; + myself.refreshPalette(); + }, + StageMorph.prototype.enableInheritance, + 'uncheck to disable\nsprite inheritance features', + 'check for sprite\ninheritance features', + false + ); menu.popup(world, pos); }; @@ -2497,10 +2566,10 @@ IDE_Morph.prototype.projectMenu = function () { function () { // read a list of libraries from an external file, var libMenu = new MenuMorph(this, 'Import library'), - libUrl = baseUrl + 'libraries/' + 'LIBRARIES'; + libUrl = baseURL + 'libraries/' + 'LIBRARIES'; function loadLib(name) { - var url = baseUrl + 'libraries/' + name + '.xml'; + var url = baseURL + 'libraries/' + name + '.xml'; myself.droppedText(myself.getURL(url), name); } @@ -2617,7 +2686,7 @@ IDE_Morph.prototype.aboutSnap = function () { module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn, world = this.world(); - aboutTxt = 'Snap! 4.0.1\nBuild Your Own Blocks\n\n' + aboutTxt = 'Snap! 4.0.2\nBuild Your Own Blocks\n\n' + 'Copyright \u24B8 2015 Jens M\u00F6nig and ' + 'Brian Harvey\n' + 'jens@moenig.org, bh@cs.berkeley.edu\n\n' @@ -2834,6 +2903,7 @@ IDE_Morph.prototype.newProject = function () { StageMorph.prototype.codeMappings = {}; StageMorph.prototype.codeHeaders = {}; StageMorph.prototype.enableCodeMapping = false; + StageMorph.prototype.enableInheritance = false; SpriteMorph.prototype.useFlatLineEnds = false; this.setProjectName(''); this.projectNotes = ''; @@ -3053,6 +3123,7 @@ IDE_Morph.prototype.rawOpenProjectString = function (str) { StageMorph.prototype.codeMappings = {}; StageMorph.prototype.codeHeaders = {}; StageMorph.prototype.enableCodeMapping = false; + StageMorph.prototype.enableInheritance = false; if (Process.prototype.isCatchingErrors) { try { this.serializer.openProject( @@ -3094,6 +3165,7 @@ IDE_Morph.prototype.rawOpenCloudDataString = function (str) { StageMorph.prototype.codeMappings = {}; StageMorph.prototype.codeHeaders = {}; StageMorph.prototype.enableCodeMapping = false; + StageMorph.prototype.enableInheritance = false; if (Process.prototype.isCatchingErrors) { try { model = this.serializer.parse(str); @@ -3417,6 +3489,7 @@ IDE_Morph.prototype.toggleAppMode = function (appMode) { this.controlBar.projectButton, this.controlBar.settingsButton, this.controlBar.stageSizeButton, + this.stageHandle, this.corral, this.corralBar, this.spriteEditor, @@ -3440,6 +3513,9 @@ IDE_Morph.prototype.toggleAppMode = function (appMode) { morph.hide(); } }); + if (world.keyboardReceiver instanceof ScriptFocusMorph) { + world.keyboardReceiver.stopEditing(); + } } else { this.setColor(this.backgroundColor); this.controlBar.setColor(this.frameColor); @@ -3476,36 +3552,32 @@ IDE_Morph.prototype.toggleStageSize = function (isSmall) { var myself = this, smallRatio = 0.5, world = this.world(), - shiftClicked = (world.currentKey === 16); + shiftClicked = (world.currentKey === 16), + altClicked = (world.currentKey === 18); function toggle() { myself.isSmallStage = isNil(isSmall) ? !myself.isSmallStage : isSmall; } - function zoomIn() { - myself.step = function () { - myself.stageRatio -= (myself.stageRatio - smallRatio) / 2; - myself.setExtent(world.extent()); - if (myself.stageRatio < (smallRatio + 0.1)) { - myself.stageRatio = smallRatio; - myself.setExtent(world.extent()); - delete myself.step; - } - }; - } - - function zoomOut() { + function zoomTo(targetRatio) { + var count = 1, + steps = 5; + myself.fps = 30; myself.isSmallStage = true; myself.step = function () { - myself.stageRatio += (1 - myself.stageRatio) / 2; - myself.setExtent(world.extent()); - if (myself.stageRatio > 0.9) { - myself.stageRatio = 1; - myself.isSmallStage = false; - myself.setExtent(world.extent()); - myself.controlBar.stageSizeButton.refresh(); + var diff; + if (count >= steps) { + myself.stageRatio = targetRatio; delete myself.step; + myself.fps = 0; + myself.isSmallStage = !(targetRatio === 1); + myself.controlBar.stageSizeButton.refresh(); + } else { + count += 1; + diff = (targetRatio - myself.stageRatio) / 2; + myself.stageRatio += diff; } + myself.setExtent(world.extent()); }; } @@ -3515,14 +3587,20 @@ IDE_Morph.prototype.toggleStageSize = function (isSmall) { if (!this.isSmallStage || (smallRatio === this.stageRatio)) { toggle(); } + } else if (altClicked) { + smallRatio = this.width() / 2 / + this.stage.dimensions.x; + if (!this.isSmallStage || (smallRatio === this.stageRatio)) { + toggle(); + } } else { toggle(); } if (this.isAnimating) { if (this.isSmallStage) { - zoomIn(); + zoomTo(smallRatio); } else { - zoomOut(); + zoomTo(1); } } else { if (this.isSmallStage) {this.stageRatio = smallRatio; } @@ -4219,7 +4297,7 @@ IDE_Morph.prototype.getURLsbeOrRelative = function (url) { var request = new XMLHttpRequest(), myself = this; try { - request.open('GET', baseUrl + url, false); + request.open('GET', baseURL + url, false); request.send(); if (request.status === 200) { return request.responseText; @@ -4660,7 +4738,7 @@ ProjectDialogMorph.prototype.setSource = function (source) { myself.nameField.setContents(item.name || ''); } src = myself.ide.getURL( - baseUrl + 'Examples/' + item.name + '.xml' + baseURL + 'Examples/' + item.name + '.xml' ); xml = myself.ide.serializer.parse(src); @@ -4714,9 +4792,9 @@ ProjectDialogMorph.prototype.getLocalProjectList = function () { ProjectDialogMorph.prototype.getExamplesProjectList = function () { var dir, projects = []; - //alert(baseUrl); + //alert(baseURL); - dir = this.ide.getURL(baseUrl + 'Examples/'); + dir = this.ide.getURL(baseURL + 'Examples/'); dir.split('\n').forEach( function (line) { var startIdx = line.search(new RegExp('href=".*xml"')), @@ -4835,7 +4913,7 @@ 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'); + src = this.ide.getURL(baseURL + 'Examples/' + proj.name + '.xml'); this.ide.openProjectString(src); this.destroy(); } else { // 'local' @@ -5042,7 +5120,7 @@ ProjectDialogMorph.prototype.shareProject = function () { projectId = 'Username=' + encodeURIComponent(usr.toLowerCase()) + '&ProjectName=' + - encodeURIComponent(proj.projectName); + encodeURIComponent(proj.ProjectName); location.hash = projectId; } }, @@ -5214,7 +5292,7 @@ function SpriteIconMorph(aSprite, aTemplate) { } SpriteIconMorph.prototype.init = function (aSprite, aTemplate) { - var colors, action, query, myself = this; + var colors, action, query, hover, myself = this; if (!aTemplate) { colors = [ @@ -5244,6 +5322,11 @@ SpriteIconMorph.prototype.init = function (aSprite, aTemplate) { return false; }; + hover = function () { + if (!aSprite.exemplar) {return null; } + return (localize('parent' + ':\n' + aSprite.exemplar.name)); + }; + // additional properties: this.object = aSprite || new SpriteMorph(); // mandatory, actually this.version = this.object.version; @@ -5259,7 +5342,7 @@ SpriteIconMorph.prototype.init = function (aSprite, aTemplate) { this.object.name, // label string query, // predicate/selector null, // environment - null, // hint + hover, // hint aTemplate // optional, for cached background images ); @@ -5430,6 +5513,9 @@ SpriteIconMorph.prototype.userMenu = function () { menu.addItem("duplicate", 'duplicateSprite'); menu.addItem("delete", 'removeSprite'); menu.addLine(); + if (StageMorph.prototype.enableInheritance) { + menu.addItem("parent...", 'chooseExemplar'); + } if (this.object.anchor) { menu.addItem( localize('detach from') + ' ' + this.object.anchor.name, @@ -5464,6 +5550,10 @@ SpriteIconMorph.prototype.exportSprite = function () { this.object.exportSprite(); }; +SpriteIconMorph.prototype.chooseExemplar = function () { + this.object.chooseExemplar(); +}; + SpriteIconMorph.prototype.showSpriteOnStage = function () { this.object.showOnStage(); }; @@ -6483,3 +6573,148 @@ JukeboxMorph.prototype.reactToDropOf = function (icon) { this.sprite.sounds.add(costume, idx); this.updateList(); }; + +// StageHandleMorph //////////////////////////////////////////////////////// + +// I am a horizontal resizing handle for a StageMorph + +// StageHandleMorph inherits from Morph: + +StageHandleMorph.prototype = new Morph(); +StageHandleMorph.prototype.constructor = StageHandleMorph; +StageHandleMorph.uber = Morph.prototype; + +// StageHandleMorph instance creation: + +function StageHandleMorph(target) { + this.init(target); +} + +StageHandleMorph.prototype.init = function (target) { + this.target = target || null; + HandleMorph.uber.init.call(this); + this.color = MorphicPreferences.isFlat ? + IDE_Morph.prototype.groupColor : new Color(190, 190, 190); + this.isDraggable = false; + this.noticesTransparentClick = true; + this.setExtent(new Point(12, 50)); +}; + +// StageHandleMorph drawing: + +StageHandleMorph.prototype.drawNew = function () { + this.normalImage = newCanvas(this.extent()); + this.highlightImage = newCanvas(this.extent()); + this.drawOnCanvas( + this.normalImage, + this.color + ); + this.drawOnCanvas( + this.highlightImage, + MorphicPreferences.isFlat ? + new Color(245, 245, 255) : new Color(100, 100, 255), + this.color + ); + this.image = this.normalImage; + this.fixLayout(); +}; + +StageHandleMorph.prototype.drawOnCanvas = function ( + aCanvas, + color, + shadowColor +) { + var context = aCanvas.getContext('2d'), + l = aCanvas.height / 8, + w = aCanvas.width / 6, + r = w / 2, + x, + y, + i; + + context.lineWidth = w; + context.lineCap = 'round'; + y = aCanvas.height / 2; + + context.strokeStyle = color.toString(); + x = aCanvas.width / 12; + for (i = 0; i < 3; i += 1) { + if (i > 0) { + context.beginPath(); + context.moveTo(x, y - (l - r)); + context.lineTo(x, y + (l - r)); + context.stroke(); + } + x += (w * 2); + l *= 2; + } + if (shadowColor) { + context.strokeStyle = shadowColor.toString(); + x = aCanvas.width / 12 + w; + l = aCanvas.height / 8; + for (i = 0; i < 3; i += 1) { + if (i > 0) { + context.beginPath(); + context.moveTo(x, y - (l - r)); + context.lineTo(x, y + (l - r)); + context.stroke(); + } + x += (w * 2); + l *= 2; + } + } +}; + +// StageHandleMorph layout: + +StageHandleMorph.prototype.fixLayout = function () { + if (!this.target) {return; } + var ide = this.target.parentThatIsA(IDE_Morph); + this.setTop(this.target.top() + 10); + this.setRight(this.target.left()); + if (ide) {ide.add(this); } // come to front +}; + +// StageHandleMorph stepping: + +StageHandleMorph.prototype.step = null; + +StageHandleMorph.prototype.mouseDownLeft = function (pos) { + var world = this.world(), + offset = this.right() - pos.x, + myself = this, + ide = this.target.parentThatIsA(IDE_Morph); + + if (!this.target) { + return null; + } + ide.isSmallStage = true; + ide.controlBar.stageSizeButton.refresh(); + this.step = function () { + var newPos, newWidth; + if (world.hand.mouseButton) { + newPos = world.hand.bounds.origin.x + offset; + newWidth = myself.target.right() - newPos; + ide.stageRatio = newWidth / myself.target.dimensions.x; + ide.setExtent(world.extent()); + + } else { + this.step = null; + ide.isSmallStage = !(ide.stageRatio === 1); + ide.controlBar.stageSizeButton.refresh(); + } + }; +}; + +// StageHandleMorph events: + +StageHandleMorph.prototype.mouseEnter = function () { + this.image = this.highlightImage; + this.changed(); +}; + +StageHandleMorph.prototype.mouseLeave = function () { + this.image = this.normalImage; + this.changed(); +}; + diff --git a/help/SnapManual.pdf b/help/SnapManual.pdf index 53672319..219c1da7 100644 Binary files a/help/SnapManual.pdf and b/help/SnapManual.pdf differ diff --git a/history.txt b/history.txt index 74d6d1fe..88bb47ff 100755 --- a/history.txt +++ b/history.txt @@ -2522,8 +2522,35 @@ ______ * Morphic, Objects, Blocks, XML: Optimizations and dramatic speed-up. Thanks, Nathan!! * Objects: push maximum clone count up to 1000, tweak Note::play -=== Start with v4.0.1 === +=== v4.0.1 (unreleased) === 150626 ------ * Morphic: Fix Inspector duplication, update documentation + +150727 +------ +* Polish translation update, thanks, Bartosz Leper! +* Turkish translation. Yay!! Thanks, Hakan Ataş! +* Hungarian translation. Yay!! Thanks, Makány György! +* GUI: relative-url fixes, Thanks, Michael! +* Morphic: enable exporting a screenshot of the World +* Morphic: enable more fine-grained control over dragging position correction +* Morphic: enable all Morphs to “scrollIntoView()” +* Morpic: keyboard accessibility for menus +* Objects: fixes and enhancements for nested sprites +* Blocks, Objects, BYOB, GUI: keyboard editing support +* Objects, Blocks, Threads, GUI, Store, Widgets: Prototypal inheritance for sprite-local variables +* Objects, Blocks: enable monitoring closurized non-locals and thread-temporaries (script vars of running processes) +* GUI: stage resizing handle + +150728 +------ +* GUI: fixed relative urls, thanks, Michael! +* Morphic: escalate origin with “accept” and “cancel” events from text cursors +* BYOB: keep BlockEditors open when or keys are pressed +* GUI: stop keyboard editing of blocks when selecting another sprite + +150730 +------ +* Blocks: improve keyboard editing for embedded rings diff --git a/lang-hu.js b/lang-hu.js new file mode 100644 index 00000000..245f58ea --- /dev/null +++ b/lang-hu.js @@ -0,0 +1,1695 @@ +/* + + lang-hu.js + + A SNAP! magyar fordítása + + written by Makány György + + Copyright (C) 2015 by Makány György + + 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.hu = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Magyar', // the name as it should appear in the language menu + 'language_translator': + 'Makány György', // your name for the Translators tab + 'translator_e-mail': + 'makany.gyorgy@gmail.com', // optional + 'last_changed': + '2015-07-26', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'névtelen', + 'development mode': + 'fejlesztői mód', + + // categories: + 'Motion': + 'Mozgás', + 'Looks': + 'Kinézet', + 'Sound': + 'Hang', + 'Pen': + 'Toll', + 'Control': + 'Vezérlés', + 'Sensing': + 'Érzékelés', + 'Operators': + 'Műveletek', + 'Variables': + 'Változók', + 'Lists': + 'Listák', + 'Other': + 'Egyebek', + + // editor: + 'draggable': + 'vonszolható', + + // tabs: + 'Scripts': + 'Feladatok', + 'Costumes': + 'Jelmezek', + 'Sounds': + 'Hangok', + + // names: + 'Sprite': + 'Szereplő', + 'Stage': + 'Játéktér', + + // rotation styles: + 'don\'t rotate': + 'nem foroghat', + 'can rotate': + 'foroghat', + 'only face left/right': + 'jobbra-balra fordulhat', + + // new sprite button: + 'add a new sprite': + 'Új szereplő', + + // tab help + 'costumes tab help': + 'Kép importálása egy webhelyről vagy a számítógépről', + 'import a sound from your computer\nby dragging it into here': + 'Hang importálása egy webhelyről vagy a számítógépről', + + // 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': + 'Választott játéktér:\nnincs mozgó elem', + + 'move %n steps': + 'menj %n lépést', + 'turn %clockwise %n degrees': + 'fordulj %clockwise %n fokot', + 'turn %counterclockwise %n degrees': + 'fordulj %counterclockwise %n fokot', + 'point in direction %dir': + 'nézz %dir fokos irányba', + 'point towards %dst': + 'nézz %dst irányába', + 'go to x: %n y: %n': + 'ugorj x: %n y: %n', + 'go to %dst': + 'ugorj %dst helyére', + 'glide %n secs to x: %n y: %n': + 'csússz %n mp-ig x: %n y: %n', + 'change x by %n': + 'x változzon: %n', + 'set x to %n': + 'x legyen %n', + 'change y by %n': + 'y változzon: %n', + 'set y to %n': + 'y legyen %n', + 'if on edge, bounce': + 'ha szélen vagy, pattanj vissza', + 'x position': + 'x hely', + 'y position': + 'y hely', + 'direction': + 'irány', + + // looks: + 'switch to costume %cst': + 'a jelmez legyen %cst', + 'next costume': + 'a következő jelmez', + 'costume #': + 'a jelmez sorszáma', + 'say %s for %n secs': + 'mondd %s %n mp-ig', + 'say %s': + 'mondd %s', + 'think %s for %n secs': + 'gondold %s %n mp-ig', + 'think %s': + 'gondold %s', + 'Hello!': + 'Szia!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + '%eff hatás változzon %n', + 'set %eff effect to %n': + '%eff hatás legyen %n', + 'clear graphic effects': + 'töröld a grafikus hatásokat', + 'change size by %n': + 'a méret változzon %n', + 'set size to %n %': + 'a méret legyen %n %', + 'size': + 'méret', + 'show': + 'jelenj meg', + 'hide': + 'tűnj el', + 'go to front': + 'kerülj legelőre', + 'go back %n layers': + 'kerülj %n szinttel hátrébb', + + 'development mode \ndebugging primitives:': + 'Fejlesztő mód \nblokkok hibakeresése', + 'console log %mult%s': + 'konzolra írás: %mult%', + 'alert %mult%s': + 'Felbukkanó: %mult%', + + // sound: + 'play sound %snd': + 'játszd le: %snd', + 'play sound %snd until done': + 'játszd le: %snd és várd meg', + 'stop all sounds': + 'minden hangot állíts le', + 'rest for %n beats': + 'szünetelj %n ütemet', + 'play note %n for %n beats': + 'szóljon %n %n ütemig', + 'change tempo by %n': + 'a tempó változzon: %n', + 'set tempo to %n bpm': + 'a tempó legyen %n ütem/perc', + 'tempo': + 'tempó', + + // pen: + 'clear': + 'töröld a rajzokat', + 'pen down': + 'tollat le', + 'pen up': + 'tollat fel', + 'set pen color to %clr': + 'a tollszín legyen %clr', + 'change pen color by %n': + 'a tollszín változzon %n', + 'set pen color to %n': + 'a tollszín legyen %n', + 'change pen shade by %n': + 'a tollárnyalat változzon %n', + 'set pen shade to %n': + 'a tollárnyalat legyen %n', + 'change pen size by %n': + 'a tollméret változzon %n', + 'set pen size to %n': + 'a tollméret legyen %n', + 'stamp': + 'készíts lenyomatot', + + // control: + 'when %greenflag clicked': + '%greenflag -ra kattintáskor', + 'when %keyHat key pressed': + '%keyHat lenyomásakor', + 'when I am %interaction': + 'amikor %interaction ', + 'clicked': + 'rám kattintanak', + 'pressed': + 'gombnyomás történik', + 'dropped': + 'leejtenek', + 'mouse-entered': + 'az egér fölém kerül', + 'mouse-departed': + 'az egér lemegy rólam', + 'when I receive %msgHat': + '%msgHat üzenet érkezésekor', + 'broadcast %msg': + 'küldj mindenkinek %msg üzenetet', + 'broadcast %msg and wait': + 'küldj mindenkinek %msg üzenetet és várj', + 'Message name': + 'Az üzenet neve', + 'message': + 'üzenet', + 'any message': + 'bármilyen üzenet', + 'wait %n secs': + 'várj %n mp-et', + 'wait until %b': + 'várj amíg %b', + 'forever %c': + 'mindig %c', + 'repeat %n %c': + 'ismételd %n -szer %c', + 'repeat until %b %c': + 'ismételd amíg %b %c', + 'if %b %c': + 'ha %b %c', + 'if %b %c else %c': + 'ha %b %c különben %c', + 'report %s': + 'jelents %s', + 'stop %stopChoices': + '%stopChoices álljon le', + 'all': + 'minden feladat', + 'this script': + 'ez a feladat', + 'this block': + 'ez a blokk', + 'stop %stopOthersChoices': + '%stopOthersChoices álljon le', + 'all but this script': + 'minden más feladat', + 'other scripts in sprite': + 'ennek a szereplőnek minden más feladata', + 'pause all %pause': + 'Várakozzon minden %pause', + 'run %cmdRing %inputs': + 'futtasd %cmdRing %inputs értékkel', + 'launch %cmdRing %inputs': + 'induljon %cmdRing %inputs', + 'call %repRing %inputs': + 'hívd %repRing auf %inputs', + 'run %cmdRing w/continuation': + 'futtatás %cmdRing folytatásával', + 'call %cmdRing w/continuation': + 'hívd meg %cmdRing folytatásával', + 'warp %c': + 'Warp %c', + 'when I start as a clone': + 'másolatként indítva', + 'create a clone of %cln': + 'készíts másolatot %cln', + 'myself': + 'magadról', + 'delete this clone': + 'töröld ezt a másolatot', + + // sensing: + 'touching %col ?': + 'érint %col színt?', + 'touching %clr ?': + 'érint %clr színt?', + 'color %clr is touching %clr ?': + '%clr szín érint %clr színt?', + 'ask %s and wait': + 'Kérdezd meg %s és várj', + 'what\'s your name?': + 'Mi a neved?', + 'answer': + 'Válasz', + 'mouse x': + 'egér x', + 'mouse y': + 'egér y', + 'mouse down?': + 'Egér lenyomva?', + 'key %key pressed?': + '%key gomb lenyomva?', + 'distance to %dst': + 'távolság: %dst', + 'reset timer': + 'Nullázd az órát', + 'timer': + 'Stopper', + '%att of %spr': + '%att %spr objektumérték', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'Turbó mód?', + 'set turbo mode to %b': + '%b turbó módjának bekapcsolása', + + 'filtered for %clr': + '%clr szín szűrése', + 'stack size': + 'Veremméret', + 'frames': + 'Keretek', + + // operators: + '%n mod %n': + '%n osztva %n maradéka', + 'round %n': + '%n kerekítve', + '%fun of %n': + '%fun %n', + 'pick random %n to %n': + 'véletlen %n és %n között', + '%b and %b': + '%b ÉS %b', + '%b or %b': + '%b VAGY %b', + 'not %b': + 'NEM %b', + 'true': + 'igaz', + 'false': + 'hamis', + 'join %words': + 'összefűz %words', + 'split %s by %delim': + '%s szétvágása %delim jeleknél', + 'hello': + 'szia', + 'world': + 'világ', + 'letter %n of %s': + '%n karaktere ennek: %s', + 'length of %s': + '%s hossza', + 'unicode of %s': + '%s Unicode-ra alakítva', + 'unicode %n as letter': + 'Unicode %n betűként', + 'is %s a %typ ?': + '%s egy %typ ?', + 'is %s identical to %s ?': + '%s ugyanaz, mint %s ?', + + 'type of %s': + 'típus: %s', + + // variables: + 'Make a variable': + 'Új változó', + 'Variable name': + 'Változónév', + 'Script variable name': + 'Feladatváltozó név', + 'Delete a variable': + 'Változó törlése', + + 'set %var to %s': + '%var legyen %s', + 'change %var by %n': + '%var változzon ennyivel: %n', + 'show variable %var': + 'írd ki: %var', + 'hide variable %var': + 'rejtsd el: %var', + 'script variables %scriptVars': + 'feladatváltozó: %scriptVars', + + // lists: + 'list %exp': + 'lista %exp', + '%s in front of %l': + '%s megelőzi %l', + 'item %idx of %l': + '%idx eleme a %l listának', + 'all but first of %l': + '%l elsőnkívüli elemei', + 'length of %l': + '%l hossza', + '%l contains %s': + '%l tartalmazza %s -t', + 'thing': + 'dolog', + 'add %s to %l': + '%s hozzáadása %l listához', + 'delete %ida of %l': + '%ida elem törlése %l listából', + 'insert %s at %idx of %l': + '%s beszúrása %idx . pozícióba ebbe: %l', + 'replace item %idx of %l with %s': + '%idx helyettesítése %l listában erre: %s', + + // other + 'Make a block': + 'Blokk készítése', + + // menus + // snap menu + 'About...': + 'A Snap! névjegye...', + 'Reference manual': + 'Kézikönyv', + 'Snap! website': + 'A Snap! webhelye', + 'Download source': + 'A forráskód letöltése', + 'Switch back to user mode': + 'Vissza a felhasználói üzemmódra', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'A deep-Morphic\nhelyzetérzékeny menük\nés a felhasználóbarát menük kikapcsolása', + 'Switch to dev mode': + 'Átkapcsolás fejlesztői módba', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'A Morphic helyzetérzékeny menük, nyomkövetők\nés a nem felhasználóbarát mód bekapcsolása', + + // project menu + 'Project notes...': + 'Projektadatok...', + 'New': + 'Új', + 'Open...': + 'Megnyitás...', + 'Save': + 'Mentés', + 'Save to disk': + 'Lemezre írás', + 'store this project\nin the downloads folder\n(in supporting browsers)': + 'a projekt tárolása\na letöltési mappába\n(ha a böngésző engedi)', + 'Save As...': + 'Mentés másként...', + 'Import...': + 'Importálás...', + 'file menu import hint': + 'egy exportált projekt, feladatkönyvtár,\njelmez vagy hang betöltése', + 'Export project as plain text...': + 'Projekt exportálása egyszerű szövegként...', + 'Export project...': + 'Projekt exportálása...', + 'show project data as XML\nin a new browser window': + 'a projekt adatainak megtekintése\negy új böngészőablakban', + 'Export blocks...': + 'Blokk exportálása...', + 'show global custom block definitions as XML\nin a new browser window': + 'globális felhasználói blokk definíciók\nmegtekintése egy új böngészőablakban', + 'Import tools': + 'Eszközök importálása', + 'load the official library of\npowerful blocks': + 'a hivatalos könyvtári\nblokkok betöltése', + 'Libraries...': + 'Modulkönyvtárak...', + 'Import library': + 'Modulkönyvtár importálása', + + // cloud menu + 'Login...': + 'Belépés...', + 'Signup...': + 'Feliratkozás...', + + // settings menu + 'Language...': + 'Nyelv...', + 'Zoom blocks...': + 'Blokkok bővítése...', + 'Stage size...': + 'A játéktér mérete...', + 'Stage size': + 'A játéktér mérete', + 'Stage width': + 'A játéktér szélessége', + 'Stage height': + 'A játéktér magassága', + 'Default': + 'Alapérték', + 'Blurred shadows': + 'Elmosódó árnyékok', + 'uncheck to use solid drop\nshadows and highlights': + 'vegye ki a jelölést, ha éles árnyékokat\nés kiemeléseket kíván használni', + 'check to use blurred drop\nshadows and highlights': + 'jelölje be, ha elmosódó árnyékokat\nés kiemeléseket kíván használni', + 'Zebra coloring': + 'Zebra színezés', + 'check to enable alternating\ncolors for nested blocks': + 'engedélyezi a beágyazott blokkok\neltérő színezését', + 'uncheck to disable alternating\ncolors for nested block': + 'tiltja a beágyazott blokkok\neltérő színezését', + 'Dynamic input labels': + 'Dinamikus beviteli feliratok', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'üresen hagyva tiltja a többszörös\nbeviteli mezők dinamikus feliratait', + 'check to enable dynamic\nlabels for variadic inputs': + 'bejelölve engedélyezi a többszörös\nbeviteli mezők dinamikus feliratait', + 'Prefer empty slot drops': + 'Szóköz használata üres karakterként', + 'settings menu prefer empty slots hint': + 'einschalten um leere Platzhalter\nbeim Platzieren von Bl\u00f6cken' + + 'zu bevorzugen', + 'uncheck to allow dropped\nreporters to kick out others': + 'ausschalten um das "Rauskicken"\nvon platzierten Bl\u00f6cken\n' + + 'zu erm\u00f6glichen', + 'Long form input dialog': + 'Hosszú formátumú beviteli párbeszéd', + 'Plain prototype labels': + 'Egyszerű blokk prototípus címkék', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'üresen hagyva mindig látszik a (+) jel\na blokk prototípus cimkéjében', + 'check to hide (+) symbols\nin block prototype labels': + 'bejelölve látszik a (+) jel\na blokk prototípus cimkéjében', + 'check to always show slot\ntypes in the input dialog': + 'bejelölve mindig látszik a csatlakozás típusa a beviteli párbeszédablakban', + 'uncheck to use the input\ndialog in short form': + 'kapcsolja ki, ha rövidített\npárbeszédablakot akar használni', + 'Virtual keyboard': + 'Virtuális billentyűzet', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'kikapcsolva letiltja a virtuális\nbillentyűzetet a mobil eszközökön', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'bejelölve engedélyezi a virtuális\nbillentyűzetet a mobil eszközökön', + 'Input sliders': + 'Beviteli csúszkák', + 'uncheck to disable\ninput sliders for\nentry fields': + 'kikapcsolva letiltja a csúszkákat\na beviteli mezőknél', + 'check to enable\ninput sliders for\nentry fields': + 'bekapcsolva engedélyezi a csúszkákat\na beviteli mezőknél', + 'Clicking sound': + 'A kattintás hangja', + 'uncheck to turn\nblock clicking\nsound off': + 'kikapcsolva letiltja a blokkra kattintás hangját', + 'check to turn\nblock clicking\nsound on': + 'bekapcsolva engedélyezi a blokkra kattintás hangját', + 'Animations': + 'Animációk', + 'uncheck to disable\nIDE animations': + 'kikapcsolva letiltja\naz IDE animációit', + 'Turbo mode': + 'Turbó mód', + 'check to prioritize\nscript execution': + 'bekapcsolva a programozott\nvégrehajtás lesz az elsődleges', + 'uncheck to run scripts\nat normal speed': + 'kikapcsolva normál sebességen\nfutnak a programok', + 'check to enable\nIDE animations': + 'bekapcsolva engedélyezi\naz IDE animációit', + 'Thread safe scripts': + 'Biztonságos programszálak', + 'uncheck to allow\nscript reentrance': + 'kikapcsolva engedélyezi a programok\n többszörös végrehajtását', + 'check to disallow\nscript reentrance': + 'bekapcsolva engedélyezi a programok\n többszörös végrehajtását', + 'Prefer smooth animations': + 'Finom animációk', + 'uncheck for greater speed\nat variable frame rates': + 'kapcsolja ki, ha nagyobb sebességet\nakar változtatható képfrissítéseknél', + 'check for smooth, predictable\nanimations across computers': + 'kapcsolja be, ha finomabb, kiszámíthatóbb\nanimációkat akar minden számítógépen', + 'Flat line ends': + 'Egyszerű vonalvégződés', + 'check for flat ends of lines': + 'kapcsolja be az\negyszerű vonalvégződéshez', + 'uncheck for round ends of lines': + 'kapcsolja ki a\nlekerekített vonalvégződésekhez', + + // inputs + 'with inputs': + 'bevitelekkel', + 'input names:': + 'beviteli név:', + 'Input Names:': + 'Beviteli név:', + 'input list:': + 'Beviteli lista:', + + // context menus: + 'help': + 'Súgó', + + // palette: + 'hide primitives': + 'az alapvetők elrejtése', + 'show primitives': + 'az alapvetők megjelentése', + + // blocks: + 'help...': + 'Súgó...', + 'relabel...': + 'átcimkézés...', + 'duplicate': + 'megkettőzés', + 'make a copy\nand pick it up': + 'másolat felvétele', + 'only duplicate this block': + 'csak készítsen egy másolatot\nerről a blokkról', + 'delete': + 'törlés', + 'script pic...': + 'Program képe...', + 'open a new window\nwith a picture of this script': + 'új böngészőablak megnyitása\nennek a programnak a képével', + 'ringify': + 'körülvesz', + 'unringify': + 'körülfogás megszüntetése', + + // custom blocks: + 'delete block definition...': + 'blokkdefiníció törlése', + 'edit...': + 'szerkesztés...', + + // sprites: + 'edit': + 'szerkesztés', + 'move': + 'mozgatás', + 'detach from': + 'leválasztás erről', + 'detach all parts': + 'minden rész szétválasztása', + 'export...': + 'exportálás...', + + // stage: + 'show all': + 'mindent mutat', + 'pic...': + 'kép exportálása...', + 'open a new window\nwith a picture of the stage': + 'új ablak nyitása a színpad képével', + + // scripting area + 'clean up': + 'törlés', + 'arrange scripts\nvertically': + 'a program függőleges átméretezése', + 'add comment': + 'megjegyzés hozzáadása', + 'undrop': + 'visszavétel', + 'undo the last\nblock drop\nin this pane': + 'az utolsó blokk visszavétele erről a lapról', + 'scripts pic...': + 'minden feladat képpé...', + 'open a new window\nwith a picture of all scripts': + 'új ablak nyitása\naz összes program képével', + 'make a block...': + 'blokk létrehozása...', + + // costumes + 'rename': + 'átnevezés', + 'export': + 'exportálás', + 'rename costume': + 'a jelmez átnevezése', + 'Paint a new costume': + 'Új jelmez rajzolása', + + // sounds + 'Play sound': + 'Hang lejátszása', + 'Stop sound': + 'A hang leállítása', + 'Stop': + 'Állj', + 'Play': + 'Lejátszás', + 'rename sound': + 'A hang átnevezése', + + // dialogs + // buttons + 'OK': + 'OK', + 'Ok': + 'Ok', + 'Cancel': + 'Mégsem', + 'Yes': + 'Igen', + 'No': + 'Nem', + + // help + 'Help': + 'Súgó', + + // zoom blocks + 'Zoom blocks': + 'Blokkokra közelítés', + 'build': + 'építés', + 'your own': + 'saját', + 'blocks': + 'blokkok', + 'normal (1x)': + 'normál (1x)', + 'demo (1.2x)': + 'Demó (1.2x)', + 'presentation (1.4x)': + 'Prezentáció (1.4x)', + 'big (2x)': + 'nagy (2x)', + 'huge (4x)': + 'óriási (4x)', + 'giant (8x)': + 'gigantikus (8x)', + 'monstrous (10x)': + 'szörnyeteg (10x)', + + // Project Manager + 'Untitled': + 'Névtelen', + 'Open Project': + 'Projekt megnyitása', + '(empty)': + '(üres)', + 'Saved!': + 'Mentve!', + 'Delete Project': + 'Projekt törlése', + 'Are you sure you want to delete': + 'Biztos, hogy törlöd?', + 'rename...': + 'átnevezés...', + + // costume editor + 'Costume Editor': + 'Jelmezszerkesztő', + 'click or drag crosshairs to move the rotation center': + 'kattints oda vagy vidd a szálkeresztet a forgás középpontjába', + + // project notes + 'Project Notes': + 'A projekt tudnivalói', + + // new project + 'New Project': + 'Új projekt', + 'Replace the current project with a new one?': + 'Felülírja az aktuális projektet egy újjal?', + + // save project + 'Save Project As...': + 'Projekt mentése másként...', + + // export blocks + 'Export blocks': + 'Blokkok exportja', + 'Import blocks': + 'Blokkok importja', + 'this project doesn\'t have any\ncustom global blocks yet': + 'ennek a projektnek még nincs globális felhasználói blokkjai', + 'select': + 'választás', + 'none': + 'egyik sem', + + // variable dialog + 'for all sprites': + 'minden alakzatra', + 'for this sprite only': + 'csak erre az alakzatra', + + // block dialog + 'Change block': + 'Blokk változtatása', + 'Command': + 'Parancs', + 'Reporter': + 'Függvény', + 'Predicate': + 'Kijelentés', + + // block editor + 'Block Editor': + 'Blokk szerkesztő', + 'Apply': + 'Alkalmazás', + + // block deletion dialog + 'Delete Custom Block': + 'Felhasználói blokk törlése', + 'block deletion dialog text': + 'Biztos, hogy eltávolítja ezt\na blokkot és minden példányát?', + + // input dialog + 'Create input name': + 'A bevitel nevének létrehozása', + 'Edit input name': + 'A bevitel nevének szerkesztése', + 'Edit label fragment': + 'A címke rész szerkesztése', + 'Title text': + 'A cím szövege', + 'Input name': + 'A bevitel neve', + 'Delete': + 'Törlés', + 'Object': + 'Objektum', + 'Number': + 'Szám', + 'Text': + 'Szöveg', + 'List': + 'Lista', + 'Any type': + 'Bármilyen típus', + 'Boolean (T/F)': + 'Logikai (I/H)', + 'Command\n(inline)': + 'Parancs\n(inline)', + 'Command\n(C-shape)': + 'Parancs\n(C-Form)', + 'Any\n(unevaluated)': + 'Bármilyen\n(nem kiértékelt)', + 'Boolean\n(unevaluated)': + 'Logikai(nem kiértékelt)', + 'Single input.': + 'Egyszerű bevitel.', + 'Default Value:': + 'Alapérték:', + 'Multiple inputs (value is list of inputs)': + 'Több érték bevitele (az érték a bevitelek listája)', + 'Upvar - make internal variable visible to caller': + 'A belső változók láthatóvá tétele a hívó számára', + + // About Snap + 'About Snap': + 'A Snap névjegye', + 'Back...': + 'Vissza...', + 'License...': + 'Licenc...', + 'Modules...': + 'Modulok...', + 'Credits...': + 'Közreműködők...', + 'Translators...': + 'Fordítók', + 'License': + 'Licenc', + 'current module versions:': + 'a jelenlegi modulverziók', + 'Contributors': + 'Közreműködők', + 'Translations': + 'Fordítások', + + // variable watchers + 'normal': + 'normál', + 'large': + 'nagy', + 'slider': + 'csúszka', + 'slider min...': + 'a csúszka minimuma...', + 'slider max...': + 'a csúszka maximuma...', + 'import...': + 'importálás...', + 'Slider minimum value': + 'A csúszka minimális értéke', + 'Slider maximum value': + 'A csúszka maximális értéke', + + // list watchers + 'length: ': + 'Hossz: ', + + // coments + 'add comment here...': + 'tegye ide a megjegyzést', + + // drow downs + // directions + '(90) right': + '(90) jobbra', + '(-90) left': + '(-90) balra', + '(0) up': + '(0) fel', + '(180) down': + '(180) le', + + // collision detection + 'mouse-pointer': + 'egérmutató', + 'edge': + 'csúcs', + 'pen trails': + 'ceruza nyomvonalak', + + // costumes + 'Turtle': + 'Teknős', + 'Empty': + 'Üres', + + // graphical effects + 'brightness': + 'világosság', + 'ghost': + 'átlátszóság', + 'negative': + 'negatív', + 'comic': + 'moáré', + 'confetti': + 'konfetti', + + // keys + 'space': + 'szóköz', + 'up arrow': + 'felfelé nyíl', + 'down arrow': + 'lefelé nyíl', + 'right arrow': + 'jobbra nyíl', + 'left arrow': + 'balra nyíl', + '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...': + 'új...', + + // math functions + 'abs': + 'abszolútérték', + 'floor': + 'egészrész', + 'sqrt': + 'négyzetgyök', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // delimiters + 'letter': + 'betű', + 'whitespace': + 'szóköz', + 'line': + 'újsor', + 'tab': + 'tabulátor', + 'cr': + 'kocsivissza', + + // data types + 'number': + 'szám', + 'text': + 'szöveg', + 'Boolean': + 'logikai', + 'list': + 'lista', + 'command': + 'parancsblokk', + 'reporter': + 'függvényblokk', + 'predicate': + 'kijelentés', + + // list indices + 'last': + 'utolsó', + 'any': + 'bármilyen', + // Ez kimaradt a német nyelvi fájlból + 'grow': + 'növekedés', + 'shrink': + 'kicsinyítés', + 'flip ↔': + 'tükrözés ↔', + 'flip ↕': + 'tükrözés ↕', + 'Export all scripts as pic...': + 'Minden feladat exportálása képként…', + 'show a picture of all scripts\nand block definitions': + 'minden feladat és blokk\ndefinícióról készült kép mutatása', + 'current %dates': + 'aktuális %dates', + 'year': + 'év', + 'month': + 'hónap', + 'date': + 'nap', + 'day of week': + 'a hét napja', + 'hour': + 'óra', + 'minute': + 'perc', + 'second': + 'másodperc', + 'time in milliseconds': + 'idő (ezredmásodpercben)', + 'find blocks...': + 'blokkok keresése…', + 'costume name': + 'a jelmez neve', + 'Open': + 'Megnyitás', + 'Share': + 'Megosztás', + 'Snap!Cloud': + 'Snap!Felhő', + 'Cloud': + 'Felhő', + 'could not connect to:': + 'nem tud csatlakozni ide:', + 'Service:': + 'Szolgáltatás:', + 'login': + 'bejelentkezés', + 'ERROR: INVALID PASSWORD': + 'HIBA: ÉRVÉNYTELEN JELSZÓ', + 'Browser': + 'Böngésző', + 'Sign up': + 'Újként regisztrálni', + 'Signup': + 'Új regisztráció', + 'Sign in': + 'Regisztráció', + 'Logout': + 'Kijelentkezés', + 'Change Password...': + 'Jelszó megváltoztatása…', + 'Change Password': + 'Jelszó megváltoztatása', + 'Account created.': + 'Az azonosító létrejött.', + 'An e-mail with your password\nhas been sent to the address provided': + 'A megadott címre e-mailben elküldtük a jelszavát.', + 'now connected.': + 'csatlakozva.', + 'disconnected.': + 'leválasztva.', + 'Reset password': + 'A jelszó alaphelyzetre állítása', + 'Reset Password...': + 'A jelszó alaphelyzetre állítása…', + 'User name:': + 'Felhasználói név:', + 'Password:': + 'Jelszó:', + 'Old password:': + 'A jelenlegi jelszó:', + 'New password:': + 'Új jelszó:', + 'Repeat new password:': + 'Az új jelszó ismét:', + 'Birth date:': + 'Születési idő:', + 'January': + 'január', + 'February': + 'február', + 'March': + 'március', + 'April': + 'április', + 'May': + 'május', + 'June': + 'június', + 'July': + 'július', + 'August': + 'augusztus', + 'September': + 'szeptember', + 'October': + 'október', + 'November': + 'november', + 'December': + 'december', + 'year:': + 'év:', + ' or before': + ' vagy előtte', + 'E-mail address:': + 'E-mail cím:', + 'E-mail address of parent or guardian:': + 'A szülő vagy gondozó email címe:', + 'Terms of Service...': + 'Felhasználási feltételek…', + 'Privacy...': + 'Jogvédelem…', + 'I have read and agree\nto the Terms of Service': + 'Elolvastam és egyetértek\na felhasználási feltételekkel', + 'stay signed in on this computer\nuntil logging out': + 'maradjon bejelentkezve, amíg ki nem jelentkezem a számítógépről', + 'please fill out\nthis field': + 'kérem, töltse ki\nezt a mezőt.', + 'User name must be four\ncharacters or longer': + 'A felhasználói név legalább\nnégy karakteres legyen.', + 'please provide a valid\nemail address': + 'kérem, adjon meg egy\nérvényes email címet.', + 'password must be six\ncharacters or longer': + 'a jelszó legyen legalább\nhat karakter hosszú.', + 'passwords do\nnot match': + 'A jelszavak nem egyeznek.', + 'please agree to\nthe TOS': + 'fogadja el a felhasználási feltételeket.', + 'Examples': + 'Példák', + 'You are not logged in': + 'Még nem lépett be', + 'Updating\nproject list...': + 'A projeklista frissítése…', + 'Opening project...': + 'Projekt megnyitása…', + 'Fetching project\nfrom the cloud...': + 'Projekt letöltése\na felhőből…', + 'Saving project\nto the cloud...': + 'Projekt mentése\na felhőbe…', + 'Sprite Nesting': + 'Szereplők összefűzése', + 'uncheck to disable\nsprite composition': + 'kapcsolja ki a szereplők összefűzésének megakadályozásához.', + 'Codification support': + 'A kodifikáció támogatása', + 'check for block\nto text mapping features': + 'Assinalar para funcionalidades\nde mapeamento entre blocos e texto.', + 'saved.': + 'mentve.', + 'options...': + 'beállítások…', + 'read-only': + 'csak olvasható', + 'Input Slot Options': + 'Bemenő adat csatlakozási lehetőségek', + 'Enter one option per line.Optionally use "=" as key/value delimiter\ne.g.\n the answer=42': + 'Soronként egy lehetőséget írjon be.\nSzükség esetén használhatja az "=" jelet\nkulcs/érték pár elválasztására, pl.\na válasz=42', + 'paint a new sprite': + 'új alakzat rajzolása', + 'Paint a new costume': + 'Új jelmez rajzolása', + 'add a new Turtle sprite': + 'új teknőc rajzának hozzáadása', + 'Flat design': + 'Síkbeli tervezés', + 'check for alternative\nGUI design': + 'más grafikus felület ellenőrzése', + 'Rasterize SVGs': + 'SVG átalakítása bittérképbe', + 'check to rasterize\nSVGs on import': + 'SVG bittérképpé alakíthatóságának\nellenőrzése az importálás során', + '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': + 'visszavon', + 'Brush size': + 'Ecsetméret', + 'Constrain proportions of shapes?\n(you can also hold shift)': + 'Megmaradjanak az alakzat arányai?\n(ehhez használhatja a SHIFT billentyűt is)', + 'Eraser tool': + 'Törlő eszköz', + 'Paintbrush tool\n(free draw)': + 'Festőecset eszköz\n(szabadkézi rajz)', + 'Line tool\n(shift: vertical/horizontal)': + 'Vonalrajzoló eszköz\n(shift: függőleges/vízszintes)', + 'Stroked Rectangle\n(shift: square)': + 'Téglalap\n(shift: négyzet)', + 'Filled Rectangle\n(shift: square)': + 'Kitöltött téglalap\n(shift: négyzet)', + 'Stroked Ellipse\n(shift: circle)': + 'Ellipszis\n(shift: kör)', + 'Filled Ellipse\n(shift: circle)': + 'Kitöltött ellipszis\n(shift: kör)', + 'Fill a region': + 'Terület kitöltése', + 'Set the rotation center': + 'A forgatás középpontjának beállítása', + 'Pipette tool\n(pick a color anywhere)': + 'Pipetta\n(szín felvétele bárhonnan)', + 'Paint Editor': + 'Képszerkesztő', + 'square': + 'négyzet', + 'pointRight': + 'háromszög jobbra', + 'gears': + 'fogaskerék', + 'file': + 'állomány', + 'fullScreen': + 'teljes képernyő', + 'normalScreen': + 'normál képernyő', + 'smallStage': + 'kis színpad', + 'normalStage': + 'normál színpad', + 'turtle': + 'teknős', + 'stage': + 'színpad', + 'turtleOutline': + 'a teknős körvonala', + 'pause': + 'szünet', + 'flag': + 'zászló', + 'octagon': + 'nyolcszög', + 'cloud': + 'felhő', + 'cloudOutline': + 'a felhő körvonala', + 'cloudGradient': + 'a felhő áttetszősége', + 'turnRight': + 'fordulj jobbra', + 'turnLeft': + 'fordulj balra', + 'storage': + 'tárolás', + 'poster': + 'háttérkép', + 'flash': + 'villám', + 'brush': + 'ecset', + 'rectangle': + 'téglalap', + 'rectangleSolid': + 'kitöltött téglalap', + 'circle': + 'kör', + 'circleSolid': + 'kitöltött kör', + 'crosshairs': + 'szálkereszt', + 'paintbucket': + 'festékesvödör', + 'eraser': + 'radír', + 'pipette': + 'pipetta', + 'speechBubble': + 'buborék', + 'speechBubbleOutline': + 'a buborék körvonala', + 'arrowUp': + 'felfelé nyíl', + 'arrowUpOutline': + 'a felfelényíl körvonala', + 'arrowLeft': + 'balra nyíl', + 'arrowLeftOutline': + 'a balra nyíl körvonala', + 'arrowDown': + 'lefelé nyíl', + 'arrowDownOutline': + 'a lefelényíl körvonala', + 'arrowRight': + 'jobbra nyíl', + 'arrowRightOutline': + 'a jobbranyíl körvonala', + 'robot': + 'robot', + 'turn pen trails into new costume...': + 'a toll beállításainak alkalmazása egy új jelmezre…', + 'turn all pen trails and stamps\ninto a new costume for the\ncurrently selected sprite': + 'minden tollbeállítás átállítása\nés átvitele az aktuális\nalak egy új jelmezébe', + 'pen': + 'toll', + 'tip': + 'tipp', + 'middle': + 'közép', + 'last changed': + 'utoljára változtatva', + 'Are you sure you want to publish': + 'Biztosan nyilvánossá teszi', + 'Are you sure you want to unpublish': + 'Biztos, hogy nemnyilvánossá teszi', + 'Share Project': + 'A projekt megosztása', + 'Unshare Project': + 'A projekt megosztásának megszüntetése', + 'sharing\nproject...': + 'a projekt\nmegosztása…', + 'unsharing\nproject...': + 'a projekt megosztásának\nmegszüntetése…', + 'shared.': + 'megosztva.', + 'unshared.': + 'nincs megosztva.', + 'Unshare': + 'Nincs megosztás', + 'password has been changed.': + 'a jelszó megváltozott.', + 'SVG costumes are\nnot yet fully supported\nin every browser': + 'Az SVG ábrákat nem minden böngésző támogatja', + 'Save Project': + 'A projekt mentése', + 'script pic with result...': + 'a program képe az eredménnyel…', + 'open a new window\nwith a picture of both\nthis script and its result': + 'Új böngészőablak megnyitása a programnak és eredményének képével.', + 'JavaScript function ( %mult%s ) { %code }': + 'JavaScript függvény ( %mult%s ) { %code }', + 'Select categories of additional blocks to add to this project.': + 'Válassza ki a projekthez adandó blokkok kategóriáit.', + 'Import sound': + 'Hang importálása', + 'Select a sound from the media library': + 'Válasszon ki egy hangot a médiakönyvtárból.', + 'Import': + 'Import', + 'Select a costume from the media library': + 'Válasszon ki egy jelmezt a médiakönyvtárból.', + 'edit rotation point only...': + 'csak a forgáspont szerkesztése…', + 'Export Project As...': + 'Projekt exportálása mint…', + 'a variable of name \'': + 'ilyen nevű változó «', + '\'\ndoes not exist in this context': + '»\nnincs ebben a környezetben', + '(temporary)': + '(ideiglenes)', + 'expecting': + 'kötelező', + 'input(s), but getting': + 'adatbevitel, de ez érkezett', + + // kódolás + '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': + '%cmdRing kódja', + 'delimiter': + 'határoló', + 'collection': + 'gyűjtemény', + 'variables': + 'változók', + 'parameters': + 'paraméterek', + 'code': + 'kód', + 'header': + 'fejléc', + 'header mapping...': + 'mapeamento para cabeçalho…', + 'code mapping...': + 'kód leképezés…', + 'Code mapping': + 'Kód leképezés', + 'Header mapping': + 'A fejléc leképezése', + '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.': + 'Gépelje be a blokk definíciójának megfelelő programkódot. Használja a látható formális paramétereket és a referenciát a törzs generált szövegkódjához.', + 'Enter code that corresponds to the block\'s definition. Choose your own\nformal parameter names (ignoring the ones shown).': + 'Gépelje be a blokk definíciójának megfelelő programkódot. Használja a saját formális paramétereit (hagyja figyelmen kívül a láthatókat).', + 'Enter code that corresponds to the block\'s operation (usually a single\nfunction invocation). Use <#n> to reference actual arguments as shown.': + 'Gépelje be a blokk működésének megfelelő programkódot (általában egy függvény bevezetésével). Használja a <#n> hivatkozási helyen látható aktuális argumentumokat.' +}; diff --git a/lang-pl.js b/lang-pl.js index 669a22b9..97a4c533 100644 --- a/lang-pl.js +++ b/lang-pl.js @@ -248,8 +248,8 @@ SnapTranslator.dict.pl = { 'tylko lewo/prawo', // new sprite button: - 'add a new sprite': - 'dodaj nowego duszka', + 'add a new Turtle sprite': + 'dodaj nowego duszka-żółwia', // tab help 'costumes tab help': @@ -343,7 +343,7 @@ SnapTranslator.dict.pl = { 'think %s': 'pomy\u015Bl %s', 'Hello!': - 'Hallo!', + 'Cześć!', 'Hmm...': 'Hmm...', 'change %eff effect by %n': @@ -545,9 +545,9 @@ SnapTranslator.dict.pl = { 'join %words': 'po\u0142\u0105cz %words', 'hello': - 'Hallo', + 'witaj', 'world': - 's\u0142owo', + 'świecie', 'letter %n of %s': 'litera %n z %s', 'length of %s': @@ -673,6 +673,8 @@ SnapTranslator.dict.pl = { 'Logowanie...', 'Signup...': 'Rejestracja...', + 'Reset Password...': + 'Zresetuj hasło...', // settings menu 'Language...': @@ -857,7 +859,7 @@ SnapTranslator.dict.pl = { 'Ok': 'Ok', 'Cancel': - 'Poniechaj', + 'Anuluj', 'Yes': 'Tak', 'No': @@ -903,9 +905,53 @@ SnapTranslator.dict.pl = { 'Delete Project': 'Usu\u0144 projekt', 'Are you sure you want to delete': - 'Czy napewno chcesz usun\u0105\u0107?', + 'Czy napewno chcesz usun\u0105\u0107', 'rename...': 'przemianuj', + 'Cloud': + 'Chmura', + 'Browser': + 'Przeglądarka', + 'Examples': + 'Przykłady', + 'You are not logged in': + 'Nie jesteś zalogowany', + 'Updating\nproject list...': + 'Aktualizowanie\nlisty projektów...', + 'last changed': + 'ostatnio zmieniony', + 'Open': + 'Otwórz', + 'Share': + 'Udostępnij', + 'Unshare': + 'Wyłącz udostępnianie', + 'Share Project': + 'Udostępnij projekt', + 'Unshare Project': + 'Wyłącz udostępnianie projektu', + 'Are you sure you want to publish': + 'Czy na pewno chcesz opublikować projekt', + 'Are you sure you want to unpublish': + 'Czy na pewno chcesz wyłączyć publikowanie projektu', + 'sharing\nproject...': + 'Udostępnianie\nprojektu...', + 'shared.': + 'Projekt udostępniony.', + 'unsharing\nproject...': + 'Wyłączanie\nudostępniania projektu...', + 'unshared.': + 'Udostępnianie wyłączone.', + 'Fetching project\nfrom the cloud...': + 'Wczytywanie projektu\nz chmury...', + 'Opening project...': + 'Otwieranie projektu...', + 'Save Project': + 'Zapisz projekt', + 'Saving project\nto the cloud...': + 'Zapisywanie projektu\ndo chmury...', + 'saved.': + 'Projekt zapisany.', // costume editor 'Costume Editor': @@ -1081,7 +1127,7 @@ SnapTranslator.dict.pl = { // costumes 'Turtle': - 'Duszek', + 'Żółw', 'Empty': 'Pusty', @@ -1221,5 +1267,61 @@ SnapTranslator.dict.pl = { 'last': 'ostatni', 'any': - 'dowolny' + 'dowolny', + + // Sign up dialog + 'Sign up': + 'Rejestracja', + 'User name:': + 'Nazwa użytkownika:', + 'Birth date:': + 'Data urodzenia:', + 'year:': + 'rok:', + 'E-mail address:': + 'Adres e-mail:', + 'E-mail address of parent or guardian:': + 'Adres e-mail rodzica lub opiekuna:', + 'Terms of Service...': + 'Regulamin...', + 'Privacy...': + 'Polityka prywatności...', + 'I have read and agree\nto the Terms of Service': + 'Przeczytałem i zgadzam się\nz Regulaminem', + 'January': + 'styczeń', + 'February': + 'luty', + 'March': + 'marzec', + 'April': + 'kwiecień', + 'May': + 'maj', + 'June': + 'czerwiec', + 'July': + 'lipiec', + 'August': + 'sierpień', + 'September': + 'wrzesień', + 'October': + 'październik', + 'November': + 'listopad', + 'December': + 'grudzień', + 'please fill out\nthis field': + 'Proszę wypełnić\nto pole', + 'please agree to\nthe TOS': + 'Proszę zaakceptować\nRegulamin', + 'Sign in': + 'Zaloguj się', + 'Password:': + 'Hasło:', + 'stay signed in on this computer\nuntil logging out': + 'Zapamiętaj mnie na tym komputerze\naż do wylogowania', + 'Reset password': + 'Zresetuj hasło' }; diff --git a/lang-tr.js b/lang-tr.js new file mode 100644 index 00000000..0b852bbd --- /dev/null +++ b/lang-tr.js @@ -0,0 +1,1275 @@ +/* + + lang-de.js + + German translation for 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.tr = { + +/* + Special characters: (see ) + + Ä, ä \u00c4, \u00e4 + Ö, ö \u00d6, \u00f6 + Ü, ü \u00dc, \u00fc + ß \u00df +*/ + + // translations meta information + 'language_name': + 'Türkçe', // the name as it should appear in the language menu + 'language_translator': + 'Hakan Atas', // your name for the Translators tab + 'translator_e-mail': + 'hakanatas@gmail.com', // optional + 'last_changed': + '2015-7-26', // this, too, will appear in the Translators tab + + // GUI + // control bar: + 'untitled': + 'adsız', + 'development mode': + 'geliştirici modu', + + // categories: + 'Motion': + 'Hareket', + 'Looks': + 'Görünümler', + 'Sound': + 'Ses', + 'Pen': + 'Kalem', + 'Control': + 'Kontrol', + 'Sensing': + 'Algılama', + 'Operators': + 'İşlemler', + 'Variables': + 'Değişkenler', + 'Lists': + 'Listeler', + 'Other': + 'Diğerleri', + + // editor: + 'draggable': + 'sürüklenebilir', + + // tabs: + 'Scripts': + 'Betikler', + 'Costumes': + 'Kostümler', + 'Sounds': + 'Sesler', + + // names: + 'Sprite': + 'Karakter', + 'Stage': + 'Sahne', + + // rotation styles: + 'don\'t rotate': + 'dönebilir', + 'can rotate': + 'dönemez', + 'only face left/right': + 'sadece sağa/sola dönebilir', + + // new sprite button: + 'add a new sprite': + 'yeni bir karakter ekle', + + // tab help + 'costumes tab help': + 'kostüm sekmesi yardımı', + 'import a sound from your computer\nby dragging it into here': + 'bilgisayarınızdan bir sesi\n' + + 'buraya sürükleyerek içeri aktarı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': + 'Seçili sahne:\nhareket temelleri yok', + 'move %n steps': + '%n adım git', + 'turn %clockwise %n degrees': + '%clockwise yönünde %n derece dön', + 'turn %counterclockwise %n degrees': + '%counterclockwise yönünde %n derece dön', + 'point in direction %dir': + '%dir yönüne dön', + 'point towards %dst': + '%dst e doğru dön', + 'go to x: %n y: %n': + 'x: %n y: %n git', + 'go to %dst': + '%dst git', + 'glide %n secs to x: %n y: %n': + 'x: %n y: %n noktasına %n saniyede süzül', + 'change x by %n': + 'x\'i %n değiştir', + 'set x to %n': + 'x\'i %n ayarla', + 'change y by %n': + 'y\'i %n değiştir', + 'set y to %n': + 'y\'i %n olarak ayarla', + 'if on edge, bounce': + 'kenardaysan sek', + 'x position': + 'x-pozisyonu', + 'y position': + 'y-pozisyonu', + 'direction': + 'doğrultu', + + // looks: + 'switch to costume %cst': + 'kostümü %cst olarak değiştir', + 'next costume': + 'sonraki kostüm', + 'costume #': + 'kostüm no:', + 'say %s for %n secs': + '%s saniye %n söyle', + 'say %s': + '%s söyle', + 'think %s for %n secs': + '%s saniye %n diye düşün', + 'think %s': + '%s diye düşün', + 'Hello!': + 'Merhaba!', + 'Hmm...': + 'Hmm...', + 'change %eff effect by %n': + '%eff etkisini %n değiştir', + 'set %eff effect to %n': + '%eff etkisini %n yap', + 'clear graphic effects': + 'görsel etkileri temizle', + 'change size by %n': + 'büyüklüğünü %n değiştir', + 'set size to %n %': + 'büyüklüğü % %n yap', + 'size': + 'büyüklük', + 'show': + 'göster', + 'hide': + 'gizle', + 'go to front': + 'öne git', + 'go back %n layers': + '%n katman alta in', + + 'development mode \ndebugging primitives:': + 'geliştirici modu \nhata ayıklama temelleri', + 'console log %mult%s': + 'log dosyasına yaz %mult%s', + 'alert %mult%s': + 'uyarı: %mult%s', + + // sound: + 'play sound %snd': + '%snd sesini çal', + 'play sound %snd until done': + '%snd sesini bitene kadar çal', + 'stop all sounds': + 'tüm sesleri durdur', + 'rest for %n beats': + '%n vuruş sus', + 'play note %n for %n beats': + '%n notasını %n vuruş çal', + 'change tempo by %n': + 'tempoyu %n değiştir', + 'set tempo to %n bpm': + 'tempoyu %n yap', + 'tempo': + 'tempo', + + // pen: + 'clear': + 'temizle', + 'pen down': + 'kalemi bastır', + 'pen up': + 'kalemi kaldır', + 'set pen color to %clr': + 'kalemin rengini %clr yap', + 'change pen color by %n': + 'kalemin rengini %n değiştir', + 'set pen color to %n': + 'kalemin rengini %n yap', + 'change pen shade by %n': + 'kalemin tonunu %n değiştir', + 'set pen shade to %n': + 'kalemin tonunu %n yap', + 'change pen size by %n': + 'kalemin kalınlığını %n değiştir', + 'set pen size to %n': + 'kalemin kalınlığını %n yap', + 'stamp': + 'damga', + + // control: + 'when %greenflag clicked': + '%greenflag tıklanınca', + 'when %keyHat key pressed': + '%keyHat tuşu basılınca', + 'when I am clicked': + 'bu kukla tıklanınca', + 'when I receive %msgHat': + '%msgHat haberi gelince', + 'broadcast %msg': + '%msg yayımla', + 'broadcast %msg and wait': + '%msg yayımla ve bekle', + 'Message name': + 'Mesaj adı', + 'message': + 'mesaj', + 'any message': + 'herhangi bir mesaj', + 'wait %n secs': + '%n sn bekle', + 'wait until %b': + '%b olana kadar bekle', + 'forever %c': + 'sürekli %c', + 'repeat %n %c': + '%n kere tekrarla %c', + 'repeat until %b %c': + '%b olana kadar tekrarla %c', + 'if %b %c': + 'eğer %b %c', + 'if %b %c else %c': + 'eğer %b %c değilse %c', + 'report %s': + 'rapor %s', + 'stop %stopChoices': + 'durdur %stopChoices', + 'all': + 'tümü', + 'this script': + 'bu script', + 'this block': + 'bu blok', + 'stop %stopOthersChoices': + 'durdur %stopOthersChoices', + 'all but this script': + 'bu hariç diğerleri', + 'other scripts in sprite': + 'bu karakter içindeki diğer betikler', + 'pause all %pause': + 'tümünü beklet %pause', + 'run %cmdRing %inputs': + 'çalıştır %cmdRing %inputs', + 'launch %cmdRing %inputs': + 'başlat %cmdRing %inputs', + 'call %repRing %inputs': + 'çağır %repRing %inputs', + 'run %cmdRing w/continuation': + 'çalıştır %cmdRing w/sürekli', + 'call %cmdRing w/continuation': + 'çağır %cmdRing w/sürekli', + 'warp %c': + 'çarpıtmak %c', + 'when I start as a clone': + 'Klon olarak başlatığında', + 'create a clone of %cln': + '%cln klonunu oluştur', + 'myself': + 'kendim', + 'delete this clone': + 'bu klonu sil', + + // sensing: + 'touching %col ?': + '%col a değiyor mu?', + 'touching %clr ?': + '%clr rengine değiyor mu?', + 'color %clr is touching %clr ?': + '%clr rengi %clr rengine değdi mi?', + 'ask %s and wait': + '%s sor ve bekle', + 'what\'s your name?': + 'adınız ne?', + 'answer': + 'cevap', + 'mouse x': + 'Fare x-konumu', + 'mouse y': + 'Fare y-konumu', + 'mouse down?': + 'fareye basılı mı?', + 'key %key pressed?': + '%key tuşu basılı mı?', + 'distance to %dst': + '%dst a mesafe', + 'reset timer': + 'zamanlayıcıyı sıfırla', + 'timer': + 'zamanlayıcı', + '%att of %spr': + '%spr nun %att ', + 'http:// %s': + 'http:// %s', + 'turbo mode?': + 'Turbomod?', + 'set turbo mode to %b': + 'turbo modu %b yap', + + 'filtered for %clr': + '%clr için filtrele', + 'stack size': + 'yığın büyüklüğü', + 'frames': + 'çerçeveler', + + // operators: + '%n mod %n': + '%n mod %n', + 'round %n': + '%n yuvarla', + '%fun of %n': + '%fun un %n si', + 'pick random %n to %n': + '%n ile %n arasında rastgele bir sayı seç', + '%b and %b': + '%b ve %b', + '%b or %b': + '%b veya %b', + 'not %b': + '%b değil', + 'true': + 'doğru', + 'false': + 'yanlış', + 'join %words': + '%words birleştir', + 'split %s by %delim': + 'parçala ayır %s , %delim e gore', + 'hello': + 'merhaba', + 'world': + 'dünya', + 'letter %n of %s': + '%n in harfleri %s', + 'length of %s': + '%s in uzunluğu', + 'unicode of %s': + '%s in unicode hali', + 'unicode %n as letter': + 'harf olarak %n in unicode hali', + 'is %s a %typ ?': + '%s bir %typ mi?', + 'is %s identical to %s ?': + '%s ile %s aynı mı?', + + 'type of %s': + '%s un tipi', + + // variables: + 'Make a variable': + 'Değişken oluştur', + 'Variable name': + 'Değişken adı', + 'Script variable name': + 'Script değişken adı', + 'Delete a variable': + 'Değişkeni sil', + + 'set %var to %s': + '%var %s olsun', + 'change %var by %n': + '%var i %n değiştir', + 'show variable %var': + '%var değişkenini göster', + 'hide variable %var': + '%var değişkenini gizle', + 'script variables %scriptVars': + '%scriptVars script değişkenleri', + + // lists: + 'list %exp': + 'Liste %exp', + '%s in front of %l': + '%s nin %l önündeki', + 'item %idx of %l': + 'item %idx of %l', + 'all but first of %l': + '%l in ilk elemanı hariç tümü', + 'length of %l': + '%l nin uzunluğu', + '%l contains %s': + '%l %s i içeriyor', + 'thing': + 'şey', + 'add %s to %l': + '%s i %l ye ekle', + 'delete %ida of %l': + 'sil %ida nın %l', + 'insert %s at %idx of %l': + 'ekle %s %idx indeksine %l listesinin', + 'replace item %idx of %l with %s': + 'yer değiştir %idx %l listesinin %s ile', + + // other + 'Make a block': + 'Yeni bir blok oluştur', + + // menus + // snap menu + 'About...': + 'Snap Hakkında!...', + 'Reference manual': + 'El kitapçığı', + 'Snap! website': + 'Snap! web sitesi', + 'Download source': + 'Indirilebilir kaynak', + 'Switch back to user mode': + 'Kullanıcı moduna geri dön', + 'disable deep-Morphic\ncontext menus\nand show user-friendly ones': + 'verl\u00e4sst Morphic', + 'Switch to dev mode': + 'geliştirici moduna dön', + 'enable Morphic\ncontext menus\nand inspectors,\nnot user-friendly!': + 'etkinleştir Morfik\niçerik menüsü\nve gözlemleme\nkullanıcı dostu değil ', + + // project menu + 'Project notes...': + 'Proje notları...', + 'New': + 'Yeni', + 'Open...': + 'Aç...', + 'Save': + 'Kaydet', + 'Save As...': + 'Farklı kaydet...', + 'Import...': + 'İçeri aktar...', + 'file menu import hint': + 'dosya menü içeri aktar ipucu', + 'Export project as plain text...': + 'Düz metin olarak projeyi dışarı aktar...', + 'Export project...': + 'Projeyi dışarı aktar...', + 'show project data as XML\nin a new browser window': + 'Yeni bir pencerede\nproje verilerini XML olarak göster', + 'Export blocks...': + 'Blokları dışarı aktar...', + 'show global custom block definitions as XML\nin a new browser window': + 'blok tanımlarını XML dosyası olarak\nyeni bir pencerede göster', + 'Import tools': + 'Araçları içeri aktar', + 'load the official library of\npowerful blocks': + 'güçlü blokların\nresmi kütüphanesini yükletin', + 'Libraries...': + 'Kütüphaneler...', + 'Import library': + 'Kütüphaneyi içeri aktar', + + // cloud menu + 'Login...': + 'Giriş yap...', + 'Signup...': + 'Kayıt ol...', + + // settings menu + 'Language...': + 'Dil...', + 'Zoom blocks...': + 'Yaklaşma blokları...', + 'Stage size...': + 'Sahne büyüklüğü...', + 'Stage size': + 'Sahne büyüküğü', + 'Stage width': + 'Sahne genişliği', + 'Stage height': + 'Sahne yüksekliği', + 'Default': + 'Varsayılan', + 'Blurred shadows': + 'Bulanış gölgeler', + 'uncheck to use solid drop\nshadows and highlights': + 'katı gölge ve ışıkları kullanmak için seçimi kaldırın', + 'check to use blurred drop\nshadows and highlights': + 'gölge ve ışıkları bulanık hale getirmek için\nseçim yapın', + 'Zebra coloring': + 'çizgili boyama', + 'check to enable alternating\ncolors for nested blocks': + 'iç içe bloklarda değişmeli renkler için\ntik atın', + 'uncheck to disable alternating\ncolors for nested block': + 'iç içe bloklarda değişmeli renkleri\n kaldırmak için seçimi kaldırın', + 'Dynamic input labels': + 'Dinamik girdi etiketleri', + 'uncheck to disable dynamic\nlabels for variadic inputs': + 'farklı girdiler için dinamik etkiletlemeyi\nkaldırmak için seçimi kaldırın', + 'check to enable dynamic\nlabels for variadic inputs': + 'farklı girdiler için dinamik etkiletlemeyi\netkinleştirmek için seçimi kaldırın', + 'Prefer empty slot drops': + 'Boş slotları tercih et', + 'settings menu prefer empty slots hint': + 'boş slotlar için menü ayarları', + 'uncheck to allow dropped\nreporters to kick out others': + 'uncheck to allow dropped\nreporters to kick out others', + 'Long form input dialog': + 'girdi dialogları için uzun form', + 'Plain prototype labels': + 'Düz protatip etkiletleri', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'prototip etiketinde (+) sembolünün\ngörünmesi için seçimi kaldırın', + 'check to hide (+) symbols\nin block prototype labels': + 'prototip etiketinde (+) sembolünün\ngizlenmesi için seçin', + 'check to always show slot\ntypes in the input dialog': + 'girdi dialoglarında\ntipinin görünmesi için seçin', + 'uncheck to use the input\ndialog in short form': + 'girdi dialoglarını kısa form\nolarak kullanmak için seçimi kaldırın', + 'Virtual keyboard': + 'Sanal klavye', + 'uncheck to disable\nvirtual keyboard support\nfor mobile devices': + 'mobil araçlar için\nsanal klavye desteğini\nkaldırmak için seçimi kaldırın', + 'check to enable\nvirtual keyboard support\nfor mobile devices': + 'mobil araçlar için\nsanal klavye desteğini\naktifleştirmek için seçin', + 'Input sliders': + 'Girdi sürgüleri', + 'uncheck to disable\ninput sliders for\nentry fields': + 'girdi alanlarındanki girdi sürgülerini etkisizleştirmek için seçimi kaldırın', + 'check to enable\ninput sliders for\nentry fields': + 'girdi alanlarındanki girdi sürgülerini etkisizleştirmek için seçin', + 'Clicking sound': + 'Sesi tıklayın', + 'uncheck to turn\nblock clicking\nsound off': + 'sesi kapatmak için\nseçimi kaldırın', + 'check to turn\nblock clicking\nsound on': + 'sesi açmak için\nseçim yapın', + 'Animations': + 'Animasyonlar', + 'uncheck to disable\nIDE animations': + 'animasyonları etkisezliştirmek için\nseçimi kaldırın', + 'Turbo mode': + 'Turbo Mod', + 'check to prioritize\nscript execution': + 'Betikleme çalışmasına öncelik\nvermek için seçim yapın', + 'uncheck to run scripts\nat normal speed': + 'betiklemelerin normal hızla çalışması\niçin seçimi kaldırın', + 'check to enable\nIDE animations': + 'IDE animasyonlarını\naktifleştirmek için seçim yapın', + 'Thread safe scripts': + 'Güvenli betik parçacığı', + 'uncheck to allow\nscript reentrance': + 'betiklemeye tekrar girişe\nizin vermek için seçimi kaldırın', + 'check to disallow\nscript reentrance': + 'vbetiklemeye tekrar girişe\nizin vermemek için seçimi kaldırın', + 'Prefer smooth animations': + 'Pürüzsüz animayonu tercih et', + 'uncheck for greater speed\nat variable frame rates': + 'çerçevelerin daha hızlanması\niçin seçimi kaldırın', + 'check for smooth, predictable\nanimations across computers': + 'bilgisayarlar arası düz,tahmin edilebilir\nanimasyonlar için seçim yapın', + 'Flat line ends': + 'Düz çizgi bitimleri', + 'check for flat ends of lines': + 'düz biten bitim çizgileri\niçin seçin', + 'uncheck for round ends of lines': + 'yumuşak köşeli bitim çizgileri\niçin seçimi kaldırın', + + // inputs + 'with inputs': + 'girdi ile', + 'input names:': + 'girdi isimleri:', + 'Input Names:': + 'girdi isimleri:', + 'input list:': + 'girdi listesi:', + + // context menus: + 'help': + 'yardım', + + // palette: + 'hide primitives': + 'primitifleri gizle', + 'show primitives': + 'primitifleri göster', + + // blocks: + 'help...': + 'yardım...', + 'relabel...': + 'yeniden adlandır...', + 'duplicate': + 'kopyala', + 'make a copy\nand pick it up': + 'kopya oluştur\nve onu al', + 'only duplicate this block': + 'sadece bu bloğun kopyasını oluştur', + 'delete': + 'sil', + 'script pic...': + 'betik resmi...', + 'open a new window\nwith a picture of this script': + 'bu betiğin resmini\n yeni bir pencerede açın', + 'ringify': + 'ringify', + 'unringify': + 'unringify', + + // custom blocks: + 'delete block definition...': + 'blok tanımlarını sil...', + 'edit...': + 'düzenle...', + + // sprites: + 'edit': + 'düzenle', + 'move': + 'hareket et', + 'detach from': + 'parçayı ayır', + 'detach all parts': + 'tüm parçaları ayır', + 'export...': + 'dşıarı aktar...', + + // stage: + 'show all': + 'hepsini göster', + 'pic...': + 'resimler...', + 'open a new window\nwith a picture of the stage': + 'sahnenin bir resmi ile\n yeni bir pence aç', + + // scripting area + 'clean up': + 'temizle', + 'arrange scripts\nvertically': + 'scriptleri dikey olarak düzenle', + 'add comment': + 'yorum ekle', + 'undrop': + 'bırak', + 'undo the last\nblock drop\nin this pane': + 'levhaya bıraktığın\n son bloğu geri al', + 'scripts pic...': + 'script resimleri...', + 'open a new window\nwith a picture of all scripts': + 'tüm scriptleri bir resim ile\n yeni bir pencerede aç', + 'make a block...': + 'bir blok oluştur...', + + // costumes + 'rename': + 'yeniden adlandır', + 'export': + 'dışarı aktar', + 'rename costume': + 'Köstümü yeniden adlandır', + + // sounds + 'Play sound': + 'Sesi oynat', + 'Stop sound': + 'Sesi durdur', + 'Stop': + 'Durdur', + 'Play': + 'Oyna', + 'rename sound': + 'sesi yeniden adlandır', + + // dialogs + // buttons + 'OK': + 'TAMAM', + 'Ok': + 'Tamam', + 'Cancel': + 'İptal', + 'Yes': + 'Evet', + 'No': + 'Hayır', + + // help + 'Help': + 'Yardım', + + // zoom blocks + 'Zoom blocks': + 'Yakınlaştırma blokları', + 'build': + 'inşaa et', + 'your own': + 'kendinizin', + 'blocks': + 'blokları', + 'normal (1x)': + 'normal (1x)', + 'demo (1.2x)': + 'Demo (1.2x)', + 'presentation (1.4x)': + 'Sunum (1.4x)', + 'big (2x)': + 'büyük (2x)', + 'huge (4x)': + 'kocaman (4x)', + 'giant (8x)': + 'devasa (8x)', + 'monstrous (10x)': + 'çok büyük (10x)', + + // Project Manager + 'Untitled': + 'Adsız', + 'Open Project': + 'Projec aç', + '(empty)': + '(boş)', + 'Saved!': + 'Kaydedildi!', + 'Delete Project': + 'Projeyi sil', + 'Are you sure you want to delete': + 'Silmek istediğinize emin misiniz?', + 'rename...': + 'yeniden adlandır...', + + // costume editor + 'Costume Editor': + 'Kostüm editörü', + 'click or drag crosshairs to move the rotation center': + 'dönme merkezini hareket ettirmek için referans noktasına tıklayın ya da sürükleyin', + + // project notes + 'Project Notes': + 'Proje Notları', + + // new project + 'New Project': + 'Yeni proje', + 'Replace the current project with a new one?': + 'Şu an ki projeyi yenisiyle değiştirelim mi?', + + // save project + 'Save Project As...': + 'Projeyi farklı kaydet...', + + // export blocks + 'Export blocks': + 'Blokları dışarı aktar', + 'Import blocks': + 'Blokları içeri aktar', + 'this project doesn\'t have any\ncustom global blocks yet': + 'bu proje henüz herhangi bir global blok içermiyor', + 'select': + 'seç', + 'none': + 'hiçbiri', + + // variable dialog + 'for all sprites': + 'tüm karakterler için', + 'for this sprite only': + 'sadece bu karakter için', + + // block dialog + 'Change block': + 'Bloğu değiştir', + 'Command': + 'Komut', + 'Reporter': + 'Fonksiyon', + 'Predicate': + 'Beyan etme', + + // block editor + 'Block Editor': + 'Blok Editörü', + 'Apply': + 'Uygula', + + // block deletion dialog + 'Delete Custom Block': + 'Bloğu Sil', + 'block deletion dialog text': + 'BSoll dieser Block mit allen seinen Exemplare\n' + + 'wirklich gel\u00f6scht werden?', + + // input dialog + 'Create input name': + 'Girdi oluştur', + 'Edit input name': + 'Girdiyi düzenle', + 'Edit label fragment': + 'Etiketi düzenle', + 'Title text': + 'Başlık', + 'Input name': + 'Girdi Adı', + 'Delete': + 'Sil', + 'Object': + 'Obje', + 'Number': + 'Sayı', + 'Text': + 'Metin', + 'List': + 'Liste', + 'Any type': + 'Herhangi bir tip', + 'Boolean (T/F)': + 'Mantıksal (D/Y)', + 'Command\n(inline)': + 'Komut', + 'Command\n(C-shape)': + 'Komut\n(C-Şenlinde)', + 'Any\n(unevaluated)': + 'Herhangi\n(değerlendirilmemiş)', + 'Boolean\n(unevaluated)': + 'Mantıksal\n(değerlendirilmemiş)', + 'Single input.': + 'Tek girdi.', + 'Default Value:': + 'Varsayılan değer:', + 'Multiple inputs (value is list of inputs)': + 'Çoklu girdi (liste olarak)', + 'Upvar - make internal variable visible to caller': + 'İç değişkeni çağırıcıya görünür kıl', + + // About Snap + 'About Snap': + 'Snap hakkında', + 'Back...': + 'Geriye...', + 'License...': + 'Lisans...', + 'Modules...': + 'Komponenten...', + 'Credits...': + 'Katkıda bulunanlar...', + 'Translators...': + 'Çevirmenler...', + 'License': + 'Lisans', + 'current module versions:': + 'Şu anki versiyonlar', + 'Contributors': + 'Katkıda bulunanlar', + 'Translations': + 'Çeviriler', + + // variable watchers + 'normal': + 'normal', + 'large': + 'geniş', + 'slider': + 'sürgü', + 'slider min...': + 'sürgü en düşük...', + 'slider max...': + 'sürgü en yüksek...', + 'import...': + 'içeri aktar...', + 'Slider minimum value': + 'Sürgünün en düşük değeri', + 'Slider maximum value': + 'Sürgünün en yüksek değeri', + + // list watchers + 'length: ': + 'uzunluk: ', + + // coments + 'add comment here...': + '.. buraya yorum ekle', + + // drow downs + // directions + '(90) right': + '(90) sağ', + '(-90) left': + '(-90) sol', + '(0) up': + '(0) yukarı', + '(180) down': + '(180) aşağı', + + // collision detection + 'mouse-pointer': + 'Fare-İşaretçisi', + 'corner': + 'kenar', + 'pen trails': + 'Malspuren', + + // costumes + 'Turtle': + 'Kaplumbağa', + 'Empty': + 'Boş', + + // graphical effects + 'brightness': + 'parlaklık', + 'ghost': + 'hayalet', + 'negative': + 'negatif', + 'comic': + 'komik', + 'confetti': + 'konfeti', + + // keys + 'space': + 'boşluk', + 'up arrow': + 'yukarı ok', + 'down arrow': + 'aşağı ok', + 'right arrow': + 'sol ok', + 'left arrow': + 'sağ ok', + '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...': + 'yeni...', + + // math functions + 'abs': + 'MutlakDeğer', + 'floor': + 'alt değer', + 'sqrt': + 'Karekök', + 'sin': + 'sin', + 'cos': + 'cos', + 'tan': + 'tan', + 'asin': + 'asin', + 'acos': + 'acos', + 'atan': + 'atan', + 'ln': + 'ln', + 'e^': + 'e^', + + // delimiters + 'letter': + 'harf', + 'whitespace': + 'alfabe dışı', + 'line': + 'çizgi', + 'tab': + 'sekme', + 'cr': + 'karakter', + + // data types + 'number': + 'sayı', + 'text': + 'metin', + 'Boolean': + 'mantıksal', + 'list': + 'Liste', + 'command': + 'komut', + 'reporter': + 'fonksiyonlar', + 'predicate': + 'ön ek', + + // list indices + 'last': + 'son', + 'any': + 'herhangi' +}; diff --git a/libraries/LIBRARIES b/libraries/LIBRARIES index f11aa852..8905553b 100644 --- a/libraries/LIBRARIES +++ b/libraries/LIBRARIES @@ -3,3 +3,5 @@ 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 diff --git a/libraries/cases.xml b/libraries/cases.xml new file mode 100644 index 00000000..8a170f7b --- /dev/null +++ b/libraries/cases.xml @@ -0,0 +1 @@ +
cont
catchtag
\ No newline at end of file diff --git a/libraries/iteration-composition.xml b/libraries/iteration-composition.xml index 0f16bb6e..d2afe059 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. \ 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
\ No newline at end of file diff --git a/libraries/leap-library.xml b/libraries/leap-library.xml new file mode 100644 index 00000000..b8252204 --- /dev/null +++ b/libraries/leap-library.xml @@ -0,0 +1 @@ +
x1
yaw1
x1
1
1
\ No newline at end of file diff --git a/libraries/stream-tools.xml b/libraries/stream-tools.xml index 66efceca..e2775c54 100644 --- a/libraries/stream-tools.xml +++ b/libraries/stream-tools.xml @@ -1 +1 @@ -
4234
1datamapmany1data lists
1001
\ No newline at end of file +
4234
1datamapmany1data lists
1001
1
\ No newline at end of file diff --git a/lists.js b/lists.js index 6fc95451..94592287 100644 --- a/lists.js +++ b/lists.js @@ -61,7 +61,7 @@ PushButtonMorph, SyntaxElementMorph, Color, Point, WatcherMorph, StringMorph, SpriteMorph, ScrollFrameMorph, CellMorph, ArrowMorph, MenuMorph, snapEquals, Morph, isNil, localize, MorphicPreferences*/ -modules.lists = '2015-June-25'; +modules.lists = '2015-July-27'; var List; var ListWatcherMorph; @@ -158,9 +158,8 @@ List.prototype.add = function (element, index) { if no index is specifed, append the element */ var idx = index || this.length() + 1, - obj = element === 0 ? 0 - : element === false ? false - : element || null; + obj = isNil(element) ? null : element; + this.becomeArray(); this.contents.splice(idx - 1, 0, obj); this.changed(); diff --git a/locale.js b/locale.js index af1e0f4f..773483dc 100644 --- a/locale.js +++ b/locale.js @@ -42,7 +42,7 @@ /*global modules, contains*/ -modules.locale = '2015-June-25'; +modules.locale = '2015-July-27'; // Global stuff @@ -475,3 +475,28 @@ SnapTranslator.dict.te = { 'last_changed': '2015-02-20' }; + +SnapTranslator.dict.tr = { + // translations meta information + 'language_name': + 'Türkçe', + 'language_translator': + 'Hakan Atas', + 'translator_e-mail': + 'hakanatas@gmail.com', + 'last_changed': + '2015-7-27' +}; + +SnapTranslator.dict.hu = { + // translations meta information + 'language_name': + 'Magyar', + 'language_translator': + 'Makány György', + 'translator_e-mail': + 'makany.gyorgy@gmail.com', + 'last_changed': + '2015-07-27' +}; + diff --git a/morphic.js b/morphic.js index f26c7bf7..e704f4a0 100644 --- a/morphic.js +++ b/morphic.js @@ -1048,7 +1048,7 @@ /*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio, FileList, getBlurredShadowSupport*/ -var morphicVersion = '2015-June-26'; +var morphicVersion = '2015-July-28'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -2501,6 +2501,32 @@ Morph.prototype.keepWithin = function (aMorph) { } }; +Morph.prototype.scrollIntoView = function () { + var leftOff, rightOff, topOff, bottomOff, + sf = this.parentThatIsA(ScrollFrameMorph); + if (!sf) {return; } + rightOff = Math.min( + this.fullBounds().right() - sf.right(), + sf.contents.right() - sf.right() + ); + if (rightOff > 0) { + sf.contents.moveBy(new Point(-rightOff, 0)); + } + leftOff = this.fullBounds().left() - sf.left(); + if (leftOff < 0) { + sf.contents.moveBy(new Point(-leftOff, 0)); + } + topOff = this.fullBounds().top() - sf.top(); + if (topOff < 0) { + sf.contents.moveBy(new Point(0, -topOff)); + } + bottomOff = this.fullBounds().bottom() - sf.bottom(); + if (bottomOff > 0) { + sf.contents.moveBy(new Point(0, -bottomOff)); + } + sf.adjustScrollBars(); +}; + // Morph accessing - dimensional changes requiring a complete redraw Morph.prototype.setExtent = function (aPoint) { @@ -3092,6 +3118,13 @@ Morph.prototype.rootForGrab = function () { return this.parent.rootForGrab(); }; +Morph.prototype.isCorrectingOutsideDrag = function () { + // make sure I don't "trail behind" the hand when dragged + // override for morphs that you want to be dragged outside + // their full bounds + return true; +}; + Morph.prototype.wantsDropOf = function (aMorph) { // default is to answer the general flag - change for my heirs if ((aMorph instanceof HandleMorph) || @@ -3174,6 +3207,17 @@ Morph.prototype.move = function () { ); }; +Morph.prototype.moveCenter = function () { + this.world().activeHandle = new HandleMorph( + this, + null, + null, + null, + null, + 'moveCenter' + ); +}; + Morph.prototype.hint = function (msg) { var m, text; text = msg; @@ -3722,7 +3766,7 @@ HandleMorph.prototype.init = function ( this.target = target || null; this.minExtent = new Point(minX || 0, minY || 0); this.inset = new Point(insetX || 0, insetY || insetX || 0); - this.type = type || 'resize'; // can also be 'move' + this.type = type || 'resize'; // can also be 'move', 'moveCenter' HandleMorph.uber.init.call(this); this.color = new Color(255, 255, 255); this.isDraggable = false; @@ -3747,11 +3791,15 @@ HandleMorph.prototype.drawNew = function () { ); this.image = this.normalImage; if (this.target) { - this.setPosition( - this.target.bottomRight().subtract( - this.extent().add(this.inset) - ) - ); + if (this.type === 'moveCenter') { + this.setCenter(this.target.center()); + } else { // 'resize', 'move' + this.setPosition( + this.target.bottomRight().subtract( + this.extent().add(this.inset) + ) + ); + } this.target.add(this); this.target.changed(); } @@ -3763,6 +3811,7 @@ HandleMorph.prototype.drawOnCanvas = function ( shadowColor ) { var context = aCanvas.getContext('2d'), + isSquare = (this.type.indexOf('move') === 0), p1, p11, p2, @@ -3774,7 +3823,7 @@ HandleMorph.prototype.drawOnCanvas = function ( context.strokeStyle = color.toString(); - if (this.type === 'move') { + if (isSquare) { p1 = this.bottomLeft().subtract(this.position()); p11 = p1.copy(); @@ -3811,7 +3860,7 @@ HandleMorph.prototype.drawOnCanvas = function ( context.strokeStyle = shadowColor.toString(); - if (this.type === 'move') { + if (isSquare) { p1 = this.bottomLeft().subtract(this.position()); p11 = p1.copy(); @@ -3853,12 +3902,17 @@ HandleMorph.prototype.step = null; HandleMorph.prototype.mouseDownLeft = function (pos) { var world = this.root(), - offset = pos.subtract(this.bounds.origin), + offset, myself = this; if (!this.target) { return null; } + if (this.type === 'moveCenter') { + offset = pos.subtract(this.center()); + } else { + offset = pos.subtract(this.bounds.origin); + } this.step = function () { var newPos, newExt; if (world.hand.mouseButton) { @@ -3875,6 +3929,8 @@ HandleMorph.prototype.mouseDownLeft = function (pos) { myself.extent().add(myself.inset) ) ); + } else if (this.type === 'moveCenter') { + myself.target.setCenter(newPos); } else { // type === 'move' myself.target.setPosition( newPos.subtract(this.target.extent()) @@ -4680,7 +4736,7 @@ CursorMorph.prototype.accept = function () { if (world) { world.stopEditing(); } - this.escalateEvent('accept', null); + this.escalateEvent('accept', this); }; CursorMorph.prototype.cancel = function () { @@ -4689,7 +4745,7 @@ CursorMorph.prototype.cancel = function () { if (world) { world.stopEditing(); } - this.escalateEvent('cancel', null); + this.escalateEvent('cancel', this); }; CursorMorph.prototype.undo = function () { @@ -6652,6 +6708,8 @@ MenuMorph.prototype.init = function (target, title, environment, fontSize) { this.label = null; this.world = null; this.isListContents = false; + this.hasFocus = false; + this.selection = null; // initialize inherited properties: MenuMorph.uber.init.call(this); @@ -6871,6 +6929,7 @@ MenuMorph.prototype.popup = function (world, pos) { } world.add(this); world.activeMenu = this; + this.world = world; // optionally enable keyboard support this.fullChanged(); }; @@ -6901,6 +6960,105 @@ MenuMorph.prototype.popUpCenteredInWorld = function (world) { ); }; +// MenuMorph keyboard accessibility + +MenuMorph.prototype.getFocus = function () { + this.world.keyboardReceiver = this; + this.selection = null; + this.selectFirst(); + this.hasFocus = true; +}; + +MenuMorph.prototype.processKeyDown = function (event) { + //console.log(event.keyCode); + switch (event.keyCode) { + case 13: // 'enter' + case 32: // 'space' + if (this.selection) { + this.selection.mouseClickLeft(); + } + return; + case 27: // 'esc' + return this.destroy(); + case 38: // 'up arrow' + return this.selectUp(); + case 40: // 'down arrow' + return this.selectDown(); + default: + nop(); + } +}; + +MenuMorph.prototype.processKeyUp = function (event) { + nop(event); +}; + +MenuMorph.prototype.processKeyPress = function (event) { + nop(event); +}; + +MenuMorph.prototype.selectFirst = function () { + var i; + for (i = 0; i < this.children.length; i += 1) { + if (this.children[i] instanceof MenuItemMorph) { + this.select(this.children[i]); + return; + } + } +}; + +MenuMorph.prototype.selectUp = function () { + var triggers, idx; + + triggers = this.children.filter(function (each) { + return each instanceof MenuItemMorph; + }); + if (!this.selection) { + if (triggers.length) { + this.select(triggers[0]); + } + return; + } + idx = triggers.indexOf(this.selection) - 1; + if (idx < 0) { + idx = triggers.length - 1; + } + this.select(triggers[idx]); +}; + +MenuMorph.prototype.selectDown = function () { + var triggers, idx; + + triggers = this.children.filter(function (each) { + return each instanceof MenuItemMorph; + }); + if (!this.selection) { + if (triggers.length) { + this.select(triggers[0]); + } + return; + } + idx = triggers.indexOf(this.selection) + 1; + if (idx >= triggers.length) { + idx = 0; + } + this.select(triggers[idx]); +}; + +MenuMorph.prototype.select = function (aMenuItem) { + this.unselectAllItems(); + aMenuItem.image = aMenuItem.highlightImage; + aMenuItem.changed(); + this.selection = aMenuItem; +}; + +MenuMorph.prototype.destroy = function () { + if (this.hasFocus) { + this.world.keyboardReceiver = null; + } + MenuMorph.uber.destroy.call(this); +}; + // StringMorph ///////////////////////////////////////////////////////// // I am a single line of text @@ -9586,32 +9744,16 @@ HandMorph.prototype.processMouseMove = function (event) { this.grabOrigin = this.morphToGrab.situation(); } if (morph) { - // if the mouse has left its fullBounds, center it + // if the mouse has left its fullBounds, allow to center it fb = morph.fullBounds(); - if (!fb.containsPoint(pos)) { + if (!fb.containsPoint(pos) && + morph.isCorrectingOutsideDrag()) { this.bounds.origin = fb.center(); this.grab(morph); this.setPosition(pos); } } } - -/* - original, more cautious code for grabbing Morphs, - retained in case of needing to fall back: - - if (morph === this.morphToGrab) { - if (morph.isDraggable) { - this.grab(morph); - } else if (morph.isTemplate) { - morph = morph.fullCopy(); - morph.isTemplate = false; - morph.isDraggable = true; - this.grab(morph); - } - } -*/ - } this.mouseOverList.forEach(function (old) { @@ -10399,6 +10541,13 @@ WorldMorph.prototype.contextMenu = function () { 'inspect', 'open a window on\nall properties' ); + menu.addItem( + "screenshot...", + function () { + window.open(this.fullImageClassic().toDataURL()); + }, + 'open a new window\nwith a picture of this morph' + ); menu.addLine(); menu.addItem( "restore display", diff --git a/objects.js b/objects.js index 3f95385d..bebff6cf 100644 --- a/objects.js +++ b/objects.js @@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.objects = '2015-June-25'; +modules.objects = '2015-July-27'; var SpriteMorph; var StageMorph; @@ -583,7 +583,7 @@ SpriteMorph.prototype.initBlocks = function () { }, /* migrated to a newer block version: - + receiveClick: { type: 'hat', category: 'control', @@ -900,17 +900,20 @@ SpriteMorph.prototype.initBlocks = function () { reifyScript: { type: 'ring', category: 'other', - spec: '%rc %ringparms' + spec: '%rc %ringparms', + alias: 'command ring lambda' }, reifyReporter: { type: 'ring', category: 'other', - spec: '%rr %ringparms' + spec: '%rr %ringparms', + alias: 'reporter ring lambda' }, reifyPredicate: { type: 'ring', category: 'other', - spec: '%rp %ringparms' + spec: '%rp %ringparms', + alias: 'predicate ring lambda' }, reportSum: { type: 'reporter', @@ -920,12 +923,14 @@ SpriteMorph.prototype.initBlocks = function () { reportDifference: { type: 'reporter', category: 'operators', - spec: '%n \u2212 %n' + spec: '%n \u2212 %n', + alias: '-' }, reportProduct: { type: 'reporter', category: 'operators', - spec: '%n \u00D7 %n' + spec: '%n \u00D7 %n', + alias: '*' }, reportQuotient: { type: 'reporter', @@ -1103,6 +1108,13 @@ SpriteMorph.prototype.initBlocks = function () { spec: 'script variables %scriptVars' }, + // inheritance - experimental + doDeleteAttr: { + type: 'command', + category: 'variables', + spec: 'delete %shd' + }, + // Lists reportNewList: { type: 'reporter', @@ -1348,11 +1360,13 @@ SpriteMorph.prototype.init = function (globals) { 'confetti': 0 }; + // sprite inheritance + this.exemplar = null; + SpriteMorph.uber.init.call(this); this.isDraggable = true; this.isDown = false; - this.heading = 90; this.changed(); this.drawNew(); @@ -1642,7 +1656,8 @@ SpriteMorph.prototype.variableBlock = function (varName) { SpriteMorph.prototype.blockTemplates = function (category) { var blocks = [], myself = this, varNames, button, - cat = category || 'motion', txt; + cat = category || 'motion', txt, + inheritedVars = this.inheritedVariableNames(); function block(selector) { if (StageMorph.prototype.hiddenPrimitives[selector]) { @@ -1657,6 +1672,9 @@ SpriteMorph.prototype.blockTemplates = function (category) { var newBlock = SpriteMorph.prototype.variableBlock(varName); newBlock.isDraggable = false; newBlock.isTemplate = true; + if (contains(inheritedVars, varName)) { + newBlock.ghost(); + } return newBlock; } @@ -1705,15 +1723,18 @@ SpriteMorph.prototype.blockTemplates = function (category) { } function addVar(pair) { + var ide; if (pair) { - if (myself.variables.silentFind(pair[0])) { + if (myself.isVariableNameInUse(pair[0], pair[1])) { myself.inform('that name is already in use'); } else { + ide = myself.parentThatIsA(IDE_Morph); myself.addVariable(pair[0], pair[1]); - myself.toggleVariableWatcher(pair[0], pair[1]); - myself.blocksCache[cat] = null; - myself.paletteCache[cat] = null; - myself.parentThatIsA(IDE_Morph).refreshPalette(); + if (!myself.showingVariableWatcher(pair[0])) { + myself.toggleVariableWatcher(pair[0], pair[1]); + } + ide.flushBlocksCache('variables'); // b/c of inheritance + ide.refreshPalette(); } } } @@ -2028,7 +2049,7 @@ SpriteMorph.prototype.blockTemplates = function (category) { button.showHelp = BlockMorph.prototype.showHelp; blocks.push(button); - if (this.variables.allNames().length > 0) { + if (this.deletableVariableNames().length > 0) { button = new PushButtonMorph( null, function () { @@ -2037,7 +2058,7 @@ SpriteMorph.prototype.blockTemplates = function (category) { null, myself ); - myself.variables.allNames().forEach(function (name) { + myself.deletableVariableNames().forEach(function (name) { menu.addItem(name, name); }); menu.popUpAtHand(myself.world()); @@ -2067,6 +2088,15 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push(block('doHideVar')); blocks.push(block('doDeclareVariables')); + // inheritance: + + if (StageMorph.prototype.enableInheritance) { + blocks.push('-'); + blocks.push(block('doDeleteAttr')); + } + + /////////////////////////////// + blocks.push('='); blocks.push(block('reportNewList')); @@ -2357,15 +2387,30 @@ SpriteMorph.prototype.freshPalette = function (category) { // SpriteMorph blocks searching -SpriteMorph.prototype.blocksMatching = function (searchString, strictly) { +SpriteMorph.prototype.blocksMatching = function ( + searchString, + strictly, + types, // optional, ['hat', 'command', 'reporter', 'predicate'] + varNames // optional, list of reachable unique variable names +) { // answer an array of block templates whose spec contains // the given search string, ordered by descending relevance + // types is an optional array containing block types the search + // is limited to, e.g. "command", "hat", "reporter", "predicate". + // Note that "predicate" is not subsumed by "reporter" and has + // to be specified explicitly. + // if no types are specified all blocks are searched var blocks = [], blocksDict, myself = this, search = searchString.toLowerCase(), stage = this.parentThatIsA(StageMorph); + if (!types || !types.length) { + types = ['hat', 'command', 'reporter', 'predicate', 'ring']; + } + if (!varNames) {varNames = []; } + function labelOf(aBlockSpec) { var words = (BlockMorph.prototype.parseSpec(aBlockSpec)), filtered = words.filter( @@ -2396,29 +2441,39 @@ SpriteMorph.prototype.blocksMatching = function (searchString, strictly) { return newBlock; } + // variable getters + varNames.forEach(function (vName) { + var rel = relevance(labelOf(vName), search); + if (rel !== -1) { + blocks.push([myself.variableBlock(vName), rel + '1']); + } + }); // custom blocks [this.customBlocks, stage.globalBlocks].forEach(function (blocksList) { blocksList.forEach(function (definition) { - var spec = localize(definition.blockSpec()).toLowerCase(), - rel = relevance(labelOf(spec), search); - if (rel !== -1) { - blocks.push([definition.templateInstance(), rel + '1']); + if (contains(types, definition.type)) { + var spec = localize(definition.blockSpec()).toLowerCase(), + rel = relevance(labelOf(spec), search); + if (rel !== -1) { + blocks.push([definition.templateInstance(), rel + '2']); + } } }); }); // primitives blocksDict = SpriteMorph.prototype.blocks; Object.keys(blocksDict).forEach(function (selector) { - if (!StageMorph.prototype.hiddenPrimitives[selector]) { + if (!StageMorph.prototype.hiddenPrimitives[selector] && + contains(types, blocksDict[selector].type)) { var block = blocksDict[selector], - spec = localize(block.spec).toLowerCase(), + spec = localize(block.alias || block.spec).toLowerCase(), rel = relevance(labelOf(spec), search); if ( (rel !== -1) && (!block.dev) && (!block.only || (block.only === myself.constructor)) ) { - blocks.push([primitive(selector), rel + '2']); + blocks.push([primitive(selector), rel + '3']); } } }); @@ -2426,18 +2481,43 @@ SpriteMorph.prototype.blocksMatching = function (searchString, strictly) { return blocks.map(function (each) {return each[0]; }); }; -SpriteMorph.prototype.searchBlocks = function () { +SpriteMorph.prototype.searchBlocks = function ( + searchString, + types, + varNames, + scriptFocus +) { var myself = this, unit = SyntaxElementMorph.prototype.fontSize, ide = this.parentThatIsA(IDE_Morph), oldSearch = '', - searchBar = new InputFieldMorph(''), - searchPane = ide.createPalette('forSearch'); + searchBar = new InputFieldMorph(searchString || ''), + searchPane = ide.createPalette('forSearch'), + blocksList = [], + selection, + focus; + + function showSelection() { + if (focus) {focus.destroy(); } + if (!selection || !scriptFocus) {return; } + focus = selection.outline( + MorphicPreferences.isFlat ? new Color(150, 200, 255) + : new Color(255, 255, 255), + 2 + ); + searchPane.contents.add(focus); + focus.scrollIntoView(); + } function show(blocks) { var oldFlag = Morph.prototype.trackChanges, x = searchPane.contents.left() + 5, y = (searchBar.bottom() + unit); + blocksList = blocks; + selection = null; + if (blocks.length && scriptFocus) { + selection = blocks[0]; + } Morph.prototype.trackChanges = false; searchPane.contents.children = [searchPane.contents.children[0]]; blocks.forEach(function (block) { @@ -2447,6 +2527,7 @@ SpriteMorph.prototype.searchBlocks = function () { y += unit * 0.3; }); Morph.prototype.trackChanges = oldFlag; + showSelection(); searchPane.changed(); } @@ -2463,17 +2544,52 @@ SpriteMorph.prototype.searchBlocks = function () { searchBar.drawNew(); searchPane.accept = function () { - var search = searchBar.getValue(); - if (search.length > 0) { - show(myself.blocksMatching(search)); + var search; + if (scriptFocus) { + searchBar.cancel(); + if (selection) { + scriptFocus.insertBlock(selection); + } + } else { + search = searchBar.getValue(); + if (search.length > 0) { + show(myself.blocksMatching(search)); + } } }; - searchPane.reactToKeystroke = function () { - var search = searchBar.getValue(); - if (search !== oldSearch) { - oldSearch = search; - show(myself.blocksMatching(search, search.length < 2)); + searchPane.reactToKeystroke = function (evt) { + var search, idx, code = evt ? evt.keyCode : 0; + switch (code) { + case 38: // up arrow + if (!scriptFocus || !selection) {return; } + idx = blocksList.indexOf(selection) - 1; + if (idx < 0) { + idx = blocksList.length - 1; + } + selection = blocksList[idx]; + showSelection(); + return; + case 40: // down arrow + if (!scriptFocus || !selection) {return; } + idx = blocksList.indexOf(selection) + 1; + if (idx >= blocksList.length) { + idx = 0; + } + selection = blocksList[idx]; + showSelection(); + return; + default: + search = searchBar.getValue(); + if (search !== oldSearch) { + oldSearch = search; + show(myself.blocksMatching( + search, + search.length < 2, + types, + varNames + )); + } } }; @@ -2484,6 +2600,7 @@ SpriteMorph.prototype.searchBlocks = function () { ide.fixLayout('refreshPalette'); searchBar.edit(); + if (searchString) {searchPane.reactToKeystroke(); } }; // SpriteMorph variable management @@ -2491,7 +2608,7 @@ SpriteMorph.prototype.searchBlocks = function () { SpriteMorph.prototype.addVariable = function (name, isGlobal) { var ide = this.parentThatIsA(IDE_Morph); if (isGlobal) { - this.variables.parentFrame.addVar(name); + this.globalVariables().addVar(name); if (ide) { ide.flushBlocksCache('variables'); } @@ -2503,7 +2620,10 @@ SpriteMorph.prototype.addVariable = function (name, isGlobal) { SpriteMorph.prototype.deleteVariable = function (varName) { var ide = this.parentThatIsA(IDE_Morph); - this.deleteVariableWatcher(varName); + if (!contains(this.inheritedVariableNames(true), varName)) { + // check only shadowed variables + this.deleteVariableWatcher(varName); + } this.variables.deleteVar(varName); if (ide) { ide.flushBlocksCache('variables'); // b/c the var could be global @@ -2658,7 +2778,7 @@ SpriteMorph.prototype.userMenu = function () { } menu.addItem("duplicate", 'duplicate'); menu.addItem("delete", 'remove'); - menu.addItem("move", 'move'); + menu.addItem("move", 'moveCenter'); if (!this.isClone) { menu.addItem("edit", 'edit'); } @@ -3176,12 +3296,20 @@ SpriteMorph.prototype.positionTalkBubble = function () { SpriteMorph.prototype.prepareToBeGrabbed = function (hand) { this.removeShadow(); this.recordLayers(); - if (!this.bounds.containsPoint(hand.position())) { + if (!this.bounds.containsPoint(hand.position()) && + this.isCorrectingOutsideDrag()) { this.setCenter(hand.position()); } this.addShadow(); }; +SpriteMorph.prototype.isCorrectingOutsideDrag = function () { + // make sure I don't "trail behind" the hand when dragged + // override for morphs that you want to be dragged outside + // their full bounds + return !this.parts.length; +}; + SpriteMorph.prototype.justDropped = function () { this.restoreLayers(); this.positionTalkBubble(); @@ -3240,6 +3368,22 @@ SpriteMorph.prototype.moveBy = function (delta, justMe) { } }; +SpriteMorph.prototype.silentMoveBy = function (delta, justMe) { + SpriteMorph.uber.silentMoveBy.call(this, delta); + if (!justMe && this.parent instanceof HandMorph) { + this.parts.forEach(function (part) { + part.moveBy(delta); + }); + } +}; + +SpriteMorph.prototype.rootForGrab = function () { + if (this.anchor) { + return this.anchor.rootForGrab(); + } + return SpriteMorph.uber.rootForGrab.call(this); +}; + SpriteMorph.prototype.slideBackTo = function (situation, inSteps) { // override the inherited default to make sure my parts follow var steps = inSteps || 5, @@ -3646,6 +3790,7 @@ SpriteMorph.prototype.reportThreadCount = function () { SpriteMorph.prototype.findVariableWatcher = function (varName) { var stage = this.parentThatIsA(StageMorph), + globals = this.globalVariables(), myself = this; if (stage === null) { return null; @@ -3655,7 +3800,7 @@ SpriteMorph.prototype.findVariableWatcher = function (varName) { function (morph) { return morph instanceof WatcherMorph && (morph.target === myself.variables - || morph.target === myself.variables.parentFrame) + || morph.target === globals) && morph.getter === varName; } ); @@ -3663,6 +3808,7 @@ SpriteMorph.prototype.findVariableWatcher = function (varName) { SpriteMorph.prototype.toggleVariableWatcher = function (varName, isGlobal) { var stage = this.parentThatIsA(StageMorph), + globals = this.globalVariables(), watcher, others; if (stage === null) { @@ -3682,12 +3828,12 @@ SpriteMorph.prototype.toggleVariableWatcher = function (varName, isGlobal) { // if no watcher exists, create a new one if (isNil(isGlobal)) { - isGlobal = contains(this.variables.parentFrame.names(), varName); + isGlobal = contains(globals.names(), varName); } watcher = new WatcherMorph( varName, this.blockColor.variables, - isGlobal ? this.variables.parentFrame : this.variables, + isGlobal ? globals : this.variables, varName ); watcher.setPosition(stage.position().add(10)); @@ -3954,6 +4100,176 @@ SpriteMorph.prototype.replaceDoubleDefinitionsFor = function (definition) { } }; +// SpriteMorph inheritance - general + +SpriteMorph.prototype.chooseExemplar = function () { + var stage = this.parentThatIsA(StageMorph), + myself = this, + other = stage.children.filter(function (m) { + return m instanceof SpriteMorph && + (!contains(m.allExemplars(), myself)); + }), + menu; + menu = new MenuMorph( + function (aSprite) {myself.setExemplar(aSprite); }, + localize('current parent') + + ':\n' + + (this.exemplar ? this.exemplar.name : localize('none')) + ); + other.forEach(function (eachSprite) { + menu.addItem(eachSprite.name, eachSprite); + }); + menu.addLine(); + menu.addItem(localize('none'), null); + menu.popUpAtHand(this.world()); +}; + +SpriteMorph.prototype.setExemplar = function (another) { + var ide = this.parentThatIsA(IDE_Morph); + this.exemplar = another; + if (isNil(another)) { + this.variables.parentFrame = (this.globalVariables()); + } else { + this.variables.parentFrame = (another.variables); + } + if (ide) { + ide.flushBlocksCache('variables'); + ide.refreshPalette(); + } +}; + +SpriteMorph.prototype.allExemplars = function () { + // including myself + var all = [], + current = this; + while (!isNil(current)) { + all.push(current); + current = current.exemplar; + } + return all; +}; + +SpriteMorph.prototype.specimens = function () { + // without myself + var myself = this; + return this.siblings().filter(function (m) { + return m instanceof SpriteMorph && (m.exemplar === myself); + }); +}; + +SpriteMorph.prototype.allSpecimens = function () { + // without myself + var myself = this; + return this.siblings().filter(function (m) { + return m instanceof SpriteMorph && contains(m.allExemplars(), myself); + }); +}; + +// SpriteMorph inheritance - variables + +SpriteMorph.prototype.isVariableNameInUse = function (vName, isGlobal) { + if (isGlobal) { + return contains(this.variables.allNames(), vName); + } + if (contains(this.variables.names(), vName)) {return true; } + return contains(this.globalVariables().names(), vName); +}; + +SpriteMorph.prototype.globalVariables = function () { + var current = this.variables.parentFrame; + while (current.owner) { + current = current.parentFrame; + } + return current; +}; + +SpriteMorph.prototype.shadowVar = function (name, value) { + var ide = this.parentThatIsA(IDE_Morph); + this.variables.addVar(name, value); + if (ide) { + ide.flushBlocksCache('variables'); + ide.refreshPalette(); + } +}; + +SpriteMorph.prototype.inheritedVariableNames = function (shadowedOnly) { + var names = [], + own = this.variables.names(), + current = this.variables.parentFrame; + + function test(each) { + return shadowedOnly ? contains(own, each) : !contains(own, each); + } + + while (current.owner instanceof SpriteMorph) { + names.push.apply( + names, + current.names().filter(test) + ); + current = current.parentFrame; + } + return names; +}; + +SpriteMorph.prototype.deletableVariableNames = function () { + var locals = this.variables.names(), + inherited = this.inheritedVariableNames(); + return locals.concat( + this.globalVariables().names().filter( + function (each) { + return !contains(locals, each) && !contains(inherited, each); + } + ) + ); +}; + +// SpriteMorph inheritance - custom blocks + +/* +// under construction, commented out for now + +SpriteMorph.prototype.ownBlocks = function () { + var dict = {}; + this.customBlocks.forEach(function (def) { + dict[def.blockSpec()] = def; + }); + return dict; +}; + +SpriteMorph.prototype.allBlocks = function (valuesOnly) { + var dict = {}; + this.allExemplars().reverse().forEach(function (sprite) { + sprite.customBlocks.forEach(function (def) { + dict[def.blockSpec()] = def; + }); + }); + if (valuesOnly) { + return Object.keys(dict).map(function (key) {return dict[key]; }); + } + return dict; +}; + +SpriteMorph.prototype.inheritedBlocks = function (valuesOnly) { + var dict = {}, + own = Object.keys(this.ownBlocks()), + others = this.allExemplars().reverse(); + others.pop(); + others.forEach(function (sprite) { + sprite.customBlocks.forEach(function (def) { + var spec = def.blockSpec(); + if (!contains(own, spec)) { + dict[spec] = def; + } + }); + }); + if (valuesOnly) { + return Object.keys(dict).map(function (key) {return dict[key]; }); + } + return dict; +}; + +*/ + // SpriteMorph thumbnail SpriteMorph.prototype.thumbnail = function (extentPoint) { @@ -4334,6 +4650,8 @@ StageMorph.prototype.codeMappings = {}; StageMorph.prototype.codeHeaders = {}; StageMorph.prototype.enableCodeMapping = false; +StageMorph.prototype.enableInheritance = false; + // StageMorph instance creation function StageMorph(globals) { @@ -4767,6 +5085,8 @@ StageMorph.prototype.processKeyEvent = function (event, action) { keyName = 'enter'; if (event.ctrlKey || event.metaKey) { keyName = 'ctrl enter'; + } else if (event.shiftKey) { + keyName = 'shift enter'; } break; case 27: @@ -4807,6 +5127,9 @@ StageMorph.prototype.fireKeyEvent = function (key) { if (evt === 'ctrl enter') { return this.fireGreenFlagEvent(); } + if (evt === 'shift enter') { + return this.editScripts(); + } if (evt === 'ctrl f') { if (!ide.isAppMode) {ide.currentSprite.searchBlocks(); } return; @@ -4907,6 +5230,25 @@ StageMorph.prototype.removeAllClones = function () { this.cloneCount = 0; }; +StageMorph.prototype.editScripts = function () { + var ide = this.parentThatIsA(IDE_Morph), + scripts, + sorted; + if (ide.isAppMode || !ScriptsMorph.prototype.enableKeyboard) {return; } + scripts = this.parentThatIsA(IDE_Morph).currentSprite.scripts; + scripts.edit(scripts.position()); + sorted = scripts.focus.sortedScripts(); + if (sorted.length) { + scripts.focus.element = sorted[0]; + if (scripts.focus.element instanceof HatBlockMorph) { + scripts.focus.nextCommand(); + } + } else { + scripts.focus.moveBy(new Point(50, 50)); + } + scripts.focus.fixLayout(); +}; + // StageMorph block templates StageMorph.prototype.blockTemplates = function (category) { @@ -4969,7 +5311,7 @@ StageMorph.prototype.blockTemplates = function (category) { function addVar(pair) { if (pair) { - if (myself.variables.silentFind(pair[0])) { + if (myself.isVariableNameInUse(pair[0])) { myself.inform('that name is already in use'); } else { myself.addVariable(pair[0], pair[1]); @@ -5271,9 +5613,7 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push(block('doShowVar')); blocks.push(block('doHideVar')); blocks.push(block('doDeclareVariables')); - blocks.push('='); - blocks.push(block('reportNewList')); blocks.push('-'); blocks.push(block('reportCONS')); @@ -5472,7 +5812,7 @@ StageMorph.prototype.show = function () { this.changed(); }; -// StageMorph cloning overrice +// StageMorph cloning override StageMorph.prototype.createClone = nop; @@ -5663,6 +6003,18 @@ StageMorph.prototype.doubleDefinitionsFor StageMorph.prototype.replaceDoubleDefinitionsFor = SpriteMorph.prototype.replaceDoubleDefinitionsFor; +// StageMorph inheritance support - variables + +StageMorph.prototype.isVariableNameInUse + = SpriteMorph.prototype.isVariableNameInUse; + +StageMorph.prototype.globalVariables + = SpriteMorph.prototype.globalVariables; + +StageMorph.prototype.inheritedVariableNames = function () { + return []; +}; + // SpriteBubbleMorph //////////////////////////////////////////////////////// /* @@ -6876,32 +7228,51 @@ WatcherMorph.prototype.isGlobal = function (selector) { // WatcherMorph slider accessing: -WatcherMorph.prototype.setSliderMin = function (num) { +WatcherMorph.prototype.setSliderMin = function (num, noUpdate) { if (this.target instanceof VariableFrame) { - this.sliderMorph.setSize(1); - this.sliderMorph.setStart(num); - this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5); + this.sliderMorph.setSize(1, noUpdate); + this.sliderMorph.setStart(num, noUpdate); + this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5, noUpdate); } }; -WatcherMorph.prototype.setSliderMax = function (num) { +WatcherMorph.prototype.setSliderMax = function (num, noUpdate) { if (this.target instanceof VariableFrame) { - this.sliderMorph.setSize(1); - this.sliderMorph.setStop(num); - this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5); + this.sliderMorph.setSize(1, noUpdate); + this.sliderMorph.setStop(num, noUpdate); + this.sliderMorph.setSize(this.sliderMorph.rangeSize() / 5, noUpdate); } }; // WatcherMorph updating: WatcherMorph.prototype.update = function () { - var newValue, - num; + var newValue, sprite, num; + if (this.target && this.getter) { this.updateLabel(); if (this.target instanceof VariableFrame) { newValue = this.target.vars[this.getter] ? this.target.vars[this.getter].value : undefined; + if (newValue === undefined && this.target.owner) { + sprite = this.target.owner; + if (contains(sprite.inheritedVariableNames(), this.getter)) { + newValue = this.target.getVar(this.getter); + // ghost cell color + this.cellMorph.setColor( + SpriteMorph.prototype.blockColor.variables + .lighter(35) + ); + } else { + this.destroy(); + return; + } + } else { + // un-ghost the cell color + this.cellMorph.setColor( + SpriteMorph.prototype.blockColor.variables + ); + } } else { newValue = this.target[this.getter](); } @@ -6986,7 +7357,11 @@ WatcherMorph.prototype.fixLayout = function () { this.sliderMorph.button.pressColor.b += 100; this.sliderMorph.setHeight(fontSize); this.sliderMorph.action = function (num) { - myself.target.vars[myself.getter].value = Math.round(num); + myself.target.setVar( + myself.getter, + Math.round(num), + myself.target.owner + ); }; this.add(this.sliderMorph); } @@ -7083,7 +7458,46 @@ WatcherMorph.prototype.userMenu = function () { var myself = this, menu = new MenuMorph(this), on = '\u25CF', - off = '\u25CB'; + off = '\u25CB', + vNames; + + function monitor(vName) { + var stage = myself.parentThatIsA(StageMorph), + varFrame = myself.currentValue.outerContext.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(); + } + ); + } + menu.addItem( (this.style === 'normal' ? on : off) + ' ' + localize('normal'), 'styleNormal' @@ -7182,6 +7596,14 @@ WatcherMorph.prototype.userMenu = function () { ); } ); + } else if (this.currentValue instanceof Context) { + vNames = this.currentValue.outerContext.variables.names(); + if (vNames.length) { + menu.addLine(); + vNames.forEach(function (vName) { + monitor(vName); + }); + } } } return menu; diff --git a/store.js b/store.js index e73ba9f5..6112c224 100644 --- a/store.js +++ b/store.js @@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/ // Global stuff //////////////////////////////////////////////////////// -modules.store = '2015-June-25'; +modules.store = '2015-July-27'; // XML_Serializer /////////////////////////////////////////////////////// @@ -413,6 +413,8 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { model.stage.attributes.threadsafe === 'true'; StageMorph.prototype.enableCodeMapping = model.stage.attributes.codify === 'true'; + StageMorph.prototype.enableInheritance = + model.stage.attributes.inheritance === 'true'; model.hiddenPrimitives = model.project.childNamed('hidden'); if (model.hiddenPrimitives) { @@ -459,9 +461,17 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { myself.loadValue(model); }); - // restore nesting associations + // restore inheritance and nesting associations myself.project.stage.children.forEach(function (sprite) { - var anchor; + var exemplar, anchor; + if (sprite.inheritanceInfo) { // only sprites can inherit + exemplar = myself.project.sprites[ + sprite.inheritanceInfo.exemplar + ]; + if (exemplar) { + sprite.setExemplar(exemplar); + } + } if (sprite.nestingInfo) { // only sprites may have nesting info anchor = myself.project.sprites[sprite.nestingInfo.anchor]; if (anchor) { @@ -471,6 +481,7 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { } }); myself.project.stage.children.forEach(function (sprite) { + delete sprite.inheritanceInfo; if (sprite.nestingInfo) { // only sprites may have nesting info sprite.nestingScale = +(sprite.nestingInfo.scale || sprite.scale); delete sprite.nestingInfo; @@ -491,7 +502,7 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { /* Watchers */ model.sprites.childrenNamed('watcher').forEach(function (model) { - var watcher, color, target, hidden, extX, extY, vFrame; + var watcher, color, target, hidden, extX, extY; color = myself.loadColor(model.attributes.color); target = Object.prototype.hasOwnProperty.call( @@ -512,20 +523,14 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { model.attributes, 'var' )) { - vFrame = isNil(target) ? project.globalVariables - : target.variables; - if (Object.prototype.hasOwnProperty.call( - vFrame.vars, - model.attributes['var'] - )) { - watcher = new WatcherMorph( - model.attributes['var'], - color, - vFrame, - model.attributes['var'], - hidden - ); - } + watcher = new WatcherMorph( + model.attributes['var'], + color, + isNil(target) ? project.globalVariables + : target.variables, + model.attributes['var'], + hidden + ); } else { watcher = new WatcherMorph( localize(myself.watcherLabels[model.attributes.s]), @@ -535,35 +540,33 @@ SnapSerializer.prototype.rawLoadProjectModel = function (xmlNode) { hidden ); } - if (watcher) { - watcher.setStyle(model.attributes.style || 'normal'); - if (watcher.style === 'slider') { - watcher.setSliderMin(model.attributes.min || '1'); - watcher.setSliderMax(model.attributes.max || '100'); - } - watcher.setPosition( - project.stage.topLeft().add(new Point( - +model.attributes.x || 0, - +model.attributes.y || 0 - )) - ); - project.stage.add(watcher); - watcher.onNextStep = function () {this.currentValue = null; }; + watcher.setStyle(model.attributes.style || 'normal'); + if (watcher.style === 'slider') { + watcher.setSliderMin(model.attributes.min || '1', true); + watcher.setSliderMax(model.attributes.max || '100', true); + } + watcher.setPosition( + project.stage.topLeft().add(new Point( + +model.attributes.x || 0, + +model.attributes.y || 0 + )) + ); + project.stage.add(watcher); + watcher.onNextStep = function () {this.currentValue = null; }; - // set watcher's contentsMorph's extent if it is showing a list - // and if its monitor dimensions are given - if (watcher.currentValue instanceof List) { - extX = model.attributes.extX; - if (extX) { - watcher.cellMorph.contentsMorph.setWidth(+extX); - } - extY = model.attributes.extY; - if (extY) { - watcher.cellMorph.contentsMorph.setHeight(+extY); - } - // adjust my contentsMorph's handle position - watcher.cellMorph.contentsMorph.handle.drawNew(); + // set watcher's contentsMorph's extent if it is showing a list and + // its monitor dimensions are given + if (watcher.currentValue instanceof List) { + extX = model.attributes.extX; + if (extX) { + watcher.cellMorph.contentsMorph.setWidth(+extX); } + extY = model.attributes.extY; + if (extY) { + watcher.cellMorph.contentsMorph.setHeight(+extY); + } + // adjust my contentsMorph's handle position + watcher.cellMorph.contentsMorph.handle.drawNew(); } }); this.objects = {}; @@ -647,9 +650,17 @@ SnapSerializer.prototype.loadSprites = function (xmlString, ide) { myself.loadObject(sprite, model); }); - // restore nesting associations + // restore inheritance and nesting associations project.stage.children.forEach(function (sprite) { - var anchor; + var exemplar, anchor; + if (sprite.inheritanceInfo) { // only sprites can inherit + exemplar = project.sprites[ + sprite.inheritanceInfo.exemplar + ]; + if (exemplar) { + sprite.setExemplar(exemplar); + } + } if (sprite.nestingInfo) { // only sprites may have nesting info anchor = project.sprites[sprite.nestingInfo.anchor]; if (anchor) { @@ -659,6 +670,7 @@ SnapSerializer.prototype.loadSprites = function (xmlString, ide) { } }); project.stage.children.forEach(function (sprite) { + delete sprite.inheritanceInfo; if (sprite.nestingInfo) { // only sprites may have nesting info sprite.nestingScale = +(sprite.nestingInfo.scale || sprite.scale); delete sprite.nestingInfo; @@ -707,6 +719,14 @@ SnapSerializer.prototype.loadObject = function (object, model) { this.loadScripts(object.scripts, model.require('scripts')); }; +SnapSerializer.prototype.loadInheritanceInfo = function (object, model) { + // private + var info = model.childNamed('inherit'); + if (info) { + object.inheritanceInfo = info.attributes; + } +}; + SnapSerializer.prototype.loadNestingInfo = function (object, model) { // private var info = model.childNamed('nest'); @@ -1416,6 +1436,7 @@ StageMorph.prototype.toXML = function (serializer) { 'costume="@" tempo="@" threadsafe="@" ' + 'lines="@" ' + 'codify="@" ' + + 'inheritance="@" ' + 'scheduled="@" ~>' + '$' + '%' + @@ -1443,6 +1464,7 @@ StageMorph.prototype.toXML = function (serializer) { this.isThreadSafe, SpriteMorph.prototype.useFlatLineEnds ? 'flat' : 'round', this.enableCodeMapping, + this.enableInheritance, StageMorph.prototype.frameRate !== 0, this.trailsCanvas.toDataURL('image/png'), serializer.store(this.costumes, this.name + '_cst'), @@ -1475,6 +1497,7 @@ SpriteMorph.prototype.toXML = function (serializer) { ' draggable="@"' + '%' + ' costume="@" color="@,@,@" pen="@" ~>' + + '%' + // inheritance info '%' + // nesting info '%' + '%' + @@ -1497,6 +1520,13 @@ SpriteMorph.prototype.toXML = function (serializer) { this.color.b, this.penPoint, + // inheritance info + this.exemplar + ? '' + : '', + // nesting info this.anchor ? '
1datamapmany1data lists
1
1
110i
1
cont
catchtag
cont
catchtag
\ No newline at end of file +
1datamapmany1data lists
1
1
110i
1
cont
catchtag
cont
catchtag
Sprite
Sprite
\ No newline at end of file diff --git a/widgets.js b/widgets.js index a8644af1..2bfb4ceb 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-June-25'; +modules.widgets = '2015-July-27'; var PushButtonMorph; var ToggleButtonMorph; @@ -560,12 +560,13 @@ ToggleButtonMorph.prototype.init = function ( // ToggleButtonMorph events ToggleButtonMorph.prototype.mouseEnter = function () { + var contents = this.hint instanceof Function ? this.hint() : this.hint; if (!this.state) { this.image = this.highlightImage; this.changed(); } - if (this.hint) { - this.bubbleHelp(this.hint); + if (contents) { + this.bubbleHelp(contents); } };