not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

2015 lines
78 KiB

// 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)));
};