|
|
- // Copyright (c) 2014-17 Walter Bender
- //
- // This program is free software; you can redistribute it and/or
- // modify it under the terms of the 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.
- //
- // You should have received a copy of the GNU Affero General Public
- // License along with this library; if not, write to the Free Software
- // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
- //
-
- // Length of a long touch
- const LONGPRESSTIME = 1500;
- const COLLAPSABLES = ['drum', 'start', 'action', 'matrix', 'pitchdrummatrix', 'rhythmruler', 'status', 'pitchstaircase', 'tempo', 'pitchslider', 'modewidget'];
- const NOHIT = ['hidden', 'hiddennoflow'];
- const SPECIALINPUTS = ['text', 'number', 'solfege', 'eastindiansolfege', 'notename', 'voicename', 'modename', 'drumname'];
-
- // Define block instance objects and any methods that are intra-block.
- function Block(protoblock, blocks, overrideName) {
- if (protoblock === null) {
- console.log('null protoblock sent to Block');
- return;
- }
-
- this.protoblock = protoblock;
- this.name = protoblock.name;
- this.overrideName = overrideName;
- this.blocks = blocks;
- this.collapsed = false; // Is this block in a collapsed stack?
- this.trash = false; // Is this block in the trash?
- this.loadComplete = false; // Has the block finished loading?
- this.label = null; // Editable textview in DOM.
- this.text = null; // A dynamically generated text label on block itself.
- this.value = null; // Value for number, text, and media blocks.
- this.privateData = null; // A block may have some private data,
- // e.g., nameboxes use this field to store
- // the box name associated with the block.
- this.image = protoblock.image; // The file path of the image.
- this.imageBitmap = null;
-
- // All blocks have at a container and least one bitmap.
- this.container = null;
- this.bounds = null;
- this.bitmap = null;
- this.highlightBitmap = null;
-
- // The svg from which the bitmaps are generated
- this.artwork = null;
- this.collapseArtwork = null;
-
- // Start and Action blocks has a collapse button (in a separate
- // container).
- this.collapseContainer = null;
- this.collapseBitmap = null;
- this.expandBitmap = null;
- this.collapseBlockBitmap = null;
- this.highlightCollapseBlockBitmap = null;
- this.collapseText = null;
-
- this.size = 1; // Proto size is copied here.
- this.docks = []; // Proto dock is copied here.
- this.connections = [];
-
- // Keep track of clamp count for blocks with clamps.
- this.clampCount = [1, 1];
- this.argClampSlots = [1];
-
- // Some blocks have some post process after they are first loaded.
- this.postProcess = null;
- this.postProcessArg = null;
-
- // Lock on label change
- this._label_lock = false;
-
- // Internal function for creating cache.
- // Includes workaround for a race condition.
- this._createCache = function () {
- var myBlock = this;
- myBlock.bounds = myBlock.container.getBounds();
-
- if (myBlock.bounds == null) {
- setTimeout(function () {
- myBlock._createCache();
- }, 200);
- } else {
- myBlock.container.cache(myBlock.bounds.x, myBlock.bounds.y, myBlock.bounds.width, myBlock.bounds.height);
- }
- };
-
- // Internal function for creating cache.
- // Includes workaround for a race condition.
- this.updateCache = function () {
- var myBlock = this;
-
- if (myBlock.bounds == null) {
- setTimeout(function () {
- myBlock.updateCache();
- }, 300);
- } else {
- myBlock.container.updateCache();
- myBlock.blocks.refreshCanvas();
- }
- };
-
- this.offScreen = function (boundary) {
- return !this.trash && boundary.offScreen(this.container.x, this.container.y);
- };
-
- this.copySize = function () {
- this.size = this.protoblock.size;
- };
-
- this.getInfo = function () {
- return this.name + ' block';
- };
-
- this.highlight = function () {
- if (this.collapsed && COLLAPSABLES.indexOf(this.name) !== -1) {
- // We may have a race condition.
- if (this.highlightCollapseBlockBitmap) {
- this.highlightCollapseBlockBitmap.visible = true;
- this.collapseBlockBitmap.visible = false;
- this.collapseText.visible = true;
- this.bitmap.visible = false;
- this.highlightBitmap.visible = false;
- }
- } else {
- this.bitmap.visible = false;
- this.highlightBitmap.visible = true;
- if (COLLAPSABLES.indexOf(this.name) !== -1) {
- // There could be a race condition when making a
- // new action block.
- if (this.highlightCollapseBlockBitmap) {
- if (this.collapseText !== null) {
- this.collapseText.visible = false;
- }
- if (this.collapseBlockBitmap.visible !== null) {
- this.collapseBlockBitmap.visible = false;
- }
- if (this.highlightCollapseBlockBitmap.visible !== null) {
- this.highlightCollapseBlockBitmap.visible = false;
- }
- }
- }
- }
- this.updateCache();
- };
-
- this.unhighlight = function () {
- if (this.collapsed && COLLAPSABLES.indexOf(this.name) !== -1) {
- if (this.highlightCollapseBlockBitmap) {
- this.highlightCollapseBlockBitmap.visible = false;
- this.collapseBlockBitmap.visible = true;
- this.collapseText.visible = true;
- this.bitmap.visible = false;
- this.highlightBitmap.visible = false;
- }
- } else {
- this.bitmap.visible = true;
- this.highlightBitmap.visible = false;
- if (COLLAPSABLES.indexOf(this.name) !== -1) {
- if (this.highlightCollapseBlockBitmap) {
- this.highlightCollapseBlockBitmap.visible = false;
- this.collapseBlockBitmap.visible = false;
- this.collapseText.visible = false;
- }
- }
- }
- this.updateCache();
- };
-
- this.updateArgSlots = function (slotList) {
- // Resize and update number of slots in argClamp
- this.argClampSlots = slotList;
- this._newArtwork();
- this.regenerateArtwork(false);
- };
-
- this.updateSlots = function (clamp, plusMinus) {
- // Resize an expandable block.
- this.clampCount[clamp] += plusMinus;
- this._newArtwork(plusMinus);
- this.regenerateArtwork(false);
- };
-
- this.resize = function (scale) {
- // If the block scale changes, we need to regenerate the
- // artwork and recalculate the hitarea.
- var myBlock = this;
-
- this.postProcess = function (args) {
- if (myBlock.imageBitmap !== null) {
- myBlock._positionMedia(myBlock.imageBitmap, myBlock.imageBitmap.image.width, myBlock.imageBitmap.image.height, scale);
- z = myBlock.container.getNumChildren() - 1;
- myBlock.container.setChildIndex(myBlock.imageBitmap, z);
- }
-
- if (myBlock.name === 'start' || myBlock.name === 'drum') {
- // Rescale the decoration on the start blocks.
- for (var turtle = 0; turtle < myBlock.blocks.turtles.turtleList.length; turtle++) {
- if (myBlock.blocks.turtles.turtleList[turtle].startBlock === myBlock) {
- myBlock.blocks.turtles.turtleList[turtle].resizeDecoration(scale, myBlock.bitmap.image.width);
- myBlock._ensureDecorationOnTop();
- break;
- }
- }
- }
- myBlock.updateCache();
- myBlock._calculateBlockHitArea();
-
- // If it is in the trash, make sure it remains hidden.
- if (myBlock.trash) {
- myBlock.hide();
- }
- };
-
- this.postProcessArg = null;
-
- this.protoblock.scale = scale;
- this._newArtwork(0);
- this.regenerateArtwork(true, []);
-
- if (this.text !== null) {
- this._positionText(scale);
- }
-
- if (this.collapseContainer !== null) {
- this.collapseContainer.uncache();
- var postProcess = function (myBlock) {
- myBlock.collapseBitmap.scaleX = myBlock.collapseBitmap.scaleY = myBlock.collapseBitmap.scale = scale / 2;
- myBlock.expandBitmap.scaleX = myBlock.expandBitmap.scaleY = myBlock.expandBitmap.scale = scale / 2;
-
- var bounds = myBlock.collapseContainer.getBounds();
- if (bounds) myBlock.collapseContainer.cache(bounds.x, bounds.y, bounds.width, bounds.height);
- myBlock._positionCollapseContainer(myBlock.protoblock.scale);
- myBlock._calculateCollapseHitArea();
- };
-
- this._generateCollapseArtwork(postProcess);
- var fontSize = 10 * scale;
- this.collapseText.font = fontSize + 'px Sans';
- this._positionCollapseLabel(scale);
- }
- };
-
- this._newArtwork = function (plusMinus) {
- if (COLLAPSABLES.indexOf(this.name) > -1) {
- var proto = new ProtoBlock('collapse');
- proto.scale = this.protoblock.scale;
- proto.extraWidth = 10;
- proto.basicBlockCollapsed();
- var obj = proto.generator();
- this.collapseArtwork = obj[0];
- var obj = this.protoblock.generator(this.clampCount[0]);
- } else if (this.name === 'ifthenelse') {
- var obj = this.protoblock.generator(this.clampCount[0], this.clampCount[1]);
- } else if (this.protoblock.style === 'clamp') {
- var obj = this.protoblock.generator(this.clampCount[0]);
- } else {
- switch (this.name) {
- case 'equal':
- case 'greater':
- case 'less':
- var obj = this.protoblock.generator(this.clampCount[0]);
- break;
- case 'calcArg':
- case 'doArg':
- case 'namedcalcArg':
- case 'nameddoArg':
- var obj = this.protoblock.generator(this.argClampSlots);
- this.size = 2;
- for (var i = 0; i < this.argClampSlots.length; i++) {
- this.size += this.argClampSlots[i];
- }
- this.docks = [];
- this.docks.push([obj[1][0][0], obj[1][0][1], this.protoblock.dockTypes[0]]);
- break;
- default:
- if (this.isArgBlock()) {
- var obj = this.protoblock.generator(this.clampCount[0]);
- } else if (this.isTwoArgBlock()) {
- var obj = this.protoblock.generator(this.clampCount[0]);
- } else {
- var obj = this.protoblock.generator();
- }
- this.size += plusMinus;
- break;
- }
- }
-
- switch (this.name) {
- case 'nameddoArg':
- for (var i = 1; i < obj[1].length - 1; i++) {
- this.docks.push([obj[1][i][0], obj[1][i][1], 'anyin']);
- }
-
- this.docks.push([obj[1][2][0], obj[1][2][1], 'in']);
- break;
- case 'namedcalcArg':
- for (var i = 1; i < obj[1].length; i++) {
- this.docks.push([obj[1][i][0], obj[1][i][1], 'anyin']);
- }
- break;
- case 'doArg':
- this.docks.push([obj[1][1][0], obj[1][1][1], this.protoblock.dockTypes[1]]);
- for (var i = 2; i < obj[1].length - 1; i++) {
- this.docks.push([obj[1][i][0], obj[1][i][1], 'anyin']);
- }
-
- this.docks.push([obj[1][3][0], obj[1][3][1], 'in']);
- break;
- case 'calcArg':
- this.docks.push([obj[1][1][0], obj[1][1][1], this.protoblock.dockTypes[1]]);
- for (var i = 2; i < obj[1].length; i++) {
- this.docks.push([obj[1][i][0], obj[1][i][1], 'anyin']);
- }
- break;
- default:
- break;
- }
-
- // Save new artwork and dock positions.
- this.artwork = obj[0];
- for (var i = 0; i < this.docks.length; i++) {
- this.docks[i][0] = obj[1][i][0];
- this.docks[i][1] = obj[1][i][1];
- }
- };
-
- this.imageLoad = function () {
- // Load any artwork associated with the block and create any
- // extra parts. Image components are loaded asynchronously so
- // most the work happens in callbacks.
-
- // We need a text label for some blocks. For number and text
- // blocks, this is the primary label; for parameter blocks,
- // this is used to display the current block value.
- var fontSize = 10 * this.protoblock.scale;
- this.text = new createjs.Text('', fontSize + 'px Sans', '#000000');
-
- this.generateArtwork(true, []);
- };
-
- this._addImage = function () {
- var image = new Image();
- var myBlock = this;
-
- image.onload = function () {
- var bitmap = new createjs.Bitmap(image);
- bitmap.name = 'media';
- myBlock.container.addChild(bitmap);
- myBlock._positionMedia(bitmap, image.width, image.height, myBlock.protoblock.scale);
- myBlock.imageBitmap = bitmap;
- myBlock.updateCache();
- };
- image.src = this.image;
- };
-
- this.regenerateArtwork = function (collapse) {
- // Sometimes (in the case of namedboxes and nameddos) we need
- // to regenerate the artwork associated with a block.
-
- // First we need to remove the old artwork.
- if (this.bitmap != null) {
- this.container.removeChild(this.bitmap);
- }
-
- if (this.highlightBitmap != null) {
- this.container.removeChild(this.highlightBitmap);
- }
-
- if (collapse && this.collapseBitmap !== null) {
- this.collapseContainer.removeChild(this.collapseBitmap);
- this.collapseContainer.removeChild(this.expandBitmap);
- this.container.removeChild(this.collapseBlockBitmap);
- this.container.removeChild(this.highlightCollapseBlockBitmap);
- }
-
- // Then we generate new artwork.
- this.generateArtwork(false);
- };
-
- this.generateArtwork = function (firstTime) {
- // Get the block labels from the protoblock.
- var myBlock = this;
- var thisBlock = this.blocks.blockList.indexOf(this);
- var block_label = '';
-
- // Create the highlight bitmap for the block.
- function __processHighlightBitmap(name, bitmap, myBlock) {
- if (myBlock.highlightBitmap != null) {
- myBlock.container.removeChild(myBlock.highlightBitmap);
- }
-
- myBlock.highlightBitmap = bitmap;
- myBlock.container.addChild(myBlock.highlightBitmap);
- myBlock.highlightBitmap.x = 0;
- myBlock.highlightBitmap.y = 0;
- myBlock.highlightBitmap.name = 'bmp_highlight_' + thisBlock;
- myBlock.highlightBitmap.cursor = 'pointer';
- // Hide highlight bitmap to start.
- myBlock.highlightBitmap.visible = false;
-
- // At me point, it should be safe to calculate the
- // bounds of the container and cache its contents.
- if (!firstTime) {
- myBlock.container.uncache();
- }
-
- myBlock._createCache();
- myBlock.blocks.refreshCanvas();
-
- if (firstTime) {
- myBlock._loadEventHandlers();
- if (myBlock.image !== null) {
- myBlock._addImage();
- }
- myBlock._finishImageLoad();
- } else {
- if (myBlock.name === 'start' || myBlock.name === 'drum') {
- myBlock._ensureDecorationOnTop();
- }
-
- // Adjust the docks.
- myBlock.blocks.adjustDocks(thisBlock, true);
-
- // Adjust the text position.
- myBlock._positionText(myBlock.protoblock.scale);
-
- if (COLLAPSABLES.indexOf(myBlock.name) !== -1) {
- myBlock.bitmap.visible = !myBlock.collapsed;
- myBlock.highlightBitmap.visible = false;
- myBlock.updateCache();
- }
-
- if (myBlock.postProcess != null) {
- myBlock.postProcess(myBlock.postProcessArg);
- myBlock.postProcess = null;
- }
- }
- };
-
- // Create the bitmap for the block.
- function __processBitmap(name, bitmap, myBlock) {
- if (myBlock.bitmap != null) {
- myBlock.container.removeChild(myBlock.bitmap);
- }
-
- myBlock.bitmap = bitmap;
- myBlock.container.addChild(myBlock.bitmap);
- myBlock.bitmap.x = 0;
- myBlock.bitmap.y = 0;
- myBlock.bitmap.name = 'bmp_' + thisBlock;
- myBlock.bitmap.cursor = 'pointer';
- myBlock.blocks.refreshCanvas();
-
- if (myBlock.protoblock.disabled) {
- var artwork = myBlock.artwork.replace(/fill_color/g, DISABLEDFILLCOLOR).replace(/stroke_color/g, DISABLEDSTROKECOLOR).replace('block_label', block_label);
- } else {
- var artwork = myBlock.artwork.replace(/fill_color/g, PALETTEHIGHLIGHTCOLORS[myBlock.protoblock.palette.name]).replace(/stroke_color/g, HIGHLIGHTSTROKECOLORS[myBlock.protoblock.palette.name]).replace('block_label', block_label);
- }
-
- for (var i = 1; i < myBlock.protoblock.staticLabels.length; i++) {
- artwork = artwork.replace('arg_label_' + i, myBlock.protoblock.staticLabels[i]);
- }
-
- _makeBitmap(artwork, myBlock.name, __processHighlightBitmap, myBlock);
- };
-
- if (this.overrideName) {
- if (['nameddo', 'nameddoArg', 'namedcalc', 'namedcalcArg'].indexOf(this.name) !== -1) {
- block_label = this.overrideName;
- if (block_label.length > 8) {
- block_label = block_label.substr(0, 7) + '...';
- }
- } else {
- block_label = this.overrideName;
- }
- } else if (this.protoblock.staticLabels.length > 0 && !this.protoblock.image) {
- // Label should be defined inside _().
- block_label = this.protoblock.staticLabels[0];
- }
-
- while (this.protoblock.staticLabels.length < this.protoblock.args + 1) {
- this.protoblock.staticLabels.push('');
- }
-
- if (firstTime) {
- // Create artwork and dock.
- var obj = this.protoblock.generator();
- this.artwork = obj[0];
- for (var i = 0; i < obj[1].length; i++) {
- this.docks.push([obj[1][i][0], obj[1][i][1], this.protoblock.dockTypes[i]]);
- }
- }
-
- if (this.protoblock.disabled) {
- var artwork = this.artwork.replace(/fill_color/g, DISABLEDFILLCOLOR).replace(/stroke_color/g, DISABLEDSTROKECOLOR).replace('block_label', block_label);
- } else {
- var artwork = this.artwork.replace(/fill_color/g, PALETTEFILLCOLORS[this.protoblock.palette.name]).replace(/stroke_color/g, PALETTESTROKECOLORS[this.protoblock.palette.name]).replace('block_label', block_label);
- }
-
- for (var i = 1; i < this.protoblock.staticLabels.length; i++) {
- artwork = artwork.replace('arg_label_' + i, this.protoblock.staticLabels[i]);
- }
-
- _makeBitmap(artwork, this.name, __processBitmap, this);
- };
-
- this._finishImageLoad = function () {
- var thisBlock = this.blocks.blockList.indexOf(this);
-
- // Value blocks get a modifiable text label.
- if (SPECIALINPUTS.indexOf(this.name) !== -1) {
- if (this.value == null) {
- switch(this.name) {
- case 'text':
- this.value = '---';
- break;
- case 'solfege':
- case 'eastindiansolfege':
- this.value = 'sol';
- break;
- case 'notename':
- this.value = 'G';
- break;
- case 'rest':
- this.value = _('rest');
- break;
- case 'number':
- this.value = NUMBERBLOCKDEFAULT;
- break;
- case 'modename':
- this.value = getModeName(DEFAULTMODE);
- break;
- case 'voicename':
- this.value = DEFAULTVOICE;
- break;
- case 'drumname':
- this.value = getDrumName(DEFAULTDRUM);
- break;
- }
- }
-
- if (this.name === 'solfege') {
- var obj = splitSolfege(this.value);
- var label = i18nSolfege(obj[0]);
- var attr = obj[1];
-
- if (attr !== '♮') {
- label += attr;
- }
- } else if (this.name === 'eastindiansolfege') {
- var obj = splitSolfege(this.value);
- var label = WESTERN2EISOLFEGENAMES[obj[0]];
- var attr = obj[1];
-
- if (attr !== '♮') {
- label += attr;
- }
- } else {
- var label = this.value.toString();
- }
-
- if (label.length > 8) {
- label = label.substr(0, 7) + '...';
- }
-
- this.text.text = label;
- this.container.addChild(this.text);
- this._positionText(this.protoblock.scale);
- } else if (this.protoblock.parameter) {
- // Parameter blocks get a text label to show their current value.
- this.container.addChild(this.text);
- this._positionText(this.protoblock.scale);
- }
-
- if (COLLAPSABLES.indexOf(this.name) === -1) {
- this.loadComplete = true;
- if (this.postProcess !== null) {
- this.postProcess(this.postProcessArg);
- this.postProcess = null;
- }
-
- this.blocks.refreshCanvas();
- this.blocks.cleanupAfterLoad(this.name);
- } else {
- // Start blocks and Action blocks can collapse, so add an
- // event handler.
- var proto = new ProtoBlock('collapse');
- proto.scale = this.protoblock.scale;
- proto.extraWidth = 10;
- proto.basicBlockCollapsed();
- var obj = proto.generator();
- this.collapseArtwork = obj[0];
- var postProcess = function (myBlock) {
- myBlock._loadCollapsibleEventHandlers();
- myBlock.loadComplete = true;
-
- if (myBlock.postProcess !== null) {
- myBlock.postProcess(myBlock.postProcessArg);
- myBlock.postProcess = null;
- }
- };
-
- this._generateCollapseArtwork(postProcess);
- }
- };
-
- this._generateCollapseArtwork = function (postProcess) {
- var myBlock = this;
- var thisBlock = this.blocks.blockList.indexOf(this);
-
- function __processHighlightCollapseBitmap(name, bitmap, myBlock) {
- myBlock.highlightCollapseBlockBitmap = bitmap;
- myBlock.highlightCollapseBlockBitmap.name = 'highlight_collapse_' + thisBlock;
- myBlock.container.addChild(myBlock.highlightCollapseBlockBitmap);
- myBlock.highlightCollapseBlockBitmap.visible = false;
-
- if (myBlock.collapseText === null) {
- var fontSize = 10 * myBlock.protoblock.scale;
- switch (myBlock.name) {
- case 'action':
- myBlock.collapseText = new createjs.Text(_('action'), fontSize + 'px Sans', '#000000');
- break;
- case 'start':
- myBlock.collapseText = new createjs.Text(_('start'), fontSize + 'px Sans', '#000000');
- break;
- case 'matrix':
- myBlock.collapseText = new createjs.Text(_('matrix'), fontSize + 'px Sans', '#000000');
- break;
- case 'status':
- myBlock.collapseText = new createjs.Text(_('status'), fontSize + 'px Sans', '#000000');
- break;
- case 'pitchdrummatrix':
- myBlock.collapseText = new createjs.Text(_('drum'), fontSize + 'px Sans', '#000000');
- break;
- case 'rhythmruler':
- myBlock.collapseText = new createjs.Text(_('ruler'), fontSize + 'px Sans', '#000000');
- break;
- case 'pitchstaircase':
- myBlock.collapseText = new createjs.Text(_('stair'), fontSize + 'px Sans', '#000000');
- break;
- case 'tempo':
- myBlock.collapseText = new createjs.Text(_('tempo'), fontSize + 'px Sans', '#000000');
- case 'modewidget':
- myBlock.collapseText = new createjs.Text(_('mode'), fontSize + 'px Sans', '#000000');
- break;
- case 'pitchslider':
- myBlock.collapseText = new createjs.Text(_('slider'), fontSize + 'px Sans', '#000000');
- break;
- case 'drum':
- myBlock.collapseText = new createjs.Text(_('drum'), fontSize + 'px Sans', '#000000');
- break;
- }
- myBlock.collapseText.textAlign = 'left';
- myBlock.collapseText.textBaseline = 'alphabetic';
- myBlock.container.addChild(myBlock.collapseText);
- }
- myBlock._positionCollapseLabel(myBlock.protoblock.scale);
- myBlock.collapseText.visible = myBlock.collapsed;
-
- myBlock._ensureDecorationOnTop();
-
- myBlock.updateCache();
-
- myBlock.collapseContainer = new createjs.Container();
- myBlock.collapseContainer.snapToPixelEnabled = true;
-
- var image = new Image();
- image.onload = function () {
- myBlock.collapseBitmap = new createjs.Bitmap(image);
- myBlock.collapseBitmap.scaleX = myBlock.collapseBitmap.scaleY = myBlock.collapseBitmap.scale = myBlock.protoblock.scale / 2;
- myBlock.collapseContainer.addChild(myBlock.collapseBitmap);
- myBlock.collapseBitmap.visible = !myBlock.collapsed;
- finishCollapseButton(myBlock);
- };
-
- image.src = 'images/collapse.svg';
-
- finishCollapseButton = function (myBlock) {
- var image = new Image();
- image.onload = function () {
- myBlock.expandBitmap = new createjs.Bitmap(image);
- myBlock.expandBitmap.scaleX = myBlock.expandBitmap.scaleY = myBlock.expandBitmap.scale = myBlock.protoblock.scale / 2;
- myBlock.collapseContainer.addChild(myBlock.expandBitmap);
- myBlock.expandBitmap.visible = myBlock.collapsed;
-
- var bounds = myBlock.collapseContainer.getBounds();
- if (bounds) myBlock.collapseContainer.cache(bounds.x, bounds.y, bounds.width, bounds.height);
- myBlock.blocks.stage.addChild(myBlock.collapseContainer);
- if (postProcess !== null) {
- postProcess(myBlock);
- }
-
- myBlock.blocks.refreshCanvas();
- myBlock.blocks.cleanupAfterLoad(myBlock.name);
- };
-
- image.src = 'images/expand.svg';
- }
- };
-
- function __processCollapseBitmap(name, bitmap, myBlock) {
- myBlock.collapseBlockBitmap = bitmap;
- myBlock.collapseBlockBitmap.name = 'collapse_' + thisBlock;
- myBlock.container.addChild(myBlock.collapseBlockBitmap);
- myBlock.collapseBlockBitmap.visible = myBlock.collapsed;
- myBlock.blocks.refreshCanvas();
-
- var artwork = myBlock.collapseArtwork;
- _makeBitmap(artwork.replace(/fill_color/g, PALETTEHIGHLIGHTCOLORS[myBlock.protoblock.palette.name]).replace(/stroke_color/g, HIGHLIGHTSTROKECOLORS[myBlock.protoblock.palette.name]).replace('block_label', ''), '', __processHighlightCollapseBitmap, myBlock);
- };
-
- var artwork = this.collapseArtwork;
- _makeBitmap(artwork.replace(/fill_color/g, PALETTEFILLCOLORS[this.protoblock.palette.name]).replace(/stroke_color/g, PALETTESTROKECOLORS[this.protoblock.palette.name]).replace('block_label', ''), '', __processCollapseBitmap, this);
- };
-
- this.hide = function () {
- this.container.visible = false;
- if (this.collapseContainer !== null) {
- this.collapseContainer.visible = false;
- this.collapseText.visible = false;
- }
- };
-
- this.show = function () {
- if (!this.trash) {
- // If it is an action block or it is not collapsed then show it.
- if (!(COLLAPSABLES.indexOf(this.name) === -1 && this.collapsed)) {
- this.container.visible = true;
- if (this.collapseContainer !== null) {
- this.collapseContainer.visible = true;
- this.collapseText.visible = true;
- }
- }
- }
- };
-
- // Utility functions
- this.isValueBlock = function () {
- return this.protoblock.style === 'value';
- };
-
- this.isNoHitBlock = function () {
- return NOHIT.indexOf(this.name) !== -1;
- };
-
- this.isArgBlock = function () {
- return this.protoblock.style === 'value' || this.protoblock.style === 'arg';
- };
-
- this.isTwoArgBlock = function () {
- return this.protoblock.style === 'twoarg';
- };
-
- this.isTwoArgBooleanBlock = function () {
- return ['equal', 'greater', 'less'].indexOf(this.name) !== -1;
- };
-
- this.isClampBlock = function () {
- return this.protoblock.style === 'clamp' || this.isDoubleClampBlock() || this.isArgFlowClampBlock();
- };
-
- this.isArgFlowClampBlock = function () {
- return this.protoblock.style === 'argflowclamp';
- };
-
- this.isDoubleClampBlock = function () {
- return this.protoblock.style === 'doubleclamp';
- };
-
- this.isNoRunBlock = function () {
- return this.name === 'action';
- };
-
- this.isArgClamp = function () {
- return this.protoblock.style === 'argclamp' || this.protoblock.style === 'argclamparg';
- };
-
- this.isExpandableBlock = function () {
- return this.protoblock.expandable;
- };
-
- this.getBlockId = function () {
- // Generate a UID based on the block index into the blockList.
- var number = blockBlocks.blockList.indexOf(this);
- return '_' + number.toString();
- };
-
- this.removeChildBitmap = function (name) {
- for (var child = 0; child < this.container.getNumChildren(); child++) {
- if (this.container.children[child].name === name) {
- this.container.removeChild(this.container.children[child]);
- break;
- }
- }
- };
-
- this.loadThumbnail = function (imagePath) {
- // Load an image thumbnail onto block.
- var thisBlock = this.blocks.blockList.indexOf(this);
- var myBlock = this;
- if (this.blocks.blockList[thisBlock].value === null && imagePath === null) {
- return;
- }
- var image = new Image();
-
- image.onload = function () {
- // Before adding new artwork, remove any old artwork.
- myBlock.removeChildBitmap('media');
-
- var bitmap = new createjs.Bitmap(image);
- bitmap.name = 'media';
-
-
- var myContainer = new createjs.Container();
- myContainer.addChild(bitmap);
-
- // Resize the image to a reasonable maximum.
- var MAXWIDTH = 600;
- var MAXHEIGHT = 450;
- if (image.width > image.height) {
- if (image.width > MAXWIDTH) {
- bitmap.scaleX = bitmap.scaleY = bitmap.scale = MAXWIDTH / image.width;
- }
- } else {
- if (image.height > MAXHEIGHT) {
- bitmap.scaleX = bitmap.scaleY = bitmap.scale = MAXHEIGHT / image.height;
- }
- }
-
- var bounds = myContainer.getBounds();
- myContainer.cache(bounds.x, bounds.y, bounds.width, bounds.height);
- myBlock.value = myContainer.getCacheDataURL();
- myBlock.imageBitmap = bitmap;
-
- // Next, scale the bitmap for the thumbnail.
- myBlock._positionMedia(bitmap, bitmap.image.width, bitmap.image.height, myBlock.protoblock.scale);
- myBlock.container.addChild(bitmap);
- myBlock.updateCache();
- };
-
- if (imagePath === null) {
- image.src = this.value;
- } else {
- image.src = imagePath;
- }
- };
-
- this._doOpenMedia = function (thisBlock) {
- var fileChooser = docById('myOpenAll');
- var myBlock = this;
-
- readerAction = function (event) {
- window.scroll(0, 0);
-
- var reader = new FileReader();
- reader.onloadend = (function () {
- if (reader.result) {
- if (myBlock.name === 'media') {
- myBlock.value = reader.result;
- myBlock.loadThumbnail(null);
- return;
- }
- myBlock.value = [fileChooser.files[0].name, reader.result];
- myBlock.blocks.updateBlockText(thisBlock);
- }
- });
- if (myBlock.name === 'media') {
- reader.readAsDataURL(fileChooser.files[0]);
- }
- else {
- reader.readAsText(fileChooser.files[0]);
- }
- fileChooser.removeEventListener('change', readerAction);
- };
-
- fileChooser.addEventListener('change', readerAction, false);
- fileChooser.focus();
- fileChooser.click();
- window.scroll(0, 0)
- };
-
- this.collapseToggle = function () {
- // Find the blocks to collapse/expand
- var myBlock = this;
- var thisBlock = this.blocks.blockList.indexOf(this);
- this.blocks.findDragGroup(thisBlock);
-
- function __toggle() {
- var collapse = myBlock.collapsed;
- if (myBlock.collapseBitmap === null) {
- console.log('collapse bitmap not ready');
- return;
- }
- myBlock.collapsed = !collapse;
-
- // These are the buttons to collapse/expand the stack.
- myBlock.collapseBitmap.visible = collapse;
- myBlock.expandBitmap.visible = !collapse;
-
- // These are the collpase-state bitmaps.
- myBlock.collapseBlockBitmap.visible = !collapse;
- myBlock.highlightCollapseBlockBitmap.visible = false;
- myBlock.collapseText.visible = !collapse;
-
- if (collapse) {
- myBlock.bitmap.visible = true;
- } else {
- myBlock.bitmap.visible = false;
- myBlock.updateCache();
- }
- myBlock.highlightBitmap.visible = false;
-
- if (myBlock.name === 'action') {
- // Label the collapsed block with the action label
- if (myBlock.connections[1] !== null) {
- var text = myBlock.blocks.blockList[myBlock.connections[1]].value;
- if (text.length > 8) {
- text = text.substr(0, 7) + '...';
- }
- myBlock.collapseText.text = text;
- } else {
- myBlock.collapseText.text = '';
- }
- }
-
- // Make sure the text is on top.
- var z = myBlock.container.getNumChildren() - 1;
- myBlock.container.setChildIndex(myBlock.collapseText, z);
-
- // Set collapsed state of blocks in drag group.
- if (myBlock.blocks.dragGroup.length > 0) {
- for (var b = 1; b < myBlock.blocks.dragGroup.length; b++) {
- var blk = myBlock.blocks.dragGroup[b];
- myBlock.blocks.blockList[blk].collapsed = !collapse;
- myBlock.blocks.blockList[blk].container.visible = collapse;
- }
- }
-
- myBlock.collapseContainer.updateCache();
- myBlock.updateCache();
- }
-
- __toggle();
- };
-
- this._positionText = function (blockScale) {
- this.text.textBaseline = 'alphabetic';
- this.text.textAlign = 'right';
- var fontSize = 10 * blockScale;
- this.text.font = fontSize + 'px Sans';
- this.text.x = TEXTX * blockScale / 2.;
- this.text.y = TEXTY * blockScale / 2.;
-
- // Some special cases
- if (SPECIALINPUTS.indexOf(this.name) !== -1) {
- this.text.textAlign = 'center';
- this.text.x = VALUETEXTX * blockScale / 2.;
- } else if (this.protoblock.args === 0) {
- var bounds = this.container.getBounds();
- this.text.x = bounds.width - 25;
- } else {
- this.text.textAlign = 'left';
- if (this.docks[0][2] === 'booleanout') {
- this.text.y = this.docks[0][1];
- }
- }
-
- // Ensure text is on top.
- z = this.container.getNumChildren() - 1;
- this.container.setChildIndex(this.text, z);
- this.updateCache();
- };
-
- this._positionMedia = function (bitmap, width, height, blockScale) {
- if (width > height) {
- bitmap.scaleX = bitmap.scaleY = bitmap.scale = MEDIASAFEAREA[2] / width * blockScale / 2;
- } else {
- bitmap.scaleX = bitmap.scaleY = bitmap.scale = MEDIASAFEAREA[3] / height * blockScale / 2;
- }
- bitmap.x = (MEDIASAFEAREA[0] - 10) * blockScale / 2;
- bitmap.y = MEDIASAFEAREA[1] * blockScale / 2;
- };
-
- this._calculateCollapseHitArea = function () {
- var bounds = this.collapseContainer.getBounds();
- var hitArea = new createjs.Shape();
- var w2 = bounds.width;
- var h2 = bounds.height;
-
- hitArea.graphics.beginFill('#FFF').drawEllipse(-w2 / 2, -h2 / 2, w2, h2);
- hitArea.x = w2 / 2;
- hitArea.y = h2 / 2;
- this.collapseContainer.hitArea = hitArea;
- };
-
- this._positionCollapseLabel = function (blockScale) {
- this.collapseText.x = COLLAPSETEXTX * blockScale / 2;
- this.collapseText.y = COLLAPSETEXTY * blockScale / 2;
-
- // Ensure text is on top.
- z = this.container.getNumChildren() - 1;
- this.container.setChildIndex(this.collapseText, z);
- };
-
- this._positionCollapseContainer = function (blockScale) {
- this.collapseContainer.x = this.container.x + (COLLAPSEBUTTONXOFF * blockScale / 2);
- this.collapseContainer.y = this.container.y + (COLLAPSEBUTTONYOFF * blockScale / 2);
- };
-
- // These are the event handlers for collapsible blocks.
- this._loadCollapsibleEventHandlers = function () {
- var myBlock = this;
- var thisBlock = this.blocks.blockList.indexOf(this);
- this._calculateCollapseHitArea();
-
- this.collapseContainer.on('mouseover', function (event) {
- myBlock.blocks.highlight(thisBlock, true);
- myBlock.blocks.activeBlock = thisBlock;
- myBlock.blocks.refreshCanvas();
- });
-
- var moved = false;
- var locked = false;
- var mousedown = false;
- var offset = {x:0, y:0};
-
- function handleClick () {
- if (locked) {
- return;
- }
-
- locked = true;
-
- setTimeout(function () {
- locked = false;
- }, 500);
-
- hideDOMLabel();
- if (!moved) {
- myBlock.collapseToggle();
- }
- }
-
- this.collapseContainer.on('click', function (event) {
- handleClick();
- });
-
- this.collapseContainer.on('mousedown', function (event) {
- hideDOMLabel();
- // Always show the trash when there is a block selected.
- trashcan.show();
- moved = false;
- mousedown = true;
- var d = new Date();
- blocks.mouseDownTime = d.getTime();
- offset = {
- x: myBlock.collapseContainer.x - Math.round(event.stageX / blocks.blockScale),
- y: myBlock.collapseContainer.y - Math.round(event.stageY / blocks.blockScale)
- };
- });
-
- this.collapseContainer.on('pressup', function (event) {
- if (!mousedown) {
- return;
- }
-
- mousedown = false;
- if (moved) {
- myBlock._collapseOut(blocks, thisBlock, moved, event);
- moved = false;
- } else {
- var d = new Date();
- if ((d.getTime() - blocks.mouseDownTime) > 1000) {
- var d = new Date();
- blocks.mouseDownTime = d.getTime();
- handleClick();
- }
- }
- });
-
- this.collapseContainer.on('mouseout', function (event) {
- if (!mousedown) {
- return;
- }
- mousedown = false;
- if (moved) {
- myBlock._collapseOut(blocks, thisBlock, moved, event);
- moved = false;
- } else {
- // Maybe restrict to Android?
- var d = new Date();
- if ((d.getTime() - blocks.mouseDownTime) < 200) {
- var d = new Date();
- blocks.mouseDownTime = d.getTime();
- handleClick();
- }
- }
- });
-
- this.collapseContainer.on('pressmove', function (event) {
- if (!mousedown) {
- return;
- }
- moved = true;
- var oldX = myBlock.collapseContainer.x;
- var oldY = myBlock.collapseContainer.y;
- myBlock.collapseContainer.x = Math.round(event.stageX / blocks.blockScale) + offset.x;
- myBlock.collapseContainer.y = Math.round(event.stageY / blocks.blockScale) + offset.y;
- var dx = myBlock.collapseContainer.x - oldX;
- var dy = myBlock.collapseContainer.y - oldY;
- myBlock.container.x += dx;
- myBlock.container.y += dy;
-
- // If we are over the trash, warn the user.
- if (trashcan.overTrashcan(event.stageX / blocks.blockScale, event.stageY / blocks.blockScale)) {
- trashcan.startHighlightAnimation();
- } else {
- trashcan.stopHighlightAnimation();
- }
-
- myBlock.blocks.findDragGroup(thisBlock)
- if (myBlock.blocks.dragGroup.length > 0) {
- for (var b = 0; b < myBlock.blocks.dragGroup.length; b++) {
- var blk = myBlock.blocks.dragGroup[b];
- if (b !== 0) {
- myBlock.blocks.moveBlockRelative(blk, dx, dy);
- }
- }
- }
-
- myBlock.blocks.refreshCanvas();
- });
- };
-
- this._collapseOut = function (blocks, thisBlock, moved, event) {
- // Always hide the trash when there is no block selected.
-
- trashcan.hide();
- blocks.unhighlight(thisBlock);
- if (moved) {
- // Check if block is in the trash.
- if (trashcan.overTrashcan(event.stageX / blocks.blockScale, event.stageY / blocks.blockScale)) {
- if (trashcan.isVisible)
- blocks.sendStackToTrash(this);
- } else {
- // Otherwise, process move.
- blocks.blockMoved(thisBlock);
- }
- }
-
- if (blocks.activeBlock !== myBlock) {
- return;
- }
-
- blocks.unhighlight(null);
- blocks.activeBlock = null;
- blocks.refreshCanvas();
- };
-
- this._calculateBlockHitArea = function () {
- var hitArea = new createjs.Shape();
- var bounds = this.container.getBounds()
-
- if (bounds === null) {
- this._createCache();
- bounds = this.bounds;
- }
-
- // Since hitarea is concave, we only detect hits on top
- // section of block. Otherwise we would not be able to grab
- // blocks placed inside of clamps.
- if (this.isClampBlock() || this.isArgClamp()) {
- hitArea.graphics.beginFill('#FFF').drawRect(0, 0, bounds.width, STANDARDBLOCKHEIGHT);
- } else if (this.isNoHitBlock()) {
- // No hit area
- hitArea.graphics.beginFill('#FFF').drawRect(0, 0, 0, 0);
- } else {
- // Shrinking the height makes it easier to grab blocks below
- // in the stack.
- hitArea.graphics.beginFill('#FFF').drawRect(0, 0, bounds.width, bounds.height * 0.75);
- }
- this.container.hitArea = hitArea;
- };
-
- // These are the event handlers for block containers.
- this._loadEventHandlers = function () {
- var myBlock = this;
- var thisBlock = this.blocks.blockList.indexOf(this);
- var blocks = this.blocks;
-
- this._calculateBlockHitArea();
-
- this.container.on('mouseover', function (event) {
- blocks.highlight(thisBlock, true);
- blocks.activeBlock = thisBlock;
- blocks.refreshCanvas();
- });
-
- var haveClick = false;
- var moved = false;
- var locked = false;
- var getInput = window.hasMouse;
-
- this.container.on('click', function (event) {
- blocks.activeBlock = thisBlock;
- haveClick = true;
-
- if (locked) {
- return;
- }
-
- locked = true;
- setTimeout(function () {
- locked = false;
- }, 500);
-
- hideDOMLabel();
-
- if ((!window.hasMouse && getInput) || (window.hasMouse && !moved)) {
- if (blocks.selectingStack) {
- var topBlock = blocks.findTopBlock(thisBlock);
- blocks.selectedStack = topBlock;
- blocks.selectingStack = false;
- } else if (myBlock.name === 'media') {
- myBlock._doOpenMedia(thisBlock);
- } else if (myBlock.name === 'loadFile') {
- myBlock._doOpenMedia(thisBlock);
- } else if (SPECIALINPUTS.indexOf(myBlock.name) !== -1) {
- if (!myBlock.trash) {
- myBlock._changeLabel();
- }
- } else {
- if (!blocks.inLongPress) {
- var topBlock = blocks.findTopBlock(thisBlock);
- console.log('running from ' + blocks.blockList[topBlock].name);
- blocks.logo.runLogoCommands(topBlock);
- }
- }
- }
- });
-
- this.container.on('mousedown', function (event) {
- // Track time for detecting long pause...
- // but only for top block in stack.
- if (myBlock.connections[0] == null) {
- var d = new Date();
- blocks.mouseDownTime = d.getTime();
- blocks.longPressTimeout = setTimeout(function () {
- blocks.triggerLongPress(myBlock);
- }, LONGPRESSTIME);
- }
-
- // Always show the trash when there is a block selected,
- trashcan.show();
-
- // Raise entire stack to the top.
- blocks.raiseStackToTop(thisBlock);
-
- // And possibly the collapse button.
- if (myBlock.collapseContainer != null) {
- blocks.stage.setChildIndex(myBlock.collapseContainer, blocks.stage.getNumChildren() - 1);
- }
-
- moved = false;
- var offset = {
- x: myBlock.container.x - Math.round(event.stageX / blocks.blockScale),
- y: myBlock.container.y - Math.round(event.stageY / blocks.blockScale)
- };
-
- myBlock.container.on('mouseout', function (event) {
- if (haveClick) {
- return;
- }
-
- if (!blocks.inLongPress) {
- myBlock._mouseoutCallback(event, moved, haveClick, true);
- }
-
- moved = false;
- });
-
- myBlock.container.on('pressup', function (event) {
- if (haveClick) {
- return;
- }
-
- if (!blocks.inLongPress) {
- myBlock._mouseoutCallback(event, moved, haveClick, true);
- }
-
- moved = false;
- });
-
- var original = {x: event.stageX / blocks.blockScale, y: event.stageY / blocks.blockScale};
-
- myBlock.container.on('pressmove', function (event) {
- // FIXME: More voodoo
- event.nativeEvent.preventDefault();
-
- if (blocks.longPressTimeout != null) {
- clearTimeout(blocks.longPressTimeout);
- blocks.longPressTimeout = null;
- }
-
- if (!moved && myBlock.label != null) {
- myBlock.label.style.display = 'none';
- }
-
- if (window.hasMouse) {
- moved = true;
- } else {
- // Make it eaiser to select text on mobile.
- setTimeout(function () {
- moved = Math.abs((event.stageX / blocks.blockScale) - original.x) + Math.abs((event.stageY / blocks.blockScale) - original.y) > 20 && !window.hasMouse;
- getInput = !moved;
- }, 200);
- }
-
- var oldX = myBlock.container.x;
- var oldY = myBlock.container.y;
-
- var dx = Math.round(Math.round(event.stageX / blocks.blockScale) + offset.x - oldX);
- var dy = Math.round(Math.round(event.stageY / blocks.blockScale) + offset.y - oldY);
- var finalPos = oldY + dy;
-
- if (blocks.stage.y === 0 && finalPos < (45 * blocks.blockScale)) {
- dy += (45 * blocks.blockScale) - finalPos;
- }
-
- blocks.moveBlockRelative(thisBlock, dx, dy);
-
- // If we are over the trash, warn the user.
- if (trashcan.overTrashcan(event.stageX / blocks.blockScale, event.stageY / blocks.blockScale)) {
- trashcan.startHighlightAnimation();
- } else {
- trashcan.stopHighlightAnimation();
- }
-
- if (myBlock.isValueBlock() && myBlock.name !== 'media') {
- // Ensure text is on top
- var z = myBlock.container.getNumChildren() - 1;
- myBlock.container.setChildIndex(myBlock.text, z);
- } else if (myBlock.collapseContainer != null) {
- myBlock._positionCollapseContainer(myBlock.protoblock.scale);
- }
-
- // ...and move any connected blocks.
- blocks.findDragGroup(thisBlock)
- if (blocks.dragGroup.length > 0) {
- for (var b = 0; b < blocks.dragGroup.length; b++) {
- var blk = blocks.dragGroup[b];
- if (b !== 0) {
- blocks.moveBlockRelative(blk, dx, dy);
- }
- }
- }
-
- blocks.refreshCanvas();
- });
- });
-
- this.container.on('mouseout', function (event) {
- if (!blocks.inLongPress) {
- myBlock._mouseoutCallback(event, moved, haveClick, true);
- }
-
- moved = false;
- });
-
- this.container.on('pressup', function (event) {
- if (!blocks.inLongPress) {
- myBlock._mouseoutCallback(event, moved, haveClick, false);
- }
-
- moved = false;
- });
- };
-
- this._mouseoutCallback = function (event, moved, haveClick, hideDOM) {
- var thisBlock = this.blocks.blockList.indexOf(this);
-
- // Always hide the trash when there is no block selected.
- trashcan.hide();
-
- if (this.blocks.longPressTimeout != null) {
- clearTimeout(this.blocks.longPressTimeout);
- this.blocks.longPressTimeout = null;
- }
-
- if (moved) {
- // Check if block is in the trash.
- if (trashcan.overTrashcan(event.stageX / blocks.blockScale, event.stageY / blocks.blockScale)) {
- if (trashcan.isVisible) {
- blocks.sendStackToTrash(this);
- }
- } else {
- // Otherwise, process move.
- // Also, keep track of the time of the last move.
- var d = new Date();
- blocks.mouseDownTime = d.getTime();
- this.blocks.blockMoved(thisBlock);
-
- // Just in case the blocks are not properly docked after
- // the move (workaround for issue #38 -- Blocks fly
- // apart). Still need to get to the root cause.
- this.blocks.adjustDocks(this.blocks.blockList.indexOf(this), true);
- }
- } else if (SPECIALINPUTS.indexOf(this.name) !== -1 || ['media', 'loadFile'].indexOf(this.name) !== -1) {
- if (!haveClick) {
- // Simulate click on Android.
- var d = new Date();
- if ((d.getTime() - blocks.mouseDownTime) < 500) {
- if (!this.trash)
- {
- var d = new Date();
- blocks.mouseDownTime = d.getTime();
- if (this.name === 'media' || this.name === 'loadFile') {
- this._doOpenMedia(thisBlock);
- } else {
- this._changeLabel();
- }
- }
- }
- }
- }
-
- if (hideDOM) {
- // Did the mouse move out off the block? If so, hide the
- // label DOM element.
- if (this.bounds != null && (event.stageX / blocks.blockScale < this.container.x || event.stageX / blocks.blockScale > this.container.x + this.bounds.width || event.stageY / blocks.blockScale < this.container.y || event.stageY / blocks.blockScale > this.container.y + this.bounds.height)) {
- this._labelChanged();
- hideDOMLabel();
- this.blocks.unhighlight(null);
- this.blocks.refreshCanvas();
- } else if (this.blocks.activeBlock !== thisBlock) {
- // Are we in a different block altogether?
- hideDOMLabel();
- this.blocks.unhighlight(null);
- this.blocks.refreshCanvas();
- } else {
- // this.blocks.unhighlight(null);
- // this.blocks.refreshCanvas();
- }
-
- this.blocks.activeBlock = null;
- }
- };
-
- this._ensureDecorationOnTop = function () {
- // Find the turtle decoration and move it to the top.
- for (var child = 0; child < this.container.getNumChildren(); child++) {
- if (this.container.children[child].name === 'decoration') {
- // Drum block in collapsed state is less wide.
- if (this.name === 'drum') {
- var bounds = this.container.getBounds();
- if (this.collapsed) {
- var dx = 25 * this.protoblock.scale / 2;
- } else {
- var dx = 0;
- }
- for (var turtle = 0; turtle < this.blocks.turtles.turtleList.length; turtle++) {
- if (this.blocks.turtles.turtleList[turtle].startBlock === this) {
- this.blocks.turtles.turtleList[turtle].decorationBitmap.x = bounds.width - dx - 50 * this.protoblock.scale / 2;
- break;
- }
- }
- }
-
- this.container.setChildIndex(this.container.children[child], this.container.getNumChildren() - 1);
- break;
- }
- }
- };
-
- this._changeLabel = function () {
- var myBlock = this;
- var blocks = this.blocks;
- var x = this.container.x;
- var y = this.container.y;
-
- var canvasLeft = blocks.canvas.offsetLeft + 28 * blocks.blockScale;
- var canvasTop = blocks.canvas.offsetTop + 6 * blocks.blockScale;
-
- var movedStage = false;
- if (!window.hasMouse && blocks.stage.y + y > 75) {
- movedStage = true;
- var fromY = blocks.stage.y;
- blocks.stage.y = -y + 75;
- }
-
- // A place in the DOM to put modifiable labels (textareas).
- var labelValue = (this.label)?this.label.value:this.value;
- var labelElem = docById('labelDiv');
-
- if (this.name === 'text') {
- var type = 'text';
- labelElem.innerHTML = '<input id="textLabel" style="position: absolute; -webkit-user-select: text;-moz-user-select: text;-ms-user-select: text;" class="text" type="text" value="' + labelValue + '" />';
- labelElem.classList.add('hasKeyboard');
- this.label = docById('textLabel');
- } else if (this.name === 'solfege') {
- var type = 'solfege';
-
- var obj = splitSolfege(this.value);
- var selectednote = obj[0];
- var selectedattr = obj[1];
-
- // solfnotes_ is used in the interface for internationalization.
- //.TRANS: the note names must be separated by single spaces
- var solfnotes_ = _('ti la sol fa mi re do').split(' ');
-
- var labelHTML = '<select name="solfege" id="solfegeLabel" style="position: absolute; background-color: #88e20a; width: 100px;">'
- for (var i = 0; i < SOLFNOTES.length; i++) {
- if (selectednote === solfnotes_[i]) {
- labelHTML += '<option value="' + SOLFNOTES[i] + '" selected>' + solfnotes_[i] + '</option>';
- } else if (selectednote === SOLFNOTES[i]) {
- labelHTML += '<option value="' + SOLFNOTES[i] + '" selected>' + solfnotes_[i] + '</option>';
- } else {
- labelHTML += '<option value="' + SOLFNOTES[i] + '">' + solfnotes_[i] + '</option>';
- }
- }
-
- labelHTML += '</select>';
- if (selectedattr === '') {
- selectedattr = '♮';
- }
- labelHTML += '<select name="noteattr" id="noteattrLabel" style="position: absolute; background-color: #88e20a; width: 60px;">';
- for (var i = 0; i < SOLFATTRS.length; i++) {
- if (selectedattr === SOLFATTRS[i]) {
- labelHTML += '<option value="' + selectedattr + '" selected>' + selectedattr + '</option>';
- } else {
- labelHTML += '<option value="' + SOLFATTRS[i] + '">' + SOLFATTRS[i] + '</option>';
- }
- }
-
- labelHTML += '</select>';
- labelElem.innerHTML = labelHTML;
- this.label = docById('solfegeLabel');
- this.labelattr = docById('noteattrLabel');
- } else if (this.name === 'eastindiansolfege') {
- var type = 'solfege';
-
- var obj = splitSolfege(this.value);
- var selectednote = WESTERN2EISOLFEGENAMES[obj[0]];
- var selectedattr = obj[1];
-
- var eisolfnotes_ = ['ni', 'dha', 'pa', 'ma', 'ga', 're', 'sa'];
-
- var labelHTML = '<select name="solfege" id="solfegeLabel" style="position: absolute; background-color: #88e20a; width: 100px;">'
- for (var i = 0; i < SOLFNOTES.length; i++) {
- if (selectednote === eisolfnotes_[i]) {
- labelHTML += '<option value="' + SOLFNOTES[i] + '" selected>' + eisolfnotes_[i] + '</option>';
- } else if (selectednote === WESTERN2EISOLFEGENAMES[SOLFNOTES[i]]) {
- labelHTML += '<option value="' + SOLFNOTES[i] + '" selected>' + eisolfnotes_[i] + '</option>';
- } else {
- labelHTML += '<option value="' + SOLFNOTES[i] + '">' + eisolfnotes_[i] + '</option>';
- }
- }
-
- labelHTML += '</select>';
- if (selectedattr === '') {
- selectedattr = '♮';
- }
- labelHTML += '<select name="noteattr" id="noteattrLabel" style="position: absolute; background-color: #88e20a; width: 60px;">';
- for (var i = 0; i < SOLFATTRS.length; i++) {
- if (selectedattr === SOLFATTRS[i]) {
- labelHTML += '<option value="' + selectedattr + '" selected>' + selectedattr + '</option>';
- } else {
- labelHTML += '<option value="' + SOLFATTRS[i] + '">' + SOLFATTRS[i] + '</option>';
- }
- }
-
- labelHTML += '</select>';
- labelElem.innerHTML = labelHTML;
- this.label = docById('solfegeLabel');
- this.labelattr = docById('noteattrLabel');
- } else if (this.name === 'notename') {
- var type = 'notename';
- const NOTENOTES = ['B', 'A', 'G', 'F', 'E', 'D', 'C'];
- const NOTEATTRS = ['♯♯', '♯', '♮', '♭', '♭♭'];
- if (this.value != null) {
- var selectednote = this.value[0];
- if (this.value.length === 1) {
- var selectedattr = '♮';
- } else if (this.value.length === 2) {
- var selectedattr = this.value[1];
- } else {
- var selectedattr = this.value[1] + this.value[1];
- }
- } else {
- var selectednote = 'G';
- var selectedattr = '♮'
- }
-
- var labelHTML = '<select name="notename" id="notenameLabel" style="position: absolute; background-color: #88e20a; width: 60px;">'
- for (var i = 0; i < NOTENOTES.length; i++) {
- if (selectednote === NOTENOTES[i]) {
- labelHTML += '<option value="' + selectednote + '" selected>' + selectednote + '</option>';
- } else {
- labelHTML += '<option value="' + NOTENOTES[i] + '">' + NOTENOTES[i] + '</option>';
- }
- }
-
- labelHTML += '</select>';
- if (selectedattr === '') {
- selectedattr = '♮';
- }
- labelHTML += '<select name="noteattr" id="noteattrLabel" style="position: absolute; background-color: #88e20a; width: 60px;">';
-
- for (var i = 0; i < NOTEATTRS.length; i++) {
- if (selectedattr === NOTEATTRS[i]) {
- labelHTML += '<option value="' + selectedattr + '" selected>' + selectedattr + '</option>';
- } else {
- labelHTML += '<option value="' + NOTEATTRS[i] + '">' + NOTEATTRS[i] + '</option>';
- }
- }
-
- labelHTML += '</select>';
- labelElem.innerHTML = labelHTML;
- this.label = docById('notenameLabel');
- this.labelattr = docById('noteattrLabel');
- } else if (this.name === 'modename') {
- var type = 'modename';
- if (this.value != null) {
- var selectedmode = this.value[0];
- } else {
- var selectedmode = getModeName(DEFAULTMODE);
- }
-
- var labelHTML = '<select name="modename" id="modenameLabel" style="position: absolute; background-color: #88e20a; width: 60px;">'
- for (var i = 0; i < MODENAMES.length; i++) {
- if (MODENAMES[i][0].length === 0) {
- // work around some weird i18n bug
- labelHTML += '<option value="' + MODENAMES[i][1] + '">' + MODENAMES[i][1] + '</option>';
- } else if (selectednote === MODENAMES[i][0]) {
- labelHTML += '<option value="' + selectedmode + '" selected>' + selectedmode + '</option>';
- } else if (selectednote === MODENAMES[i][1]) {
- labelHTML += '<option value="' + selectedmode + '" selected>' + selectedmode + '</option>';
- } else {
- labelHTML += '<option value="' + MODENAMES[i][0] + '">' + MODENAMES[i][0] + '</option>';
- }
- }
-
- labelHTML += '</select>';
- labelElem.innerHTML = labelHTML;
- this.label = docById('modenameLabel');
- } else if (this.name === 'drumname') {
- var type = 'drumname';
- if (this.value != null) {
- var selecteddrum = getDrumName(this.value);
- } else {
- var selecteddrum = getDrumName(DEFAULTDRUM);
- }
-
- var labelHTML = '<select name="drumname" id="drumnameLabel" style="position: absolute; background-color: #00b0a4; width: 60px;">'
- for (var i = 0; i < DRUMNAMES.length; i++) {
- if (DRUMNAMES[i][0].length === 0) {
- // work around some weird i18n bug
- labelHTML += '<option value="' + DRUMNAMES[i][1] + '">' + DRUMNAMES[i][1] + '</option>';
- } else if (selecteddrum === DRUMNAMES[i][0]) {
- labelHTML += '<option value="' + selecteddrum + '" selected>' + selecteddrum + '</option>';
- } else if (selecteddrum === DRUMNAMES[i][1]) {
- labelHTML += '<option value="' + selecteddrum + '" selected>' + selecteddrum + '</option>';
- } else {
- labelHTML += '<option value="' + DRUMNAMES[i][0] + '">' + DRUMNAMES[i][0] + '</option>';
- }
- }
-
- labelHTML += '</select>';
- labelElem.innerHTML = labelHTML;
- this.label = docById('drumnameLabel');
- } else if (this.name === 'voicename') {
- var type = 'voicename';
- if (this.value != null) {
- var selectedvoice = getVoiceName(this.value);
- } else {
- var selectedvoice = getVoiceName(DEFAULTVOICE);
- }
-
- var labelHTML = '<select name="voicename" id="voicenameLabel" style="position: absolute; background-color: #00b0a4; width: 60px;">'
- for (var i = 0; i < VOICENAMES.length; i++) {
- if (VOICENAMES[i][0].length === 0) {
- // work around some weird i18n bug
- labelHTML += '<option value="' + VOICENAMES[i][1] + '">' + VOICENAMES[i][1] + '</option>';
- } else if (selectedvoice === VOICENAMES[i][0]) {
- labelHTML += '<option value="' + selectedvoice + '" selected>' + selectedvoice + '</option>';
- } else if (selectedvoice === VOICENAMES[i][1]) {
- labelHTML += '<option value="' + selectedvoice + '" selected>' + selectedvoice + '</option>';
- } else {
- labelHTML += '<option value="' + VOICENAMES[i][0] + '">' + VOICENAMES[i][0] + '</option>';
- }
- }
-
- labelHTML += '</select>';
- labelElem.innerHTML = labelHTML;
- this.label = docById('voicenameLabel');
- } else {
- var type = 'number';
- labelElem.innerHTML = '<input id="numberLabel" style="position: absolute; -webkit-user-select: text;-moz-user-select: text;-ms-user-select: text;" class="number" type="number" value="' + labelValue + '" />';
- labelElem.classList.add('hasKeyboard');
- this.label = docById('numberLabel');
- }
-
- var focused = false;
-
- var __blur = function (event) {
- // Not sure why the change in the input is not available
- // immediately in FireFox. We need a workaround if hardware
- // acceleration is enabled.
-
- if (!focused) {
- return;
- }
-
- myBlock._labelChanged();
-
- event.preventDefault();
-
- labelElem.classList.remove('hasKeyboard');
-
- window.scroll(0, 0);
- myBlock.label.removeEventListener('keypress', __keypress);
-
- if (movedStage) {
- blocks.stage.y = fromY;
- blocks.updateStage();
- }
- };
-
- if (this.name === 'text' || this.name === 'number') {
- this.label.addEventListener('blur', __blur);
- }
-
- var __keypress = function (event) {
- if ([13, 10, 9].indexOf(event.keyCode) !== -1) {
- __blur(event);
- }
- };
-
- this.label.addEventListener('keypress', __keypress);
-
- this.label.addEventListener('change', function () {
- myBlock._labelChanged();
- });
-
- if (this.labelattr != null) {
- this.labelattr.addEventListener('change', function () {
- myBlock._labelChanged();
- });
- }
-
- this.label.style.left = Math.round((x + blocks.stage.x) * blocks.blockScale + canvasLeft) + 'px';
- this.label.style.top = Math.round((y + blocks.stage.y) * blocks.blockScale + canvasTop) + 'px';
-
- // There may be a second select used for # and b.
- if (this.labelattr != null) {
- this.label.style.width = Math.round(60 * blocks.blockScale) * this.protoblock.scale / 2 + 'px';
- this.labelattr.style.left = Math.round((x + blocks.stage.x + 60) * blocks.blockScale + canvasLeft) + 'px';
- this.labelattr.style.top = Math.round((y + blocks.stage.y) * blocks.blockScale + canvasTop) + 'px';
- this.labelattr.style.width = Math.round(60 * blocks.blockScale) * this.protoblock.scale / 2 + 'px';
- this.labelattr.style.fontSize = Math.round(20 * blocks.blockScale * this.protoblock.scale / 2) + 'px';
- } else {
- this.label.style.width = Math.round(100 * blocks.blockScale) * this.protoblock.scale / 2 + 'px';
- }
-
- this.label.style.fontSize = Math.round(20 * blocks.blockScale * this.protoblock.scale / 2) + 'px';
- this.label.style.display = '';
- this.label.focus();
-
- // Firefox fix
- setTimeout(function () {
- myBlock.label.style.display = '';
- myBlock.label.focus();
- focused = true;
- }, 100);
- };
-
- this._labelChanged = function () {
- // Update the block values as they change in the DOM label.
- if (this == null || this.label == null) {
- // console.log('cannot find block associated with label change');
- this._label_lock = false;
- return;
- }
-
- this._label_lock = true;
-
- this.label.style.display = 'none';
- if (this.labelattr != null) {
- this.labelattr.style.display = 'none';
- }
-
- var oldValue = this.value;
-
- if (this.label.value === '') {
- this.label.value = '_';
- }
- var newValue = this.label.value;
-
- if (this.labelattr != null) {
- var attrValue = this.labelattr.value;
- switch (attrValue) {
- case '♯♯':
- case '♯':
- case '♭♭':
- case '♭':
- newValue = newValue + attrValue;
- break;
- default:
- break;
- }
- }
-
- if (oldValue === newValue) {
- // Nothing to do in this case.
- this._label_lock = false;
- return;
- }
-
- var c = this.connections[0];
- if (this.name === 'text' && c != null) {
- var cblock = this.blocks.blockList[c];
- switch (cblock.name) {
- case 'action':
- var that = this;
-
- setTimeout(function () {
- that.blocks.palettes.removeActionPrototype(oldValue);
- }, 1000);
-
- // Ensure new name is unique.
- var uniqueValue = this.blocks.findUniqueActionName(newValue);
- if (uniqueValue !== newValue) {
- newValue = uniqueValue;
- this.value = newValue;
- var label = this.value.toString();
- if (label.length > 8) {
- label = label.substr(0, 7) + '...';
- }
- this.text.text = label;
- this.label.value = newValue;
- this.updateCache();
- }
- break;
- default:
- break;
- }
- }
-
- // Update the block value and block text.
- if (this.name === 'number') {
- this.value = Number(newValue);
- if (isNaN(this.value)) {
- var thisBlock = this.blocks.blockList.indexOf(this);
- this.blocks.errorMsg(newValue + ': Not a number', thisBlock);
- this.blocks.refreshCanvas();
- this.value = oldValue;
- }
- } else {
- this.value = newValue;
- }
-
- if (this.name === 'solfege') {
- var obj = splitSolfege(this.value);
- var label = i18nSolfege(obj[0]);
- var attr = obj[1];
-
- if (attr !== '♮') {
- label += attr;
- }
- } else if (this.name === 'eastindiansolfege') {
- var obj = splitSolfege(this.value);
- var label = WESTERN2EISOLFEGENAMES[obj[0]];
- var attr = obj[1];
-
- if (attr !== '♮') {
- label += attr;
- }
- } else {
- var label = this.value.toString();
- }
-
- if (label.length > 8) {
- label = label.substr(0, 7) + '...';
- }
-
- this.text.text = label;
-
- // and hide the DOM textview...
- this.label.style.display = 'none';
-
- // Make sure text is on top.
- var z = this.container.getNumChildren() - 1;
- this.container.setChildIndex(this.text, z);
- this.updateCache();
-
- var c = this.connections[0];
- if (this.name === 'text' && c != null) {
- var cblock = this.blocks.blockList[c];
- switch (cblock.name) {
- case 'action':
- // If the label was the name of an action, update the
- // associated run this.blocks and the palette buttons
- // Rename both do <- name and nameddo blocks.
- this.blocks.renameDos(oldValue, newValue);
-
- if (oldValue === _('action')) {
- this.blocks.newNameddoBlock(newValue, this.blocks.actionHasReturn(c), this.blocks.actionHasArgs(c));
- this.blocks.setActionProtoVisiblity(false);
- }
-
- this.blocks.newNameddoBlock(newValue, this.blocks.actionHasReturn(c), this.blocks.actionHasArgs(c));
- var blockPalette = blocks.palettes.dict['action'];
- for (var blk = 0; blk < blockPalette.protoList.length; blk++) {
- var block = blockPalette.protoList[blk];
- if (oldValue === _('action')) {
- if (block.name === 'nameddo' && block.defaults.length === 0) {
- block.hidden = true;
- }
- }
- else {
- if (block.name === 'nameddo' && block.defaults[0] === oldValue) {
- blockPalette.remove(block,oldValue);
- }
- }
- }
-
- if (oldValue === _('action')) {
- this.blocks.newNameddoBlock(newValue, this.blocks.actionHasReturn(c), this.blocks.actionHasArgs(c));
- this.blocks.setActionProtoVisiblity(false);
- }
- this.blocks.renameNameddos(oldValue, newValue);
- this.blocks.palettes.hide();
- this.blocks.palettes.updatePalettes('action');
- this.blocks.palettes.show();
- break;
- case 'storein':
- // If the label was the name of a storein, update the
- // associated box this.blocks and the palette buttons.
- if (this.value !== 'box') {
- this.blocks.newStoreinBlock(this.value);
- this.blocks.newNamedboxBlock(this.value);
- }
- // Rename both box <- name and namedbox blocks.
- this.blocks.renameBoxes(oldValue, newValue);
- this.blocks.renameNamedboxes(oldValue, newValue);
- this.blocks.palettes.hide();
- this.blocks.palettes.updatePalettes('boxes');
- this.blocks.palettes.show();
- break;
- case 'setdrum':
- case 'playdrum':
- if (_THIS_IS_MUSIC_BLOCKS_) {
- if (newValue.slice(0, 4) === 'http') {
- this.blocks.logo.synth.loadSynth(newValue);
- }
- }
- break;
- default:
- break;
- }
- }
-
- // We are done changing the label, so unlock.
- this._label_lock = false;
-
- if (_THIS_IS_MUSIC_BLOCKS_) {
- // Load the synth for the selected drum.
- if (this.name === 'drumname') {
- this.blocks.logo.synth.loadSynth(getDrumSynthName(this.value));
- } else if (this.name === 'voicename') {
- this.blocks.logo.synth.loadSynth(getVoiceSynthName(this.value));
- }
- }
- };
-
- };
-
-
- function $() {
- var elements = new Array();
-
- for (var i = 0; i < arguments.length; i++) {
- var element = arguments[i];
- if (typeof element === 'string')
- element = docById(element);
- if (arguments.length === 1)
- return element;
- elements.push(element);
- }
- return elements;
- }
-
-
- window.hasMouse = false;
- // Mousemove is not emulated for touch
- document.addEventListener('mousemove', function (e) {
- window.hasMouse = true;
- });
-
-
- function _makeBitmap(data, name, callback, args) {
- // Async creation of bitmap from SVG data.
- // Works with Chrome, Safari, Firefox (untested on IE).
- var img = new Image();
- img.onload = function () {
- var bitmap = new createjs.Bitmap(img);
- callback(name, bitmap, args);
- };
-
- img.src = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(data)));
- };
|