diff options
| -rw-r--r-- | blocks.js | 112 | ||||
| -rw-r--r-- | byob.js | 2 | ||||
| -rw-r--r-- | gui.js | 54 | ||||
| -rwxr-xr-x | history.txt | 11 | ||||
| -rw-r--r-- | index.html | 7 | ||||
| -rw-r--r-- | lang-fr.js | 154 | ||||
| -rw-r--r-- | lists.js | 5 | ||||
| -rw-r--r-- | locale.js | 4 | ||||
| -rw-r--r-- | morphic.js | 372 | ||||
| -rwxr-xr-x | morphic.txt | 37 | ||||
| -rw-r--r-- | objects.js | 19 | ||||
| -rw-r--r-- | paint.js | 4 | ||||
| -rw-r--r-- | store.js | 2 | ||||
| -rw-r--r-- | threads.js | 43 | ||||
| -rw-r--r-- | widgets.js | 4 | ||||
| -rwxr-xr-x[-rw-r--r--] | xml.js | 129 |
16 files changed, 487 insertions, 472 deletions
@@ -155,8 +155,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/ // Global stuff //////////////////////////////////////////////////////// -modules.blocks = '2015-June-08'; - +modules.blocks = '2015-June-25'; var SyntaxElementMorph; var BlockMorph; @@ -360,6 +359,10 @@ SyntaxElementMorph.prototype.init = function () { this.cachedInputs = null; }; +// SyntaxElementMorph stepping: + +SyntaxElementMorph.prototype.step = null; + // SyntaxElementMorph accessing: SyntaxElementMorph.prototype.parts = function () { @@ -1111,9 +1114,9 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { acos : ['acos'], atan : ['atan'], ln : ['ln'], - // log : 'log', - 'e^' : ['e^'] - // '10^' : '10^' + log : ['log'], + 'e^' : ['e^'], + '10^' : ['10^'] }, true ); @@ -1417,16 +1420,19 @@ SyntaxElementMorph.prototype.labelPart = function (spec) { new Point() : this.embossing; part.drawNew(); } else { - part = new StringMorph(spec); - part.fontName = this.labelFontName; - part.fontStyle = this.labelFontStyle; - part.fontSize = this.fontSize; - part.color = new Color(255, 255, 255); - part.isBold = true; - part.shadowColor = this.color.darker(this.labelContrast); - part.shadowOffset = MorphicPreferences.isFlat ? - new Point() : this.embossing; - part.drawNew(); + part = new StringMorph( + spec, // text + this.fontSize, // fontSize + this.labelFontStyle, // fontStyle + true, // bold + false, // italic + false, // isNumeric + MorphicPreferences.isFlat ? + new Point() : this.embossing, // shadowOffset + this.color.darker(this.labelContrast), // shadowColor + new Color(255, 255, 255), // color + this.labelFontName // fontName + ); } return part; }; @@ -2058,7 +2064,8 @@ BlockMorph.prototype.setSpec = function (spec) { } part = myself.labelPart(word); myself.add(part); - if (!(part instanceof CommandSlotMorph)) { + if (!(part instanceof CommandSlotMorph || + part instanceof StringMorph)) { part.drawNew(); } if (part instanceof RingMorph) { @@ -2225,7 +2232,7 @@ BlockMorph.prototype.userMenu = function () { ); if (this instanceof CommandBlockMorph && this.nextBlock()) { menu.addItem( - this.thumbnail(0.5, 60, false), + this.thumbnail(0.5, 60), function () { var cpy = this.fullCopy(), nb = cpy.nextBlock(), @@ -3103,38 +3110,34 @@ BlockMorph.prototype.mouseClickLeft = function () { // BlockMorph thumbnail -BlockMorph.prototype.thumbnail = function (scale, clipWidth, noShadow) { - var block = this.fullCopy(), - nb = block.nextBlock(), +BlockMorph.prototype.thumbnail = function (scale, clipWidth) { + var nb = this.nextBlock(), fadeout = 12, ext, trgt, ctx, gradient; - if (nb) {nb.destroy(); } - if (!noShadow) {block.addShadow(); } - ext = block.fullBounds().extent(); - if (!noShadow) { - ext = ext.subtract(this.shadowBlur * - (useBlurredShadows && !MorphicPreferences.isFlat ? 1 : 2)); - } + + if (nb) {nb.isVisible = false; } + ext = this.fullBounds().extent(); trgt = newCanvas(new Point( - Math.min(ext.x * scale, clipWidth || ext.x), + clipWidth ? Math.min(ext.x * scale, clipWidth) : ext.x * scale, ext.y * scale )); ctx = trgt.getContext('2d'); ctx.scale(scale, scale); - ctx.drawImage(block.fullImage(), 0, 0); + ctx.drawImage(this.fullImage(), 0, 0); // draw fade-out - if (trgt.width === clipWidth) { + if (clipWidth && ext.x * scale > clipWidth) { gradient = ctx.createLinearGradient( trgt.width / scale - fadeout, 0, trgt.width / scale, 0 ); - gradient.addColorStop(0, new Color(255, 255, 255, 0).toString()); - gradient.addColorStop(1, 'white'); + gradient.addColorStop(0, 'transparent'); + gradient.addColorStop(1, 'black'); + ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = gradient; ctx.fillRect( trgt.width / scale - fadeout, @@ -3143,6 +3146,7 @@ BlockMorph.prototype.thumbnail = function (scale, clipWidth, noShadow) { trgt.height / scale ); } + if (nb) {nb.isVisible = true; } return trgt; }; @@ -6917,7 +6921,8 @@ InputSlotMorph.prototype.setChoices = function (dict, readonly) { // InputSlotMorph layout: InputSlotMorph.prototype.fixLayout = function () { - var contents = this.contents(), + var width, height, arrowWidth, + contents = this.contents(), arrow = this.arrow(); contents.isNumeric = this.isNumeric; @@ -6934,35 +6939,32 @@ InputSlotMorph.prototype.fixLayout = function () { arrow.setSize(this.fontSize); arrow.show(); } else { - arrow.setSize(0); arrow.hide(); } - this.setHeight( - contents.height() - + this.edge * 2 - // + this.typeInPadding * 2 - ); + arrowWidth = arrow.isVisible ? arrow.width() : 0; + + height = contents.height() + this.edge * 2; // + this.typeInPadding * 2 if (this.isNumeric) { - this.setWidth(contents.width() - + Math.floor(arrow.width() * 0.5) - + this.height() - + this.typeInPadding * 2 - ); + width = contents.width() + + Math.floor(arrowWidth * 0.5) + + height + + this.typeInPadding * 2; } else { - this.setWidth(Math.max( + width = Math.max( contents.width() - + arrow.width() + + arrowWidth + this.edge * 2 + this.typeInPadding * 2, contents.rawHeight ? // single vs. multi-line contents - contents.rawHeight() + arrow.width() - : contents.height() / 1.2 + arrow.width(), + contents.rawHeight() + arrowWidth + : contents.height() / 1.2 + arrowWidth, this.minWidth // for text-type slots - )); + ); } + this.setExtent(new Point(width, height)); if (this.isNumeric) { contents.setPosition(new Point( - Math.floor(this.height() / 2), + Math.floor(height / 2), this.edge ).add(new Point(this.typeInPadding, 0)).add(this.position())); } else { @@ -6972,10 +6974,12 @@ InputSlotMorph.prototype.fixLayout = function () { ).add(new Point(this.typeInPadding, 0)).add(this.position())); } - arrow.setPosition(new Point( - this.right() - arrow.width() - this.edge, - contents.top() - )); + if (arrow.isVisible) { + arrow.setPosition(new Point( + this.right() - arrowWidth - this.edge, + contents.top() + )); + } if (this.parent) { if (this.parent.fixLayout) { @@ -106,7 +106,7 @@ SymbolMorph, isNil*/ // Global stuff //////////////////////////////////////////////////////// -modules.byob = '2015-May-23'; +modules.byob = '2015-June-25'; // Declarations @@ -69,7 +69,7 @@ SpeechBubbleMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.gui = '2015-May-18'; +modules.gui = '2015-June-25'; // Declarations @@ -82,6 +82,11 @@ var WardrobeMorph; var SoundIconMorph; var JukeboxMorph; +// Get the full url without "snap.html" +var baseUrl = document.URL.split('/'); +baseUrl.pop(baseUrl.length - 1); +baseUrl = baseUrl.join('/') + '/'; + // IDE_Morph /////////////////////////////////////////////////////////// // I am SNAP's top-level frame, the Editor window @@ -304,7 +309,8 @@ IDE_Morph.prototype.openIn = function (world) { } throw new Error('unable to retrieve ' + url); } catch (err) { - return; + myself.showMessage('unable to retrieve project'); + return ''; } } @@ -2491,13 +2497,10 @@ IDE_Morph.prototype.projectMenu = function () { function () { // read a list of libraries from an external file, var libMenu = new MenuMorph(this, 'Import library'), - libUrl = 'http://snap.berkeley.edu/snapsource/libraries/' + - 'LIBRARIES'; + libUrl = baseUrl + 'libraries/' + 'LIBRARIES'; function loadLib(name) { - var url = 'http://snap.berkeley.edu/snapsource/libraries/' - + name - + '.xml'; + var url = baseUrl + 'libraries/' + name + '.xml'; myself.droppedText(myself.getURL(url), name); } @@ -2614,7 +2617,7 @@ IDE_Morph.prototype.aboutSnap = function () { module, btn1, btn2, btn3, btn4, licenseBtn, translatorsBtn, world = this.world(); - aboutTxt = 'Snap! 4.0\nBuild Your Own Blocks\n\n' + aboutTxt = 'Snap! 4.0.1\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' @@ -2648,7 +2651,7 @@ IDE_Morph.prototype.aboutSnap = function () { creditsTxt = localize('Contributors') + '\n\nNathan Dinsmore: Saving/Loading, Snap-Logo Design, ' - + 'countless bugfixes' + + '\ncountless bugfixes and optimizations' + '\nKartik Chandra: Paint Editor' + '\nMichael Ball: Time/Date UI, many bugfixes' + '\n"Ava" Yuan Yuan: Graphic Effects' @@ -4216,8 +4219,7 @@ IDE_Morph.prototype.getURLsbeOrRelative = function (url) { var request = new XMLHttpRequest(), myself = this; try { - request.open('GET', 'http://snap.berkeley.edu/snapsource/' + - url, false); + request.open('GET', baseUrl + url, false); request.send(); if (request.status === 200) { return request.responseText; @@ -4658,8 +4660,7 @@ ProjectDialogMorph.prototype.setSource = function (source) { myself.nameField.setContents(item.name || ''); } src = myself.ide.getURL( - 'http://snap.berkeley.edu/snapsource/Examples/' + - item.name + '.xml' + baseUrl + 'Examples/' + item.name + '.xml' ); xml = myself.ide.serializer.parse(src); @@ -4713,8 +4714,9 @@ ProjectDialogMorph.prototype.getLocalProjectList = function () { ProjectDialogMorph.prototype.getExamplesProjectList = function () { var dir, projects = []; + //alert(baseUrl); - dir = this.ide.getURL('http://snap.berkeley.edu/snapsource/Examples/'); + dir = this.ide.getURL(baseUrl + 'Examples/'); dir.split('\n').forEach( function (line) { var startIdx = line.search(new RegExp('href=".*xml"')), @@ -4733,8 +4735,8 @@ ProjectDialogMorph.prototype.getExamplesProjectList = function () { } } ); - projects.sort(function (x, y) { - return x.name < y.name ? -1 : 1; + projects = projects.sort(function (x, y) { + return x.name.toLowerCase() < y.name.toLowerCase() ? -1 : 1; }); return projects; }; @@ -4833,10 +4835,7 @@ ProjectDialogMorph.prototype.openProject = function () { if (this.source === 'cloud') { this.openCloudProject(proj); } else if (this.source === 'examples') { - src = this.ide.getURL( - 'http://snap.berkeley.edu/snapsource/Examples/' + - proj.name + '.xml' - ); + src = this.ide.getURL(baseUrl + 'Examples/' + proj.name + '.xml'); this.ide.openProjectString(src); this.destroy(); } else { // 'local' @@ -5006,6 +5005,7 @@ ProjectDialogMorph.prototype.deleteProject = function () { ProjectDialogMorph.prototype.shareProject = function () { var myself = this, + ide = this.ide, proj = this.listField.selected, entry = this.listField.active; @@ -5036,6 +5036,15 @@ ProjectDialogMorph.prototype.shareProject = function () { myself.ide.cloudError(), [proj.ProjectName] ); + // Set the Shared URL if the project is currently open + if (proj.ProjectName === ide.projectName) { + var usr = SnapCloud.username, + projectId = 'Username=' + + encodeURIComponent(usr.toLowerCase()) + + '&ProjectName=' + + encodeURIComponent(proj.projectName); + location.hash = projectId; + } }, myself.ide.cloudError() ); @@ -5046,6 +5055,7 @@ ProjectDialogMorph.prototype.shareProject = function () { ProjectDialogMorph.prototype.unshareProject = function () { var myself = this, + ide = this.ide, proj = this.listField.selected, entry = this.listField.active; @@ -5077,6 +5087,10 @@ ProjectDialogMorph.prototype.unshareProject = function () { myself.ide.cloudError(), [proj.ProjectName] ); + // Remove the shared URL if the project is open. + if (proj.ProjectName === ide.projectName) { + location.hash = ''; + } }, myself.ide.cloudError() ); diff --git a/history.txt b/history.txt index 1262a28..74d6d1f 100755 --- a/history.txt +++ b/history.txt @@ -2516,3 +2516,14 @@ ______ 150608 ------ * Blocks: Fixed #820 + +150625 +------ +* 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 === + +150626 +------ +* Morphic: Fix Inspector duplication, update documentation diff --git a/index.html b/index.html new file mode 100644 index 0000000..4c1a0f4 --- /dev/null +++ b/index.html @@ -0,0 +1,7 @@ +<!DOCTYPE html> +<html> + <head> + <meta HTTP-EQUIV="REFRESH" content="0.1; url=snap.html"> + </head> +</html> + @@ -185,7 +185,7 @@ SnapTranslator.dict.fr = { 'translator_e-mail': 'i.scool@mac.com', // optional 'last_changed': - '2014-02-04', // this, too, will appear in the Translators tab + '2015-06-25', // this, too, will appear in the Translators tab // GUI // control bar: @@ -392,7 +392,7 @@ SnapTranslator.dict.fr = { 'clear': 'effacer tout', 'pen down': - 'stylo en position d\u0027\u00EAcriture', + 'stylo en position d\u0027\u00E9criture', 'pen up': 'relever le stylo', 'set pen color to %clr': @@ -419,6 +419,8 @@ SnapTranslator.dict.fr = { 'Quand %keyHat est press\u00E9', 'when I am clicked': 'Quand je suis press\u00E9 ', + 'when I am %interaction': + 'Quand je suis %interaction', 'when I receive %msgHat': 'Quand je re\u00E7ois %msgHat', 'broadcast %msg': @@ -447,6 +449,10 @@ SnapTranslator.dict.fr = { 'arr\u00EAter le bloc', 'stop script': 'arr\u00EAter le script', + 'stop %stopOthersChoices': + 'arr\u00EAter %stopOthersChoices', + 'stop %stopChoices': + 'arr\u00EAter %stopChoices', 'stop all %stop': 'arr\u00EAter tout %stop', 'run %cmdRing %inputs': @@ -469,8 +475,16 @@ SnapTranslator.dict.fr = { 'moi-m\u00EAme', 'delete this clone': 'supprime ce clone', - 'pause all': - 'mettre en pause', + 'pause all %pause': + 'mettre en pause %pause', + 'all but this script': + 'tout sauf ce lutin', + 'other scripts in sprite': + 'les autres scripts de ce lutin', + 'this script': + 'ce script', + 'this block': + 'ce bloc', // sensing: 'touching %col ?': @@ -958,6 +972,8 @@ SnapTranslator.dict.fr = { 'tabulations', 'cr': 'retours de ligne', + 'letter': + 'lettres', // About Snap 'About Snap': @@ -1162,5 +1178,133 @@ SnapTranslator.dict.fr = { 'last': 'dernier', 'any': - 'n\u0027importe quel' + 'n\u0027importe quel', + + // miscellaneous + 'find blocks...': + 'chercher des blocs...', + 'hide primitives': + 'cacher les primitives', + 'show primitives': + 'montrer les primitives', + 'Login...': + 'Connexion...', + 'Signup...': + 'S\u0027enregistrer...', + 'Reset Password...': + 'Remise \u00E0 z\u00E9ro du mot de passe', + 'show all': + 'tout montrer', + 'pic...': + 'image...', + 'open a new window\nwith a picture of the stage': + 'ouvre une nouvelle fen\u00EAtre\navec une image de la sc\u00E8ne', + 'scripts pic...': + 'image des scripts...', + 'open a new window\nwith a picture of all scripts': + 'ouvre une nouvelle fen\u00EAtre\navec une image de tous les scripts', + 'Stage size...': + 'Taille de la sc\u00E8ne...', + 'Zoom blocks...': + 'Agrandir les blocs...', + + 'Plain prototype labels': + '\u00C9tiquettes simples de d\u00E9finition', + 'uncheck to always show (+) symbols\nin block prototype labels': + 'd\u00E9cocher pour montrer en permance le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', + 'check to hide (+) symbols\nin block prototype labels': + 'cocher pour cacher le symbole (+)\ndans les \u00e9tiquettes de d\u00E9finition de bloc', + + 'check for flat ends of lines': + 'cocher pour dessiner des fins de ligne plates', + 'uncheck for round ends of lines': + 'd\u00E9cocher pour dessiner des fins de lignes arrondies', + 'Flat line ends': + 'Fins de ligne plates', + + 'Codification support': + 'Support de la \u00AB codification \u00BB', + 'uncheck to disable\nblock to text mapping features': + 'd\u00E9cocher pour d\u00E9activer\nla fonction de transformation :\nbloc vers texte', + 'check for block\nto text mapping features': + 'cocher pour activer\nla fonction de transformation :\nbloc vers texte', + + 'current %dates': + 'date courante %dates', + 'year':'ann\u00E9e', + 'month':'mois', + 'date':'jour', + 'hour':'heure', + 'minute':'minute', + 'second':'seconde', + 'time in milliseconds': + 'heure en millisecondes', + 'day of week': + 'jour de la semaine', + + 'brightness': + 'luminosit\u00E9', + 'transparence': + 'transparence', + 'negative': + 'n\u00E9gatif', + 'comic': + 'bande dessin\u00E9e', + + 'clicked': + 'cliqu\u00E9', + 'pressed': + 'press\u00E9', + 'dropped': + 'd\u00E9pos\u00E9', + 'mouse-entered': + 'survol\u00E9', + 'mouse-departed': + 'quitt\u00E9', + + 'JavaScript function ( %mult%s ) { %code }': + 'fonction JavaScript ( %mult%s ) { %code }', + + + // Copy / Paste + 'Press CTRL+C one more time to effectively copy to clipboard.': + 'Taper une nouvelle fois sur CTRL+C pour copier effectivement vers le presse-papier.', + 'Press CTRL+V one more time to effectively paste from clipboard.': + 'Taper une nouvelle fois sur CTRL+V pour coller effectivement depuis le presse-papier.', + 'Press CTRL+X one more time to effectively cut to clipboard.': + 'Taper une nouvelle fois sur CTRL+X pour couper effectivement vers le presse-papier.', + + // Paint.js + 'undo':'d\u00E9faire', + 'Paintbrush tool\n(free draw)': + 'Pinceau\n(dessin \u00E0 main lev\u00E9)', + 'Stroked Rectangle\n(shift: square)': + 'Rectangle\n(Maj: carr\u00E9)', + 'Stroked Ellipse\n(shift: circle)': + 'Ellipse\n(Maj: cercle)', + 'Eraser tool': + 'Gomme', + 'Set the rotation center': + 'Fixe le centre de rotation', + 'Line tool\n(shift: vertical/horizontal)': + 'Ligne\n(Maj: verticale/horizontale)', + 'Filled Rectangle\n(shift: square)': + 'Rectangle plein\n(Maj: carr\u00E9)', + 'Filled Ellipse\n(shift: circle)': + 'Ellipse pleine\n(Maj: cercle)', + 'Fill a region': + 'Remplir une r\u00E9gion', + 'Pipette tool\n(pick a color anywhere)': + 'Pipette\n(s\u00E9lectionnez une couleur n\u0027importe o\u00F9', + 'grow':'agrandir', + 'shrink':'r\u00E9duire', + 'flip \u2194': + 'miroir \u2194', + 'flip \u2195': + 'miroir \u2195', + 'Brush size': + 'Taille de pinceau', + 'Constrain proportions of shapes?\n(you can also hold shift)': + 'Contrainte sur les proportions de la forme ?\n(vous pouvez aussi maintenir appuy\u00E9 Maj)' + }; @@ -7,7 +7,7 @@ written by Jens Mönig and Brian Harvey jens@moenig.org, bh@cs.berkeley.edu - Copyright (C) 2014 by Jens Mönig and Brian Harvey + Copyright (C) 2015 by Jens Mönig and Brian Harvey This file is part of Snap!. @@ -61,7 +61,7 @@ PushButtonMorph, SyntaxElementMorph, Color, Point, WatcherMorph, StringMorph, SpriteMorph, ScrollFrameMorph, CellMorph, ArrowMorph, MenuMorph, snapEquals, Morph, isNil, localize, MorphicPreferences*/ -modules.lists = '2014-November-20'; +modules.lists = '2015-June-25'; var List; var ListWatcherMorph; @@ -634,6 +634,7 @@ ListWatcherMorph.prototype.setStartIndex = function (index) { }; ListWatcherMorph.prototype.fixLayout = function () { + if (!this.label) {return; } Morph.prototype.trackChanges = false; if (this.frame) { this.arrangeCells(); @@ -42,7 +42,7 @@ /*global modules, contains*/ -modules.locale = '2015-May-18'; +modules.locale = '2015-June-25'; // Global stuff @@ -257,7 +257,7 @@ SnapTranslator.dict.fr = { 'translator_e-mail': 'i.scool@mac.com', 'last_changed': - '2014-02-04' + '2015-06-25' }; SnapTranslator.dict.si = { @@ -528,7 +528,7 @@ whose "isTemplate" flag is false, in other words: a non-template. When creating a copy from a template, the copy's - + reactToTemplateCopy is invoked, if it is present. @@ -828,13 +828,13 @@ // use context to paint stuff here }; - If your new morph stores or references other morphs outside of the - submorph tree in other properties, be sure to also override the + If your new morph stores or references to other morphs outside of + the submorph tree in other properties, be sure to also override the default - copyRecordingReferences() + updateReferences() - method accordingly if you want it to support duplication. + method if you want it to support duplication. (6) development and user modes @@ -1036,7 +1036,7 @@ ---------------------- Joe Otto found and fixed many early bugs and taught me some tricks. Nathan Dinsmore contributed mouse wheel scrolling, cached - background texture handling and countless bug fixes. + background texture handling, countless bug fixes and optimizations. Ian Reynolds contributed backspace key handling for Chrome. Davide Della Casa contributed performance optimizations for Firefox. @@ -1048,7 +1048,7 @@ /*global window, HTMLCanvasElement, getMinimumFontHeight, FileReader, Audio, FileList, getBlurredShadowSupport*/ -var morphicVersion = '2015-May-01'; +var morphicVersion = '2015-June-26'; var modules = {}; // keep track of additional loaded modules var useBlurredShadows = getBlurredShadowSupport(); // check for Chrome-bug @@ -1243,19 +1243,9 @@ function getDocumentPositionOf(aDOMelement) { return pos; } -function clone(target) { - // answer a new instance of target's type - if (typeof target === 'object') { - var Clone = function () {nop(); }; - Clone.prototype = target; - return new Clone(); - } - return target; -} - function copy(target) { // answer a shallow copy of target - var value, c, property; + var value, c, property, keys, l, i; if (typeof target !== 'object') { return target; @@ -1266,18 +1256,16 @@ function copy(target) { } if (target instanceof target.constructor && target.constructor !== Object) { - c = clone(target.constructor.prototype); - for (property in target) { - if (Object.prototype.hasOwnProperty.call(target, property)) { - c[property] = target[property]; - } + c = Object.create(target.constructor.prototype); + keys = Object.keys(target); + for (l = keys.length, i = 0; i < l; i += 1) { + property = keys[i]; + c[property] = target[property]; } } else { c = {}; for (property in target) { - if (!c[property]) { - c[property] = target[property]; - } + c[property] = target[property]; } } return c; @@ -2212,7 +2200,7 @@ function Morph() { // Morph initialization: -Morph.prototype.init = function () { +Morph.prototype.init = function (noDraw) { Morph.uber.init.call(this); this.isMorph = true; this.bounds = new Rectangle(0, 0, 50, 40); @@ -2225,7 +2213,7 @@ Morph.prototype.init = function () { this.isTemplate = false; this.acceptsDrops = false; this.noticesTransparentClick = false; - this.drawNew(); + if (!noDraw) {this.drawNew(); } this.fps = 0; this.customContextMenu = null; this.lastTime = Date.now(); @@ -2411,19 +2399,19 @@ Morph.prototype.visibleBounds = function () { // Morph accessing - simple changes: Morph.prototype.moveBy = function (delta) { - this.changed(); - this.bounds = this.bounds.translateBy(delta); - this.children.forEach(function (child) { - child.moveBy(delta); - }); - this.changed(); + this.fullChanged(); + this.silentMoveBy(delta); + this.fullChanged(); }; Morph.prototype.silentMoveBy = function (delta) { + var children = this.children, + i = children.length; this.bounds = this.bounds.translateBy(delta); - this.children.forEach(function (child) { - child.silentMoveBy(delta); - }); + // ugly optimization avoiding forEach() + for (i; i > 0; i -= 1) { + children[i - 1].silentMoveBy(delta); + } }; Morph.prototype.setPosition = function (aPoint) { @@ -2899,7 +2887,7 @@ Morph.prototype.fullChanged = function () { }; Morph.prototype.childChanged = function () { - // react to a change in one of my children, + // react to a change in one of my children, // default is to just pass this message on upwards // override this method for Morphs that need to adjust accordingly if (this.parent) { @@ -2936,6 +2924,18 @@ Morph.prototype.addBack = function (aMorph) { this.addChildFirst(aMorph); }; +Morph.prototype.topMorphAt = function (point) { + var i, result; + if (!this.isVisible) {return null; } + for (i = this.children.length - 1; i >= 0; i -= 1) { + result = this.children[i].topMorphAt(point); + if (result) {return result; } + } + return this.bounds.containsPoint(point) && + (this.noticesTransparentClick || !this.isTransparentAt(point)) ? this + : null; +}; + Morph.prototype.topMorphSuchThat = function (predicate) { var next; if (predicate.call(null, this)) { @@ -2951,30 +2951,6 @@ Morph.prototype.topMorphSuchThat = function (predicate) { return null; }; -Morph.prototype.morphAt = function (aPoint) { - var morphs = this.allChildren().slice(0).reverse(), - result = null; - morphs.forEach(function (m) { - if (m.fullBounds().containsPoint(aPoint) && - (result === null)) { - result = m; - } - }); - return result; -}; - -/* - alternative - more elegant and possibly more - performant - solution for morphAt. - Has some issues, commented out for now - -Morph.prototype.morphAt = function (aPoint) { - return this.topMorphSuchThat(function (m) { - return m.fullBounds().containsPoint(aPoint); - }); -}; -*/ - Morph.prototype.overlappedMorphs = function () { //exclude the World var world = this.world(), @@ -3046,45 +3022,54 @@ Morph.prototype.fullCopy = function () { Other properties are also *shallow* copied, so you must override to deep copy Arrays and (complex) Objects */ - var dict = {}, c; - c = this.copyRecordingReferences(dict); + var map = new Map(), c; + c = this.copyRecordingReferences(map); c.forAllChildren(function (m) { - m.updateReferences(dict); + m.updateReferences(map); }); return c; }; -Morph.prototype.copyRecordingReferences = function (dict) { +Morph.prototype.copyRecordingReferences = function (map) { /* Recursively copy this entire composite morph, recording the correspondence between old and new morphs in the given dictionary. This dictionary will be used to update intra-composite references in the copy. See updateReferences(). - Note: This default implementation copies ONLY morphs in the - submorph hierarchy. If a morph stores morphs in other properties - that it wants to copy, then it should override this method to do so. - The same goes for morphs that contain other complex data that - should be copied when the morph is duplicated. + + Note: This default implementation copies ONLY morphs. If a morph + stores morphs in other properties that it wants to copy, then it + should override this method to do so. The same goes for morphs that + contain other complex data that should be copied when the morph is + duplicated. */ var c = this.copy(); - dict[this] = c; + map.set(this, c); this.children.forEach(function (m) { - c.add(m.copyRecordingReferences(dict)); + c.add(m.copyRecordingReferences(map)); }); return c; }; -Morph.prototype.updateReferences = function (dict) { +Morph.prototype.updateReferences = function (map) { /* Update intra-morph references within a composite morph that has been copied. For example, if a button refers to morph X in the orginal composite then the copy of that button in the new composite should refer to the copy of X in new composite, not the original X. */ - var property; - for (property in this) { - if (this[property] && this[property].isMorph && dict[property]) { - this[property] = dict[property]; + var properties = Object.keys(this), + l = properties.length, + property, + value, + reference, + i; + for (i = 0; i < l; i += 1) { + property = properties[i]; + value = this[property]; + if (value && value.isMorph) { + reference = map.get(value); + if (reference) { this[property] = reference; } } } }; @@ -3155,9 +3140,7 @@ Morph.prototype.slideBackTo = function (situation, inSteps) { this.fps = 0; this.step = function () { - myself.fullChanged(); - myself.silentMoveBy(new Point(xStep, yStep)); - myself.fullChanged(); + myself.moveBy(new Point(xStep, yStep)); stepCount += 1; if (stepCount === steps) { situation.origin.add(myself); @@ -3706,6 +3689,10 @@ function ShadowMorph() { this.init(); } +ShadowMorph.prototype.topMorphAt = function () { + return null; +}; + // HandleMorph //////////////////////////////////////////////////////// // I am a resize / move handle that can be attached to any Morph @@ -3923,20 +3910,6 @@ HandleMorph.prototype.mouseLeave = function () { this.changed(); }; -// HandleMorph duplicating: - -HandleMorph.prototype.copyRecordingReferences = function (dict) { - // inherited, see comment in Morph - var c = HandleMorph.uber.copyRecordingReferences.call( - this, - dict - ); - if (c.target && dict[this.target]) { - c.target = (dict[this.target]); - } - return c; -}; - // HandleMorph menu: HandleMorph.prototype.attach = function () { @@ -4260,20 +4233,6 @@ ColorPaletteMorph.prototype.updateTarget = function () { } }; -// ColorPaletteMorph duplicating: - -ColorPaletteMorph.prototype.copyRecordingReferences = function (dict) { - // inherited, see comment in Morph - var c = ColorPaletteMorph.uber.copyRecordingReferences.call( - this, - dict - ); - if (c.target && dict[this.target]) { - c.target = (dict[this.target]); - } - return c; -}; - // ColorPaletteMorph menu: ColorPaletteMorph.prototype.developersMenu = function () { @@ -5840,23 +5799,6 @@ SliderMorph.prototype.updateTarget = function () { } }; -// SliderMorph duplicating: - -SliderMorph.prototype.copyRecordingReferences = function (dict) { - // inherited, see comment in Morph - var c = SliderMorph.uber.copyRecordingReferences.call( - this, - dict - ); - if (c.target && dict[this.target]) { - c.target = (dict[this.target]); - } - if (c.button && dict[this.button]) { - c.button = (dict[this.button]); - } - return c; -}; - // SliderMorph menu: SliderMorph.prototype.developersMenu = function () { @@ -6563,7 +6505,7 @@ InspectorMorph.prototype.setExtent = function (aPoint) { this.fixLayout(); }; -//InspectorMorph editing ops: +// InspectorMorph editing ops: InspectorMorph.prototype.save = function () { var txt = this.detail.contents.children[0].text.toString(), @@ -6653,6 +6595,15 @@ InspectorMorph.prototype.step = function () { this.fixLayout(); }; +// InspectorMorph duplicating: + +InspectorMorph.prototype.updateReferences = function (map) { + var active = this.list.activeIndex(); + InspectorMorph.uber.updateReferences.call(this, map); + this.buildPanes(); + this.list.activateIndex(active); +}; + // MenuMorph /////////////////////////////////////////////////////////// // MenuMorph: referenced constructors @@ -7024,7 +6975,7 @@ StringMorph.prototype.init = function ( this.markedBackgoundColor = new Color(60, 60, 120); // initialize inherited properties: - StringMorph.uber.init.call(this); + StringMorph.uber.init.call(this, true); // override inherited properites: this.color = color || new Color(0, 0, 0); @@ -7402,6 +7353,11 @@ StringMorph.prototype.selectionStartSlot = function () { }; StringMorph.prototype.clearSelection = function () { + if (!this.currentlySelecting && + this.startMark === 0 && + this.endMark === 0) { + return; + } this.currentlySelecting = false; this.startMark = 0; this.endMark = 0; @@ -8141,20 +8097,6 @@ TriggerMorph.prototype.createLabel = function () { this.add(this.label); }; -// TriggerMorph duplicating: - -TriggerMorph.prototype.copyRecordingReferences = function (dict) { - // inherited, see comment in Morph - var c = TriggerMorph.uber.copyRecordingReferences.call( - this, - dict - ); - if (c.label && dict[this.label]) { - c.label = (dict[this.label]); - } - return c; -}; - // TriggerMorph action: TriggerMorph.prototype.trigger = function () { @@ -8500,15 +8442,19 @@ FrameMorph.prototype.fullDrawOn = function (aCanvas, aRect) { }); }; -// FrameMorph scrolling optimization: +// FrameMorph navigation: -FrameMorph.prototype.moveBy = function (delta) { - this.changed(); - this.bounds = this.bounds.translateBy(delta); - this.children.forEach(function (child) { - child.silentMoveBy(delta); - }); - this.changed(); +FrameMorph.prototype.topMorphAt = function (point) { + var i, result; + if (!(this.isVisible && this.bounds.containsPoint(point))) { + return null; + } + for (i = this.children.length - 1; i >= 0; i -= 1) { + result = this.children[i].topMorphAt(point); + if (result) {return result; } + } + return this.noticesTransparentClick || + !this.isTransparentAt(point) ? this : null; }; // FrameMorph scrolling support: @@ -8600,20 +8546,6 @@ FrameMorph.prototype.reactToGrabOf = function () { this.adjustBounds(); }; -// FrameMorph duplicating: - -FrameMorph.prototype.copyRecordingReferences = function (dict) { - // inherited, see comment in Morph - var c = FrameMorph.uber.copyRecordingReferences.call( - this, - dict - ); - if (c.frame && dict[this.scrollFrame]) { - c.frame = (dict[this.scrollFrame]); - } - return c; -}; - // FrameMorph menus: FrameMorph.prototype.developersMenu = function () { @@ -8958,32 +8890,23 @@ ScrollFrameMorph.prototype.mouseScroll = function (y, x) { // ScrollFrameMorph duplicating: -ScrollFrameMorph.prototype.copyRecordingReferences = function (dict) { - // inherited, see comment in Morph - var c = ScrollFrameMorph.uber.copyRecordingReferences.call( - this, - dict - ); - if (c.contents && dict[this.contents]) { - c.contents = (dict[this.contents]); - } - if (c.hBar && dict[this.hBar]) { - c.hBar = (dict[this.hBar]); - c.hBar.action = function (num) { - c.contents.setPosition( - new Point(c.left() - num, c.contents.position().y) +ScrollFrameMorph.prototype.updateReferences = function (map) { + var myself = this; + ScrollFrameMorph.uber.updateReferences.call(this, map); + if (this.hBar) { + this.hBar.action = function (num) { + myself.contents.setPosition( + new Point(myself.left() - num, myself.contents.position().y) ); }; } - if (c.vBar && dict[this.vBar]) { - c.vBar = (dict[this.vBar]); - c.vBar.action = function (num) { - c.contents.setPosition( - new Point(c.contents.position().x, c.top() - num) + if (this.vBar) { + this.vBar.action = function (num) { + myself.contents.setPosition( + new Point(myself.contents.position().x, myself.top() - num) ); }; } - return c; }; // ScrollFrameMorph menu: @@ -9147,6 +9070,18 @@ ListMorph.prototype.setExtent = function (aPoint) { ListMorph.uber.setExtent.call(this, aPoint); }; +ListMorph.prototype.activeIndex = function () { + return this.listContents.children.indexOf(this.active); +}; + +ListMorph.prototype.activateIndex = function (idx) { + var item = this.listContents.children[idx]; + if (!item) {return; } + item.image = item.pressImage; + item.changed(); + item.trigger(); +}; + // StringFieldMorph //////////////////////////////////////////////////// // StringFieldMorph inherit from FrameMorph: @@ -9243,20 +9178,6 @@ StringFieldMorph.prototype.mouseClickLeft = function (pos) { } }; -// StringFieldMorph duplicating: - -StringFieldMorph.prototype.copyRecordingReferences = function (dict) { - // inherited, see comment in Morph - var c = StringFieldMorph.uber.copyRecordingReferences.call( - this, - dict - ); - if (c.text && dict[this.text]) { - c.text = (dict[this.text]); - } - return c; -}; - // BouncerMorph //////////////////////////////////////////////////////// // I am a Demo of a stepping custom Morph @@ -9384,54 +9305,18 @@ HandMorph.prototype.changed = function () { if (this.world !== null) { b = this.fullBounds(); if (!b.extent().eq(new Point())) { - this.world.broken.push(this.fullBounds().spread()); + this.world.broken.push(b.spread()); } } - }; -// HandMorph navigation: - -HandMorph.prototype.morphAtPointer = function () { - var morphs = this.world.allChildren().slice(0).reverse(), - myself = this, - result = null; - - morphs.forEach(function (m) { - if (m.visibleBounds().containsPoint(myself.bounds.origin) && - result === null && - m.isVisible && - (m.noticesTransparentClick || - (!m.isTransparentAt(myself.bounds.origin))) && - (!(m instanceof ShadowMorph)) && - m.allParents().every(function (each) { - return each.isVisible; - })) { - result = m; - } - }); - if (result !== null) { - return result; - } - return this.world; -}; +HandMorph.prototype.fullChanged = HandMorph.prototype.changed; -/* - alternative - more elegant and possibly more - performant - solution for morphAtPointer. - Has some issues, commented out for now +// HandMorph navigation: HandMorph.prototype.morphAtPointer = function () { - var myself = this; - return this.world.topMorphSuchThat(function (m) { - return m.visibleBounds().containsPoint(myself.bounds.origin) && - m.isVisible && - (m.noticesTransparentClick || - (! m.isTransparentAt(myself.bounds.origin))) && - (! (m instanceof ShadowMorph)); - }); + return this.world.topMorphAt(this.bounds.origin) || this.world; }; -*/ HandMorph.prototype.allMorphsAtPointer = function () { var morphs = this.world.allChildren(), @@ -9971,16 +9856,6 @@ HandMorph.prototype.destroyTemporaries = function () { }); }; -// HandMorph dragging optimization - -HandMorph.prototype.moveBy = function (delta) { - Morph.prototype.trackChanges = false; - HandMorph.uber.moveBy.call(this, delta); - Morph.prototype.trackChanges = true; - this.fullChanged(); -}; - - // WorldMorph ////////////////////////////////////////////////////////// // I represent the <canvas> element @@ -10009,6 +9884,7 @@ WorldMorph.prototype.init = function (aCanvas, fillPage) { this.isDraggable = false; this.currentKey = null; // currently pressed key code this.worldCanvas = aCanvas; + this.noticesTransparentClick = true; // additional properties: this.stamp = Date.now(); // reference in multi-world setups diff --git a/morphic.txt b/morphic.txt index 042b7e5..11a323d 100755 --- a/morphic.txt +++ b/morphic.txt @@ -7,9 +7,9 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2012 by Jens Mönig + Copyright (C) 2015 by Jens Mönig - this documentation last changed: April 07, 2013 + this documentation last changed: June 26, 2015 This file is part of Snap!. @@ -465,9 +465,15 @@ MyMorph.prototype.mouseMove = function(pos) {}; - The only optional parameter of such a method is a Point object + All of these methods have as optional parameter a Point object indicating the current position of the Hand inside the World's - coordinate system. + coordinate system. The + + mouseMove(pos, button) + + event method has an additional optional parameter indicating the + currently pressed mouse button, which is either 'left' or 'right'. + You can use this to let users interact with 3D environments. Events may be "bubbled" up a morph's owner chain by calling @@ -522,6 +528,12 @@ a duplicate of the template whose "isDraggable" flag is true and whose "isTemplate" flag is false, in other words: a non-template. + When creating a copy from a template, the copy's + + reactToTemplateCopy + + is invoked, if it is present. + Dragging is indicated by adding a drop shadow to the morph in hand. If a morph follows the hand without displaying a drop shadow it is merely being moved about without changing its parent (owner morph), @@ -817,13 +829,13 @@ // use context to paint stuff here }; - If your new morph stores or references other morphs outside of the - submorph tree in other properties, be sure to also override the + If your new morph stores or references to other morphs outside of + the submorph tree in other properties, be sure to also override the default - copyRecordingReferences() + updateReferences() - method accordingly if you want it to support duplication. + method if you want it to support duplication. (6) development and user modes @@ -1015,16 +1027,17 @@ programming hero. I have originally written morphic.js in Florian Balmer's Notepad2 - editor for Windows and later switched to Apple's Dashcode. I've also - come to depend on both Douglas Crockford's JSLint, Mozilla's Firebug - and Google's Chrome to get it right. + editor for Windows, later switched to Apple's Dashcode and later + still to Apple's Xcode. I've also come to depend on both Douglas + Crockford's JSLint, Mozilla's Firebug and Google's Chrome to get + it right. IX. contributors ---------------------- Joe Otto found and fixed many early bugs and taught me some tricks. Nathan Dinsmore contributed mouse wheel scrolling, cached - background texture handling and countless bug fixes. + background texture handling, countless bug fixes and optimizations. Ian Reynolds contributed backspace key handling for Chrome. Davide Della Casa contributed performance optimizations for Firefox. @@ -105,7 +105,7 @@ InspectorMorph, ListMorph, Math, MenuItemMorph, MenuMorph, Morph, MorphicPreferences, MouseSensorMorph, Node, Object, PenMorph, Point, Rectangle, ScrollFrameMorph, ShadowMorph, SliderButtonMorph, SliderMorph, String, StringFieldMorph, StringMorph, TextMorph, -TriggerMorph, WorldMorph, clone, contains, copy, degrees, detect, +TriggerMorph, WorldMorph, contains, copy, degrees, detect, document, getDocumentPositionOf, isNaN, isObject, isString, newCanvas, nop, parseFloat, radians, standardSettings, touchScreenSettings, useBlurredShadows, version, window, modules, IDE_Morph, VariableDialogMorph, @@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/ // Global stuff //////////////////////////////////////////////////////// -modules.objects = '2015-May-18'; +modules.objects = '2015-June-25'; var SpriteMorph; var StageMorph; @@ -583,7 +583,7 @@ SpriteMorph.prototype.initBlocks = function () { }, /* migrated to a newer block version: - + receiveClick: { type: 'hat', category: 'control', @@ -2718,7 +2718,7 @@ SpriteMorph.prototype.remove = function () { SpriteMorph.prototype.createClone = function () { var stage = this.parentThatIsA(StageMorph); - if (stage && stage.cloneCount <= 300) { + if (stage && stage.cloneCount <= 1000) { this.fullCopy().clonify(stage); } }; @@ -3008,7 +3008,7 @@ SpriteMorph.prototype.applyGraphicsEffects = function (canvas) { var i; if (value !== 0) { for (i = 0; i < p.length; i += 4) { - p[i] += value; //255 = 100% of this color + p[i] += value; //255 = 100% of this color p[i + 1] += value; p[i + 2] += value; } @@ -3290,7 +3290,7 @@ SpriteMorph.prototype.nestingBounds = function () { // SpriteMorph motion primitives -Morph.prototype.setPosition = function (aPoint, justMe) { +SpriteMorph.prototype.setPosition = function (aPoint, justMe) { // override the inherited default to make sure my parts follow // unless it's justMe var delta = aPoint.subtract(this.topLeft()); @@ -3509,7 +3509,10 @@ SpriteMorph.prototype.allHatBlocksFor = function (message) { if (morph.selector) { if (morph.selector === 'receiveMessage') { event = morph.inputs()[0].evaluate(); - return event === message || (event instanceof Array); + return event === message + || (event instanceof Array + && message !== '__shout__go__' + && message !== '__clone__init__'); } if (morph.selector === 'receiveGo') { return message === '__shout__go__'; @@ -6429,7 +6432,7 @@ Note.prototype.play = function () { if (!this.oscillator.stop) { this.oscillator.stop = this.oscillator.noteOff; } - this.oscillator.type = 0; + this.oscillator.type = 'sine'; this.oscillator.frequency.value = Math.pow(2, (this.pitch - 69) / 12) * 440; this.oscillator.connect(this.gainNode); @@ -5,7 +5,7 @@ inspired by the Scratch paint editor. written by Kartik Chandra - Copyright (C) 2014 by Kartik Chandra + Copyright (C) 2015 by Kartik Chandra This file is part of Snap!. @@ -69,7 +69,7 @@ // Global stuff //////////////////////////////////////////////////////// -modules.paint = '2014-September-29'; +modules.paint = '2015-June-25'; // Declarations @@ -61,7 +61,7 @@ SyntaxElementMorph, Variable*/ // Global stuff //////////////////////////////////////////////////////// -modules.store = '2015-April-26'; +modules.store = '2015-June-25'; // XML_Serializer /////////////////////////////////////////////////////// @@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/ // Global stuff //////////////////////////////////////////////////////// -modules.threads = '2015-May-01'; +modules.threads = '2015-June-25'; var ThreadManager; var Process; @@ -339,7 +339,7 @@ ThreadManager.prototype.findProcess = function (block) { */ Process.prototype = {}; -Process.prototype.contructor = Process; +Process.prototype.constructor = Process; Process.prototype.timeout = 500; // msecs after which to force yield Process.prototype.isCatchingErrors = true; @@ -2122,14 +2122,14 @@ Process.prototype.reportMonadic = function (fname, n) { case 'ln': result = Math.log(x); break; - case 'log': - result = 0; + case 'log': // base 10 + result = Math.log(x) / Math.LN10; break; case 'e^': result = Math.exp(x); break; case '10^': - result = 0; + result = Math.pow(10, x); break; default: nop(); @@ -2640,31 +2640,30 @@ Process.prototype.reportTimer = function () { }; // Process Dates and times in Snap -// Map block options to built-in functions -var dateMap = { - 'year' : 'getFullYear', - 'month' : 'getMonth', - 'date': 'getDate', - 'day of week' : 'getDay', - 'hour' : 'getHours', - 'minute' : 'getMinutes', - 'second' : 'getSeconds', - 'time in milliseconds' : 'getTime' -}; - Process.prototype.reportDate = function (datefn) { - var inputFn = this.inputOption(datefn), - currDate = new Date(), - func = dateMap[inputFn], - result = currDate[func](); + var currDate, func, result, + inputFn = this.inputOption(datefn), + // Map block options to built-in functions + dateMap = { + 'year' : 'getFullYear', + 'month' : 'getMonth', + 'date': 'getDate', + 'day of week' : 'getDay', + 'hour' : 'getHours', + 'minute' : 'getMinutes', + 'second' : 'getSeconds', + 'time in milliseconds' : 'getTime' + }; if (!dateMap[inputFn]) { return ''; } + currDate = new Date(); + func = dateMap[inputFn]; + result = currDate[func](); // Show months as 1-12 and days as 1-7 if (inputFn === 'month' || inputFn === 'day of week') { result += 1; } - return result; }; @@ -7,7 +7,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2015 by Jens Mönig This file is part of Snap!. @@ -74,7 +74,7 @@ HTMLCanvasElement, fontHeight, SymbolMorph, localize, SpeechBubbleMorph, ArrowMorph, MenuMorph, isString, isNil, SliderMorph, MorphicPreferences, ScrollFrameMorph*/ -modules.widgets = '2014-February-13'; +modules.widgets = '2015-June-25'; var PushButtonMorph; var ToggleButtonMorph; @@ -7,7 +7,7 @@ written by Jens Mönig jens@moenig.org - Copyright (C) 2014 by Jens Mönig + Copyright (C) 2015 by Jens Mönig This file is part of Snap!. @@ -57,15 +57,17 @@ Nathan Dinsmore contributed to the design and implemented a first working version of a complete XMLSerializer. I have taken much of the overall design and many of the functions and methods in this file from - Nathan's fine original prototype. + Nathan's fine original prototype. Recently Nathan has once again + worked his magic on the parser and optimized it by an order of + magnitude. */ -/*global modules, isString, detect, Node, isNil*/ +/*global modules, detect, Node, isNil*/ // Global stuff //////////////////////////////////////////////////////// -modules.xml = '2014-January-09'; +modules.xml = '2015-June-25'; // Declarations @@ -85,7 +87,8 @@ function ReadStream(arrayOrString) { // ReadStream constants: -ReadStream.prototype.space = /[\s]/; +ReadStream.prototype.nonSpace = /\S|$/g; +ReadStream.prototype.nonWord = /[\s\>\/\=]|$/g; // ReadStream accessing: @@ -115,46 +118,26 @@ ReadStream.prototype.atEnd = function () { // ReadStream accessing String contents: -ReadStream.prototype.upTo = function (regex) { - var i, start; - if (!isString(this.contents)) {return ''; } - i = this.contents.substr(this.index).search(regex); - if (i === -1) { - return ''; - } - start = this.index; - this.index += i; - return this.contents.substring(start, this.index); +ReadStream.prototype.upTo = function (str) { + var i = this.contents.indexOf(str, this.index); + return i === -1 ? '' : this.contents.slice(this.index, this.index = i); }; -ReadStream.prototype.peekUpTo = function (regex) { - if (!isString(this.contents)) {return ''; } - var i = this.contents.substr(this.index).search(regex); - if (i === -1) { - return ''; - } - return this.contents.substring(this.index, this.index + i); +ReadStream.prototype.peekUpTo = function (str) { + var i = this.contents.indexOf(str, this.index); + return i === -1 ? '' : this.contents.slice(this.index, i); }; ReadStream.prototype.skipSpace = function () { - if (!isString(this.contents)) {return ''; } - var ch = this.peek(); - while (this.space.test(ch) && ch !== '') { - this.skip(); - ch = this.peek(); - } + this.nonSpace.lastIndex = this.index; + var result = this.nonSpace.exec(this.contents); + if (result) this.index = result.index; }; ReadStream.prototype.word = function () { - var i, start; - if (!isString(this.contents)) {return ''; } - i = this.contents.substr(this.index).search(/[\s\>\/\=]|$/); - if (i === -1) { - return ''; - } - start = this.index; - this.index += i; - return this.contents.substring(start, this.index); + this.nonWord.lastIndex = this.index; + var result = this.nonWord.exec(this.contents); + return result ? this.contents.slice(this.index, this.index = result.index) : ''; }; // XML_Element /////////////////////////////////////////////////////////// @@ -166,7 +149,7 @@ ReadStream.prototype.word = function () { // XML_Element inherits from Node: -XML_Element.prototype = new Node(); +XML_Element.prototype = Object.create(Node.prototype); XML_Element.prototype.constructor = XML_Element; XML_Element.uber = Node.prototype; @@ -190,9 +173,7 @@ XML_Element.prototype.init = function (tag, contents, parent) { XML_Element.uber.init.call(this); // override inherited properties - if (parent instanceof XML_Element) { - parent.addChild(this); - } + if (parent) parent.addChild(this); }; // XML_Element DOM navigation: (aside from what's inherited from Node) @@ -318,51 +299,18 @@ XML_Element.prototype.escape = function (string, ignoreQuotes) { }; XML_Element.prototype.unescape = function (string) { - var stream = new ReadStream(string), - result = '', - ch, - esc; - - function nextPut(str) { - result += str; - stream.upTo(';'); - stream.skip(); - } - - while (!stream.atEnd()) { - ch = stream.next(); - if (ch === '&') { - esc = stream.peekUpTo(';'); - switch (esc) { - case 'apos': - nextPut('\''); - break; - case 'quot': - nextPut('\"'); - break; - case 'lt': - nextPut('<'); - break; - case 'gt': - nextPut('>'); - break; - case 'amp': - nextPut('&'); - break; - case '#xD': - nextPut('\n'); - break; - case '#126': - nextPut('~'); - break; - default: - result += ch; - } - } else { - result += ch; + return string.replace(/&(amp|apos|quot|lt|gt|#xD|#126);/g, function(_, name) { + switch (name) { + case 'amp': return '&'; + case 'apos': return '\''; + case 'quot': return '"'; + case 'lt': return '<'; + case 'gt': return '>'; + case '#xD': return '\n'; + case '#126': return '~'; + default: console.warn('unreachable'); } - } - return result; + }); }; // XML_Element parsing: @@ -375,10 +323,7 @@ XML_Element.prototype.parseString = function (string) { }; XML_Element.prototype.parseStream = function (stream) { - var key, - value, - ch, - child; + var key, value, ch, child; // tag: this.tag = stream.word(); @@ -395,9 +340,7 @@ XML_Element.prototype.parseStream = function (stream) { stream.skipSpace(); ch = stream.next(); if (ch !== '"' && ch !== "'") { - throw new Error( - 'Expected single- or double-quoted attribute value' - ); + throw new Error('Expected single- or double-quoted attribute value'); } value = stream.upTo(ch); stream.skip(1); @@ -407,7 +350,7 @@ XML_Element.prototype.parseStream = function (stream) { } // empty tag: - if (stream.peek() === '/') { + if (ch === '/') { stream.skip(); if (stream.next() !== '>') { throw new Error('Expected ">" after "/" in empty tag'); |
