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.
 
 
 
 
 

4052 lines
160 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
// Minimum distance (squared) between two docks required before
// connecting them.
const MINIMUMDOCKDISTANCE = 400;
// Special value flags to uniquely identify these media blocks.
const CAMERAVALUE = '##__CAMERA__##';
const VIDEOVALUE = '##__VIDEO__##';
// Blocks holds the list of blocks and most of the block-associated
// methods, since most block manipulations are inter-block.
function Blocks () {
if (sugarizerCompatibility.isInsideSugarizer()) {
storage = sugarizerCompatibility.data;
} else {
storage = localStorage;
}
this.canvas = null;
this.stage = null;
this.refreshCanvas = null;
this.trashcan = null;
this.updateStage = null;
this.getStageScale = null;
// We keep a list of stacks in the trash.
this.trashStacks = [];
// We keep a dictionary for the proto blocks,
this.protoBlockDict = {}
// and a list of the blocks we create.
this.blockList = [];
// Track the time with mouse down.
this.mouseDownTime = 0;
this.longPressTimeout = null;
// "Copy stack" selects a stack for pasting. Are we selecting?
this.selectingStack = false;
// and what did we select?
this.selectedStack = null;
// and a copy of the selected stack for pasting.
this.selectedBlocksObj = null;
// If we somehow have a malformed block database (for example,
// from importing a corrupted datafile, we need to avoid infinite
// loops while crawling the block list.
this._loopCounter = 0;
this._sizeCounter = 0;
this._searchCounter = 0;
// We need a reference to the palettes.
this.palettes = null;
// Which block, if any, is highlighted?
this.highlightedBlock = null;
// Which block, if any, is active?
this.activeBlock = null;
// Are the blocks visible?
this.visible = true;
// The group of blocks being dragged or moved together
this.dragGroup = [];
// The blocks at the tops of stacks
this.stackList = [];
// The blocks that need expanding
this._expandablesList = [];
// Number of blocks to load
this._loadCounter = 0;
// Stacks of blocks that need adjusting as blocks are repositioned
// due to expanding and contracting or insertion into the flow.
this._adjustTheseDocks = [];
// Blocks that need collapsing after load.
this.blocksToCollapse = [];
// Arg blocks that need expanding after load.
this._checkTwoArgBlocks = [];
// Arg clamp blocks that need expanding after load.
this._checkArgClampBlocks = [];
// Clamp blocks that need expanding after load.
this._clampBlocksToCheck = [];
// We need to keep track of certain classes of blocks that exhibit
// different types of behavior.
// Blocks with parts that expand, e.g.,
this._expandableBlocks = [];
// Blocks that contain child flows of blocks
this.clampBlocks = [];
this.doubleExpandable = [];
this.argClampBlocks = [];
// Blocks that are used as arguments to other blocks
this.argBlocks = [];
// Blocks that return values
this.valueBlocks = [];
// Two-arg blocks with two arguments (expandable).
this.twoArgBlocks = [];
// Blocks that don't run when clicked.
this.noRunBlocks = [];
this._homeButtonContainers = [];
this.blockScale = DEFAULTBLOCKSCALE;
// We need to know if we are processing a copy or save stack command.
this.inLongPress = false;
// We stage deletion of prototype action blocks on the palette so
// as to avoid palette refresh race conditions.
this.deleteActionTimeout = 0;
this.setCanvas = function (canvas) {
this.canvas = canvas;
return this;
};
this.setStage = function (stage) {
this.stage = stage;
return this;
};
this.setRefreshCanvas = function (refreshCanvas) {
this.refreshCanvas = refreshCanvas;
return this;
};
this.setTrashcan = function (trashcan) {
this.trashcan = trashcan;
return this;
};
this.setUpdateStage = function (updateStage) {
this.updateStage = updateStage;
return this;
};
this.setGetStageScale = function (getStageScale) {
this.getStageScale = getStageScale;
return this;
};
// Change the scale of the blocks (and the protoblocks on the palette).
this.setBlockScale = function (scale) {
console.log('New block scale is ' + scale);
this.blockScale = scale;
// Regenerate all of the artwork at the new scale.
for (var blk = 0; blk < this.blockList.length; blk++) {
// if (!this.blockList[blk].trash) {
this.blockList[blk].resize(scale);
// }
}
this.findStacks();
for (var stack = 0; stack < this.stackList.length; stack++) {
// console.log('Adjust Docks: ' + this.blockList[this.stackList[stack]].name);
this.adjustDocks(this.stackList[stack], true);
}
// We reset the protoblock scale on the palettes, but don't
// modify the palettes themselves.
for (var palette in this.palettes.dict) {
for (var blk = 0; blk < this.palettes.dict[palette].protoList.length; blk++) {
this.palettes.dict[palette].protoList[blk].scale = scale;
}
}
};
// We need access to the msg block...
this.setMsgText = function (msgText) {
this.msgText = msgText;
};
// and the Error msg function.
this.setErrorMsg = function (errorMsg) {
this.errorMsg = errorMsg;
return this;
};
// We need access to the macro dictionary because we add to it.
this.setMacroDictionary = function (obj) {
this.macroDict = obj;
return this;
};
// We need access to the turtles list because we associate a
// turtle with each start block.
this.setTurtles = function (turtles) {
this.turtles = turtles;
return this;
};
// We need to access the "pseudo-Logo interpreter" when we click
// on blocks.
this.setLogo = function (logo) {
this.logo = logo;
return this;
};
// The scale of the graphics is determined by screen size.
this.setScale = function (scale) {
this.blockScale = scale;
return this;
};
// Toggle state of collapsible blocks.
this.toggleCollapsibles = function () {
for (var blk in this.blockList) {
var myBlock = this.blockList[blk];
if (COLLAPSABLES.indexOf(myBlock.name) !== -1 && !myBlock.trash) {
myBlock.collapseToggle();
}
}
};
// We need access to the go-home buttons and boundary.
this.setHomeContainers = function (containers, boundary) {
this._homeButtonContainers = containers;
this.boundary = boundary;
return this;
};
// set up copy/paste, dismiss, and copy-stack buttons
this.makeCopyPasteButtons = function (makeButton, updatePasteButton) {
var that = this;
this.updatePasteButton = updatePasteButton;
this.dismissButton = makeButton('cancel-button', '', 0, 0, 55, 0, this.stage);
this.dismissButton.visible = false;
this.saveStackButton = makeButton('save-blocks-button', _('Save stack'), 0, 0, 55, 0, this.stage);
this.saveStackButton.visible = false;
this.dismissButton.on('click', function (event) {
that.saveStackButton.visible = false;
that.dismissButton.visible = false;
that.inLongPress = false;
that.refreshCanvas();
});
this.saveStackButton.on('click', function (event) {
// Only invoked from action blocks.
var topBlock = that.findTopBlock(that.activeBlock);
that.inLongPress = false;
that.selectedStack = topBlock;
that.saveStackButton.visible = false;
that.dismissButton.visible = false;
that.saveStack();
that.refreshCanvas();
});
};
// Walk through all of the proto blocks in order to make lists of
// any blocks that need special treatment.
this.findBlockTypes = function () {
for (var proto in this.protoBlockDict) {
if (this.protoBlockDict[proto].expandable) {
this._expandableBlocks.push(this.protoBlockDict[proto].name);
}
if (this.protoBlockDict[proto].style === 'clamp') {
this.clampBlocks.push(this.protoBlockDict[proto].name);
}
if (this.protoBlockDict[proto].style === 'argclamp') {
this.argClampBlocks.push(this.protoBlockDict[proto].name);
}
if (this.protoBlockDict[proto].style === 'argflowclamp') {
this.clampBlocks.push(this.protoBlockDict[proto].name);
}
if (this.protoBlockDict[proto].style === 'argclamparg') {
this.argClampBlocks.push(this.protoBlockDict[proto].name);
this.argBlocks.push(this.protoBlockDict[proto].name);
}
if (this.protoBlockDict[proto].style === 'twoarg') {
this.twoArgBlocks.push(this.protoBlockDict[proto].name);
}
if (this.protoBlockDict[proto].style === 'arg') {
this.argBlocks.push(this.protoBlockDict[proto].name);
}
if (this.protoBlockDict[proto].style === 'value') {
this.argBlocks.push(this.protoBlockDict[proto].name);
this.valueBlocks.push(this.protoBlockDict[proto].name);
}
if (this.protoBlockDict[proto].style === 'doubleclamp') {
this.doubleExpandable.push(this.protoBlockDict[proto].name);
}
}
};
this._actionBlock = function (name) {
return ['do', 'doArg', 'calc', 'calcArg'].indexOf(name) !== -1;
};
this._namedActionBlock = function (name) {
return ['nameddo', 'nameddoArg', 'namedcalc', 'namedcalcArg'].indexOf(name) !== -1;
};
// Adjust the docking postions of all blocks in the current drag
// group.
this._adjustBlockPositions = function () {
if (this.dragGroup.length < 2) {
return;
}
// console.log('Adjust Docks: ' + this.blockList[this.dragGroup[0]].name);
this.adjustDocks(this.dragGroup[0], true)
};
// Adjust the size of the clamp in an expandable block when blocks
// are inserted into (or removed from) the child flow. This is a
// common operation for start and action blocks, but also for
// repeat, forever, if, etc.
this._adjustExpandableClampBlock = function () {
if (this._clampBlocksToCheck.length === 0) {
return;
}
var obj = this._clampBlocksToCheck.pop();
var blk = obj[0];
var clamp = obj[1];
var myBlock = this.blockList[blk];
if (myBlock.isArgFlowClampBlock()) {
// Make sure myBlock is a clamp block.
} else if (myBlock.isArgBlock() || myBlock.isTwoArgBlock()) {
return;
} else if (myBlock.isArgClamp()) {
// We handle ArgClamp blocks elsewhere.
this._adjustArgClampBlock([blk]);
return;
}
function clampAdjuster(blocks, blk, myBlock, clamp) {
// First we need to count up the number of (and size of) the
// blocks inside the clamp; The child flow is usually the
// second-to-last argument.
if (myBlock.isArgFlowClampBlock()) {
var c = 1; // 0: outie; and 1: child flow
} else if (clamp === 0) {
var c = myBlock.connections.length - 2;
} else { // e.g., Bottom clamp in if-then-else
var c = myBlock.connections.length - 3;
}
blocks._sizeCounter = 0;
var childFlowSize = 1;
if (c > 0 && myBlock.connections[c] != null) {
childFlowSize = Math.max(blocks._getStackSize(myBlock.connections[c]), 1);
}
// Adjust the clamp size to match the size of the child
// flow.
var plusMinus = childFlowSize - myBlock.clampCount[clamp];
if (plusMinus !== 0) {
if (!(childFlowSize === 0 && myBlock.clampCount[clamp] === 1)) {
myBlock.updateSlots(clamp, plusMinus);
}
}
// Recurse through the list.
setTimeout(function () {
if (blocks._clampBlocksToCheck.length > 0) {
blocks._adjustExpandableClampBlock();
}
}, 250);
};
clampAdjuster(this, blk, myBlock, clamp);
};
// Returns the block size.
this._getBlockSize = function (blk) {
var myBlock = this.blockList[blk];
return myBlock.size;
};
// Adjust the slot sizes of arg clamps.
this._adjustArgClampBlock = function (argBlocksToCheck) {
if (argBlocksToCheck.length === 0) {
return;
}
var blk = argBlocksToCheck.pop();
var myBlock = this.blockList[blk];
// Which connection do we start with?
if (['doArg', 'calcArg'].indexOf(myBlock.name) !== -1) {
var ci = 2;
} else {
var ci = 1;
}
// Get the current slot list.
var slotList = myBlock.argClampSlots;
var update = false;
// Determine the size of each argument.
for (var i = 0; i < slotList.length; i++) {
var c = myBlock.connections[ci + i];
var size = 1; // Minimum size
if (c != null) {
size = Math.max(this._getBlockSize(c), 1);
}
if (slotList[i] !== size) {
slotList[i] = size;
update = true;
}
}
if (update) {
myBlock.updateArgSlots(slotList);
}
};
// We also adjust the size of twoarg blocks. It is similar to how
// we adjust clamps, but enough different that it is in its own
// function.
this._adjustExpandableTwoArgBlock = function (argBlocksToCheck) {
if (argBlocksToCheck.length === 0) {
return;
}
var blk = argBlocksToCheck.pop();
var myBlock = this.blockList[blk];
// Determine the size of the first argument.
var c = myBlock.connections[1];
var firstArgumentSize = 1; // Minimum size
if (c != null) {
firstArgumentSize = Math.max(this._getBlockSize(c), 1);
}
// Expand/contract block by plusMinus.
var plusMinus = firstArgumentSize - myBlock.clampCount[0];
if (plusMinus !== 0) {
if (!(firstArgumentSize === 0)) {
myBlock.updateSlots(0, plusMinus);
}
}
};
this._addRemoveVspaceBlock = function (blk) {
var myBlock = this.blockList[blk];
var c = myBlock.connections[myBlock.connections.length - 2];
var secondArgumentSize = 1;
if (c != null) {
var secondArgumentSize = Math.max(this._getBlockSize(c), 1);
}
var that = this;
var vSpaceCount = howManyVSpaceBlocksBelow(blk);
if (secondArgumentSize < vSpaceCount + 1) {
// Remove a vspace block
var n = Math.abs(secondArgumentSize - vSpaceCount - 1);
for (var i = 0; i < n; i++) {
var lastConnection = myBlock.connections.length - 1;
var vspaceBlock = this.blockList[myBlock.connections[lastConnection]];
var nextBlockIndex = vspaceBlock.connections[1];
myBlock.connections[lastConnection] = nextBlockIndex;
if (nextBlockIndex != null) {
this.blockList[nextBlockIndex].connections[0] = blk;
}
vspaceBlock.connections = [null, null];
vspaceBlock.trash = true;
vspaceBlock.hide();
}
} else if (secondArgumentSize > vSpaceCount + 1) {
// Add vspace blocks
var n = secondArgumentSize - vSpaceCount - 1;
var nextBlock = last(myBlock.connections);
var thisBlock = myBlock;
var newPos = this.blockList.length;
var that = this;
function vspaceAdjuster(args) { // nextBlock, vspace, i, n
var thisBlock = args[0];
var nextBlock = args[1];
var vspace = args[2];
var i = args[3];
var n = args[4];
var vspaceBlock = that.blockList[vspace];
var lastDock = last(thisBlock.docks);
var dx = lastDock[0] - vspaceBlock.docks[0][0];
var dy = lastDock[1] - vspaceBlock.docks[0][1];
vspaceBlock.container.x = thisBlock.container.x + dx;
vspaceBlock.container.y = thisBlock.container.y + dy;
vspaceBlock.connections[0] = that.blockList.indexOf(thisBlock);
vspaceBlock.connections[1] = nextBlock;
thisBlock.connections[thisBlock.connections.length - 1] = vspace;
if (nextBlock) {
that.blockList[nextBlock].connections[0] = vspace;
}
if (i + 1 < n) {
var newPos = that.blockList.length;
thisBlock = last(that.blockList);
nextBlock = last(thisBlock.connections);
that._makeNewBlockWithConnections('vspace', newPos, [null, null], vspaceAdjuster, [thisBlock, nextBlock, newPos, i + 1, n]);
}
};
this._makeNewBlockWithConnections('vspace', newPos, [null, null], vspaceAdjuster, [thisBlock, nextBlock, newPos, 0, n]);
};
function howManyVSpaceBlocksBelow(blk) {
// Need to know how many vspace blocks are below the block
// we're checking against.
var nextBlock = last(that.blockList[blk].connections);
if (nextBlock && that.blockList[nextBlock].name === 'vspace') {
return 1 + howManyVSpaceBlocksBelow(nextBlock);
// Recurse until it isn't a vspace
}
return 0;
};
};
this._getStackSize = function (blk) {
// How many block units in this stack?
var size = 0;
this._sizeCounter += 1;
if (this._sizeCounter > this.blockList.length * 2) {
console.log('Infinite loop encountered detecting size of expandable block? ' + blk);
return size;
}
if (blk == null) {
return size;
}
var myBlock = this.blockList[blk];
if (myBlock == null) {
console.log('Something very broken in _getStackSize.');
}
if (myBlock.isClampBlock()) {
var c = myBlock.connections.length - 2;
var csize = 0;
if (c > 0) {
var cblk = myBlock.connections[c];
if (cblk != null) {
csize = this._getStackSize(cblk);
}
if (csize === 0) {
size = 1; // minimum of 1 slot in clamp
} else {
size = csize;
}
}
if (myBlock.isDoubleClampBlock()) {
var c = myBlock.connections.length - 3;
var csize = 0;
if (c > 0) {
var cblk = myBlock.connections[c];
if (cblk != null) {
csize = this._getStackSize(cblk);
}
if (csize === 0) {
size += 1; // minimum of 1 slot in clamp
} else {
size += csize;
}
}
}
// add top and bottom of clamp
size += myBlock.size;
} else {
size = myBlock.size;
}
// check on any connected block
if (!myBlock.isValueBlock()) {
var cblk = last(myBlock.connections);
if (cblk != null) {
size += this._getStackSize(cblk);
}
}
return size;
};
this.adjustDocks = function (blk, resetLoopCounter) {
// Give a block, adjust the dock positions
// of all of the blocks connected to it
var myBlock = this.blockList[blk];
// For when we come in from makeBlock
if (resetLoopCounter != null) {
this._loopCounter = 0;
}
// These checks are to test for malformed data. All blocks
// should have connections.
if (myBlock == null) {
console.log('Saw a null block: ' + blk);
return;
}
if (myBlock.connections == null) {
console.log('Saw a block with null connections: ' + blk);
return;
}
if (myBlock.connections.length === 0) {
console.log('Saw a block with [] connections: ' + blk);
return;
}
// Value blocks only have one dock.
if (myBlock.docks.length === 1) {
return;
}
this._loopCounter += 1;
if (this._loopCounter > this.blockList.length * 2) {
console.log('Infinite loop encountered while adjusting docks: ' + blk + ' ' + this.blockList);
return;
}
// Walk through each connection except the parent block; the
// exception being the parent block of boolean 2arg blocks,
// since the dock[0] position can change.
if (myBlock.isTwoArgBooleanBlock()) {
var start = 0;
} else {
var start = 1;
}
for (var c = start; c < myBlock.connections.length; c++) {
// Get the dock position for this connection.
var bdock = myBlock.docks[c];
// Find the connecting block.
var cblk = myBlock.connections[c];
// Nothing connected here so continue to the next connection.
if (cblk == null) {
continue;
}
// Another database integrety check.
if (this.blockList[cblk] == null) {
console.log('This is not good: we encountered a null block: ' + cblk);
continue;
}
// Find the dock position in the connected block.
var foundMatch = false;
for (var b = 0; b < this.blockList[cblk].connections.length; b++) {
if (this.blockList[cblk].connections[b] === blk) {
foundMatch = true;
break
}
}
// Yet another database integrety check.
if (!foundMatch) {
console.log('Did not find match for ' + myBlock.name + ' (' + blk + ') and ' + this.blockList[cblk].name + ' (' + cblk + ')');
console.log(myBlock.connections);
console.log(this.blockList[cblk].connections);
break;
}
var cdock = this.blockList[cblk].docks[b];
if (c > 0) {
// Move the connected block...
var dx = bdock[0] - cdock[0];
var dy = bdock[1] - cdock[1];
if (myBlock.container == null) {
console.log('Does this ever happen any more?')
} else {
var nx = myBlock.container.x + dx;
var ny = myBlock.container.y + dy;
}
this._moveBlock(cblk, nx, ny);
} else {
// or it's parent.
var dx = cdock[0] - bdock[0];
var dy = cdock[1] - bdock[1];
var nx = this.blockList[cblk].container.x + dx;
var ny = this.blockList[cblk].container.y + dy;
this._moveBlock(blk, nx, ny);
}
if (c > 0) {
// Recurse on connected blocks.
this.adjustDocks(cblk, true);
}
}
};
this.addDefaultBlock = function (parentblk, oldBlock, skipOldBlock) {
// Add an action name whenever the user removes the name from
// an action block. Add a box name whenever the user removes
// the name from a storein block. Add a Silence block
// whenever the user removes all the blocks from a Note block.
if (parentblk == null) {
return;
}
if (this.blockList[parentblk].name === 'action') {
var cblk = this.blockList[parentblk].connections[1];
if (cblk == null) {
var that = this;
postProcess = function (args) {
var parentblk = args[0];
var oldBlock = args[1];
var blk = that.blockList.length - 1;
that.blockList[parentblk].connections[1] = blk;
that.blockList[blk].value = that.findUniqueActionName(_('action'));
var label = that.blockList[blk].value;
if (label.length > 8) {
label = label.substr(0, 7) + '...';
}
that.blockList[blk].text.text = label;
that.blockList[blk].container.updateCache();
if (that.blockList[blk].value !== that.blockList[oldBlock].value) {
that.newNameddoBlock(that.blockList[blk].value, that.actionHasReturn(parentblk), that.actionHasArgs(parentblk));
var blockPalette = that.palettes.dict['action'];
for (var b = 0; b < blockPalette.protoList.length; b++) {
var protoblock = blockPalette.protoList[b];
if (protoblock.name === 'nameddo' && protoblock.defaults[0] === that.blockList[oldBlock].value) {
setTimeout(function () {
blockPalette.remove(protoblock, that.blockList[oldBlock].value);
delete that.protoBlockDict['myDo_' + that.blockList[oldBlock].value];
that.palettes.hide();
that.palettes.updatePalettes('action');
that.palettes.show();
}, 500);
break;
}
}
that.renameNameddos(that.blockList[oldBlock].value, that.blockList[blk].value);
if (skipOldBlock) {
that.renameDos(that.blockList[oldBlock].value, that.blockList[blk].value, oldBlock);
} else {
that.renameDos(that.blockList[oldBlock].value, that.blockList[blk].value);
}
}
that.adjustDocks(parentblk, true);
};
this._makeNewBlockWithConnections('text', 0, [parentblk], postProcess, [parentblk, oldBlock], false);
}
} else if (this.blockList[parentblk].name === 'storein') {
var cblk = this.blockList[parentblk].connections[1];
if (cblk == null) {
var that = this;
postProcess = function (args) {
var parentblk = args[0];
var oldBlock = args[1];
var blk = that.blockList.length - 1;
that.blockList[parentblk].connections[1] = blk;
that.blockList[blk].value = _('box');
var label = that.blockList[blk].value;
if (label.length > 8) {
label = label.substr(0, 7) + '...';
}
that.blockList[blk].text.text = label;
that.blockList[blk].container.updateCache();
that.adjustDocks(parentblk, true);
};
this._makeNewBlockWithConnections('text', 0, [parentblk], postProcess, [parentblk, oldBlock], false);
}
} else if (['newnote', 'osctime'].indexOf(this.blockList[parentblk].name) !== -1) {
var cblk = this.blockList[parentblk].connections[2];
if (cblk == null) {
var blkname = 'vspace';
var newVspaceBlock = this.makeBlock(blkname, '__NOARG__');
this.blockList[parentblk].connections[2] = newVspaceBlock;
this.blockList[newVspaceBlock].connections[0] = parentblk;
var blkname = 'rest2';
var newSilenceBlock = this.makeBlock(blkname, '__NOARG__');
this.blockList[newSilenceBlock].connections[0] = newVspaceBlock;
this.blockList[newSilenceBlock].connections[1] = null;
this.blockList[newVspaceBlock].connections[1] = newSilenceBlock;
} else if (this.blockList[cblk].name === 'vspace' && this.blockList[cblk].connections[1] == null) {
var blkname = 'rest2';
var newSilenceBlock = this.makeBlock(blkname, '__NOARG__');
this.blockList[newSilenceBlock].connections[0] = cblk;
this.blockList[newSilenceBlock].connections[1] = null;
this.blockList[cblk].connections[1] = newSilenceBlock;
}
}
};
this.deleteNextDefault = function (thisBlock) {
// Remove the Silence block from a Note block if another block
// is inserted above the silence block.
var thisBlockobj = this.blockList[thisBlock];
for (var i = 1; i < thisBlockobj.connections.length; i++) {
if (thisBlockobj.connections[i] && this.blockList[thisBlockobj.connections[i]].name === 'rest2') {
var silenceBlock = thisBlockobj.connections[i];
var silenceBlockobj = this.blockList[silenceBlock];
silenceBlockobj.hide();
silenceBlockobj.trash = true;
this.blockList[thisBlock].connections[i] = silenceBlockobj.connections[1];
break;
}
}
};
this.deletePreviousDefault = function (thisBlock) {
// Remove the Silence block from a Note block if another block
// is inserted just after the Silence block.
var thisBlockobj = this.blockList[thisBlock];
if (thisBlockobj && this.blockList[thisBlockobj.connections[0]] && this.blockList[thisBlockobj.connections[0]].name === 'rest2') {
var silenceBlock = thisBlockobj.connections[0];
var silenceBlockobj = this.blockList[silenceBlock];
silenceBlockobj.hide();
silenceBlockobj.trash = true;
for (var i = 0; i < this.blockList[silenceBlockobj.connections[0]].connections.length; i++) {
if (this.blockList[silenceBlockobj.connections[0]].connections[i] === silenceBlock) {
this.blockList[silenceBlockobj.connections[0]].connections[i] = thisBlock;
break;
}
}
thisBlockobj.connections[0] = silenceBlockobj.connections[0];
}
return thisBlockobj.connections[0];
};
this.blockMoved = function (thisBlock) {
// When a block is moved, we have lots of things to check:
// (0) Is it inside of a expandable block?
// Is it an arg inside an arg clamp?
// (1) Is it an arg block connected to a two-arg block?
// (2) Disconnect its connection[0];
// (3) Look for a new connection;
// Is it potentially an arg inside an arg clamp?
// (4) Is it an arg block connected to a 2-arg block?
// (5) Is it a pitch block being inserted or removed from
// a Note clamp? In which case, we may have to remove
// or add a silence block.
// (6) Is it the name of an action block? In which case we
// need to check to see if we need to rename it.
// (7) Is it the name of a storein block? In which case we
// need to check to see if we need to add a palette entry.
// (8) And we need to recheck if it inside of a expandable block.
// Find any containing expandable blocks.
this._clampBlocksToCheck = [];
if (thisBlock == null) {
console.log('blockMoved called with null block.');
return;
}
var blk = this._insideExpandableBlock(thisBlock);
var expandableLoopCounter = 0;
var parentblk = null;
if (blk != null) {
parentblk = blk;
}
var actionCheck = false;
while (blk != null) {
expandableLoopCounter += 1;
if (expandableLoopCounter > 2 * this.blockList.length) {
console.log('Infinite loop encountered checking for expandables?');
break;
}
this._clampBlocksToCheck.push([blk, 0]);
blk = this._insideExpandableBlock(blk);
}
this._checkTwoArgBlocks = [];
var checkArgBlocks = [];
var myBlock = this.blockList[thisBlock];
if (myBlock == null) {
console.log('null block found in blockMoved method: ' + thisBlock);
return;
}
var c = myBlock.connections[0];
if (c != null) {
var cBlock = this.blockList[c];
}
// If it is an arg block, where is it coming from?
if (myBlock.isArgBlock() && c != null) {
// We care about twoarg (2arg) blocks with
// connections to the first arg;
if (this.blockList[c].isTwoArgBlock() || this.blockList[c].isArgClamp()) {
if (cBlock.connections[1] === thisBlock) {
this._checkTwoArgBlocks.push(c);
}
} else if (this.blockList[c].isArgBlock() && this.blockList[c].isExpandableBlock() || this.blockList[c].isArgClamp()) {
if (cBlock.connections[1] === thisBlock) {
this._checkTwoArgBlocks.push(c);
}
}
}
// Disconnect from connection[0] (both sides of the connection).
if (c != null) {
// Disconnect both ends of the connection.
for (var i = 1; i < cBlock.connections.length; i++) {
if (cBlock.connections[i] === thisBlock) {
cBlock.connections[i] = null;
break;
}
}
myBlock.connections[0] = null;
this.raiseStackToTop(thisBlock);
}
// Look for a new connection.
var x1 = myBlock.container.x + myBlock.docks[0][0];
var y1 = myBlock.container.y + myBlock.docks[0][1];
// Find the nearest dock; if it is close enough, make the
// connection.
var newBlock = null;
var newConnection = null;
var min = (MINIMUMDOCKDISTANCE/DEFAULTBLOCKSCALE) * this.blockScale;
var blkType = myBlock.docks[0][2];
// Is the added block above the silence block or below?
var insertAfterDefault = true;
for (var b = 0; b < this.blockList.length; b++) {
// Don't connect to yourself.
if (b === thisBlock) {
continue;
}
// Don't connect to a collapsed block.
if (this.blockList[b].collapsed) {
continue;
}
// Don't connect to a block in the trash.
if (this.blockList[b].trash) {
continue;
}
for (var i = 1; i < this.blockList[b].connections.length; i++) {
// When converting from Python projects to JS format,
// sometimes extra null connections are added. We need
// to ignore them.
if (i === this.blockList[b].docks.length) {
break;
}
if ((i === this.blockList[b].connections.length - 1) && (this.blockList[b].connections[i] != null) && (this.blockList[this.blockList[b].connections[i]].isNoHitBlock())) {
// Don't break the connection between a block and
// a hidden block below it.
continue;
} else if ((['backward', 'status'].indexOf(this.blockList[b].name) !== -1) && (i === 1) && (this.blockList[b].connections[1] != null) && (this.blockList[this.blockList[b].connections[1]].isNoHitBlock())) {
// Don't break the connection betweem a backward
// block and a hidden block attached to its clamp.
continue;
} else if (this.blockList[b].name === 'action' && (i === 2) && (this.blockList[b].connections[2] != null) && (this.blockList[this.blockList[b].connections[2]].isNoHitBlock())) {
// Don't break the connection betweem an action
// block and a hidden block attached to its clamp.
continue;
}
// Look for available connections.
if (this._testConnectionType(blkType, this.blockList[b].docks[i][2])) {
var x2 = this.blockList[b].container.x + this.blockList[b].docks[i][0];
var y2 = this.blockList[b].container.y + this.blockList[b].docks[i][1];
var dist = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
if (dist < min) {
newBlock = b;
newConnection = i;
min = dist;
}
} else {
// TODO: bounce away from illegal connection?
// only if the distance was small
// console.log('cannot not connect these two block types');
}
}
}
if (newBlock != null) {
// We found a match.
myBlock.connections[0] = newBlock;
var connection = this.blockList[newBlock].connections[newConnection];
if (connection == null) {
if (this.blockList[newBlock].isArgClamp()) {
// If it is an arg clamp, we may have to adjust
// the slot size.
if ((this.blockList[newBlock].name === 'doArg' || this.blockList[newBlock].name === 'calcArg') && newConnection === 1) {
} else if (['doArg', 'nameddoArg'].indexOf(this.blockList[newBlock].name) !== -1 && newConnection === this.blockList[newBlock].connections.length - 1) {
} else {
// Get the size of the block we are inserting
// adding.
var size = this._getBlockSize(thisBlock);
// console.log('inserting block of size ' + size + ' to arg clamp ' + this.blockList[newBlock].name);
// Get the current slot list.
var slotList = this.blockList[newBlock].argClampSlots;
// Which slot is this block in?
if (['doArg', 'calcArg'].indexOf(this.blockList[newBlock].name) !== -1) {
var si = newConnection - 2;
} else {
var si = newConnection - 1;
}
if (slotList[si] !== size) {
slotList[si] = size;
this.blockList[newBlock].updateArgSlots(slotList);
}
}
}
} else {
// Three scenarios in which we may be overriding an
// existing connection:
// (1) if it is an argClamp, add a new slot below the
// current block;
// (2) if it is an arg block, replace it; or
// (3) if it is a flow block, insert it into the flow.
// A few corner cases: Whenever we connect (or disconnect)
// from an action block (c[1] arg), we need to ensure we have
// a unique action name; Whenever we connect to a newnote
// block (c[2] flow), we need to ensure we have either a silence
// block or a pitch block. And if we are connecting to a
// storein block, we need to ensure that there is a palette
// entry for the new namedbox.
insertAfterDefault = false;
if (this.blockList[newBlock].isArgClamp()) {
if ((this.blockList[newBlock].name === 'doArg' || this.blockList[newBlock].name === 'calcArg') && newConnection === 1) {
// If it is the action name then treat it like
// a standard replacement.
this.blockList[connection].connections[0] = null;
this.findDragGroup(connection);
for (var c = 0; c < this.dragGroup.length; c++) {
this.moveBlockRelative(this.dragGroup[c], 40, 40);
}
} else if (['doArg', 'nameddoArg'].indexOf(this.blockList[newBlock].name) !== -1 && newConnection === this.blockList[newBlock].connections.length - 1) {
// If it is the bottom of the flow, insert as
// usual.
var bottom = this.findBottomBlock(thisBlock);
this.blockList[connection].connections[0] = bottom;
this.blockList[bottom].connections[this.blockList[bottom].connections.length - 1] = connection;
} else {
// Move the block in the current slot down one
// slot (cascading and creating a new slot if
// necessary).
// Get the size of the block we are inserting adding.
var size = this._getBlockSize(thisBlock);
// Get the current slot list.
var slotList = this.blockList[newBlock].argClampSlots;
// Which slot is this block in?
var ci = this.blockList[newBlock].connections.indexOf(connection);
if (['doArg', 'calcArg'].indexOf(this.blockList[newBlock].name) !== -1) {
var si = ci - 2;
} else {
var si = ci - 1;
}
var emptySlot = null;
var emptyConnection = null;
// Is there an empty slot below?
for (var emptySlot = si; emptySlot < slotList.length; emptySlot++) {
if (this.blockList[newBlock].connections[ci + emptySlot - si] == null) {
emptyConnection = ci + emptySlot - si;
break;
}
}
if (emptyConnection == null) {
slotList.push(1);
this._newLocalArgBlock(slotList.length);
emptyConnection = ci + emptySlot - si;
this.blockList[newBlock].connections.push(null);
// Slide everything down one slot.
for (var i = slotList.length - 1; i > si + 1; i--) {
slotList[i] = slotList[i - 1];
}
for (var i = this.blockList[newBlock].connections.length - 1; i > ci + 1; i--) {
this.blockList[newBlock].connections[i] = this.blockList[newBlock].connections[i - 1];
}
}
// The new block is added below the current
// connection...
newConnection += 1;
// Set its slot size too.
slotList[si + 1] = size;
this.blockList[newBlock].updateArgSlots(slotList);
}
} else if (myBlock.isArgBlock()) {
this.blockList[connection].connections[0] = null;
this.findDragGroup(connection);
for (var c = 0; c < this.dragGroup.length; c++) {
this.moveBlockRelative(this.dragGroup[c], 40, 40);
}
// We need to rename the action stack.
if (this.blockList[newBlock].name === 'action') {
actionCheck = true;
if (myBlock.value !== this.blockList[connection].value) {
// Temporarily disconnect to ensure we don't
// find myBlock when looking for a unique name.
var c = myBlock.connections[0];
myBlock.connections[0] = null;
var name = this.findUniqueActionName(myBlock.value);
myBlock.connections[0] = c;
if (name !== myBlock.value) {
myBlock.value = name;
var label = name;
if (label.length > 8) {
label = label.substr(0, 7) + '...';
}
myBlock.text.text = label;
myBlock.container.updateCache();
}
var that = this;
setTimeout(function () {
// A previously disconnected name may have left
// an entry in the palette we need to remove.
var name = that.blockList[connection].value;
if (that.protoBlockDict['myDo_' + name] != undefined) {
delete that.protoBlockDict['myDo_' + name];
that.palettes.dict['action'].hideMenu(true);
}
that.newNameddoBlock(myBlock.value, that.actionHasReturn(newBlock), that.actionHasArgs(newBlock));
var blockPalette = that.palettes.dict['action'];
for (var b = 0; b < blockPalette.protoList.length; b++) {
var protoblock = blockPalette.protoList[b];
if (protoblock.name === 'nameddo' && protoblock.staticLabels[0] === that.blockList[connection].value) {
setTimeout(function () {
blockPalette.remove(protoblock, that.blockList[connection].value);
delete that.protoBlockDict['myDo_' + that.blockList[connection].value];
that.palettes.hide();
that.palettes.updatePalettes('action');
that.palettes.show();
}, 500);
break;
}
}
that.renameNameddos(that.blockList[connection].value, myBlock.value);
that.renameDos(that.blockList[connection].value, myBlock.value);
}, 750);
}
} else if (this.blockList[newBlock].name === 'storein') {
// We may need to add new storein and namedo
// blocks to the palette.
if (myBlock.value !== 'box') {
this.newStoreinBlock(myBlock.value);
this.newNamedboxBlock(myBlock.value);
var that = this;
setTimeout(function () {
that.palettes.hide();
that.palettes.updatePalettes('boxes');
that.palettes.show();
}, 500);
}
}
} else if (!this.blockList[thisBlock].isArgFlowClampBlock()) {
var bottom = this.findBottomBlock(thisBlock);
this.blockList[connection].connections[0] = bottom;
this.blockList[bottom].connections[this.blockList[bottom].connections.length - 1] = connection;
}
}
this.blockList[newBlock].connections[newConnection] = thisBlock;
// Remove the silence block (if it is present) after
// adding a new block inside of a note block.
if (this._insideNoteBlock(thisBlock) != null) {
// If blocks are inserted above the silence block.
if (insertAfterDefault) {
newBlock = this.deletePreviousDefault(thisBlock);
} else {
this.deleteNextDefault(bottom);
}
}
// If we attached a name to an action block, check to see
// if we need to rename it.
if (this.blockList[newBlock].name === 'action' && !actionCheck) {
// Is there already another action block with this name?
for (var b = 0; b < this.blockList.length; b++) {
if (b === newBlock) continue;
if (this.blockList[b].name === 'action') {
if (this.blockList[b].connections[1] != null) {
if (this.blockList[this.blockList[b].connections[1]].value === this.blockList[thisBlock].value) {
this.blockList[thisBlock].value = this.findUniqueActionName(this.blockList[thisBlock].value);
var label = this.blockList[thisBlock].value;
if (label.length > 8) {
label = label.substr(0, 7) + '...';
}
this.blockList[thisBlock].text.text = label;
this.blockList[thisBlock].container.updateCache();
this.newNameddoBlock(this.blockList[thisBlock].value, this.actionHasReturn(b), this.actionHasArgs(b));
this.setActionProtoVisiblity(false);
}
}
}
}
}
// console.log('Adjust Docks: ' + this.blockList[newBlock].name);
this.adjustDocks(newBlock, true);
// TODO: some graphical feedback re new connection?
}
// If it is an arg block, where is it coming from?
// FIXME: improve mechanism for testing block types.
if ((myBlock.isArgBlock() || myBlock.name === 'calcArg' || myBlock.name === 'namedcalcArg') && newBlock != null) {
// We care about twoarg blocks with connections to the
// first arg;
if (this.blockList[newBlock].isTwoArgBlock()) {
if (this.blockList[newBlock].connections[1] === thisBlock) {
if (this._checkTwoArgBlocks.indexOf(newBlock) === -1) {
this._checkTwoArgBlocks.push(newBlock);
}
}
} else if (this.blockList[newBlock].isArgBlock() && this.blockList[newBlock].isExpandableBlock()) {
if (this.blockList[newBlock].connections[1] === thisBlock) {
if (this._checkTwoArgBlocks.indexOf(newBlock) === -1) {
this._checkTwoArgBlocks.push(newBlock);
}
}
}
// We also care about the second-to-last connection to an
// arg block.
var n = this.blockList[newBlock].connections.length;
if (this.blockList[newBlock].connections[n - 2] === thisBlock) {
// Only flow blocks, but not ArgClamps
if (!this.blockList[newBlock].isArgClamp() && this.blockList[newBlock].docks[n - 1][2] === 'in') {
checkArgBlocks.push(newBlock);
}
}
}
this.addDefaultBlock(parentblk, thisBlock, actionCheck);
// Put block adjustments inside a slight delay to make the
// addition/substraction of vspace and changes of block shape
// appear less abrupt (and it can be a little racy).
var that = this;
setTimeout(function () {
// If we changed the contents of a arg block, we may need a vspace.
if (checkArgBlocks.length > 0) {
for (var i = 0; i < checkArgBlocks.length; i++) {
that._addRemoveVspaceBlock(checkArgBlocks[i]);
}
}
// If we changed the contents of a two-arg block, we need to
// adjust it.
if (that._checkTwoArgBlocks.length > 0) {
that._adjustExpandableTwoArgBlock(that._checkTwoArgBlocks);
}
// First, adjust the docks for any blocks that may have
// had a vspace added.
for (var i = 0; i < checkArgBlocks.length; i++) {
// console.log('Adjust Docks: ' + this.blockList[checkArgBlocks[i]].name);
that.adjustDocks(checkArgBlocks[i], true);
}
// Next, recheck if the connection is inside of a
// expandable block.
var blk = that._insideExpandableBlock(thisBlock);
var expandableLoopCounter = 0;
while (blk != null) {
// Extra check for malformed data.
expandableLoopCounter += 1;
if (expandableLoopCounter > 2 * that.blockList.length) {
console.log('Infinite loop checking for expandables?');
console.log(that.blockList);
break;
}
if (that.blockList[blk].name === 'ifthenelse') {
that._clampBlocksToCheck.push([blk, 0]);
that._clampBlocksToCheck.push([blk, 1]);
} else {
that._clampBlocksToCheck.push([blk, 0]);
}
blk = that._insideExpandableBlock(blk);
}
that._adjustExpandableClampBlock();
that.refreshCanvas();
}, 250);
};
this._testConnectionType = function (type1, type2) {
// Can these two blocks dock?
if (type1 === 'in' && type2 === 'out') {
return true;
}
if (type1 === 'out' && type2 === 'in') {
return true;
}
if (type1 === 'numberin' && ['numberout', 'anyout'].indexOf(type2) !== -1) {
return true;
}
if (['numberout', 'anyout'].indexOf(type1) !== -1 && type2 === 'numberin') {
return true;
}
if (type1 === 'textin' && ['textout', 'anyout'].indexOf(type2) !== -1) {
return true;
}
if (['textout', 'anyout'].indexOf(type1) !== -1 && type2 === 'textin') {
return true;
}
if (type1 === 'booleanout' && type2 === 'booleanin') {
return true;
}
if (type1 === 'booleanin' && type2 === 'booleanout') {
return true;
}
if (type1 === 'mediain' && type2 === 'mediaout') {
return true;
}
if (type1 === 'mediaout' && type2 === 'mediain') {
return true;
}
if (type1 === 'mediain' && type2 === 'textout') {
return true;
}
if (type2 === 'mediain' && type1 === 'textout') {
return true;
}
if (type1 === 'filein' && type2 === 'fileout') {
return true;
}
if (type1 === 'fileout' && type2 === 'filein') {
return true;
}
if (type1 === 'solfegein' && ['anyout', 'solfegeout', 'textout', 'noteout', 'numberout'].indexOf(type2) !== -1) {
return true;
}
if (type2 === 'solfegein' && ['anyout', 'solfegeout', 'textout', 'noteout', 'numberout'].indexOf(type1) !== -1) {
return true;
}
if (type1 === 'notein' && ['solfegeout', 'textout', 'noteout'].indexOf(type2) !== -1) {
return true;
}
if (type2 === 'notein' && ['solfegeout', 'textout', 'noteout'].indexOf(type1) !== -1) {
return true;
}
if (type1 === 'anyin' && ['textout', 'mediaout', 'numberout', 'anyout', 'fileout', 'solfegeout', 'noteout'].indexOf(type2) !== -1) {
return true;
}
if (type2 === 'anyin' && ['textout', 'mediaout', 'numberout', 'anyout', 'fileout', 'solfegeout', 'noteout'].indexOf(type1) !== -1) {
return true;
}
return false;
};
this.updateBlockPositions = function () {
// Create the block image if it doesn't yet exist.
for (var blk = 0; blk < this.blockList.length; blk++) {
this._moveBlock(blk, this.blockList[blk].container.x, this.blockList[blk].container.y);
}
};
this.bringToTop = function () {
// Move all the blocks to the top layer of the stage
this._adjustTheseStacks = [];
for (var blk in this.blockList) {
var myBlock = this.blockList[blk];
/*
this.stage.removeChild(myBlock.container);
this.stage.addChild(myBlock.container);
if (myBlock.collapseContainer != null) {
this.stage.removeChild(myBlock.collapseContainer);
this.stage.addChild(myBlock.collapseContainer);
}
*/
if (myBlock.connections[0] == null) {
this._adjustTheseStacks.push(blk);
}
}
for (var blk = 0; blk < this._adjustTheseStacks.length; blk++) {
// console.log('Adjust Stack: ' + this.blockList[this._adjustTheseStacks[blk]].name);
this.raiseStackToTop(this._adjustTheseStacks[blk]);
}
this.refreshCanvas();
};
this.checkBounds = function () {
var onScreen = true;
for (var blk = 0; blk < this.blockList.length; blk++) {
if (this.blockList[blk].connections[0] == null) {
if (this.blockList[blk].offScreen(this.boundary)) {
this._homeButtonContainers[0].visible = true;
this._homeButtonContainers[1].visible = false;
this.boundary.show();
onScreen = false;
break;
}
}
}
if (onScreen) {
this._homeButtonContainers[0].visible = false;
this._homeButtonContainers[1].visible = true;
this.boundary.hide();
}
};
this._moveBlock = function (blk, x, y) {
// Move a block (and its label) to x, y.
var myBlock = this.blockList[blk];
if (myBlock.container != null) {
myBlock.container.x = x;
myBlock.container.y = y;
if (myBlock.collapseContainer != null) {
myBlock.collapseContainer.x = x + COLLAPSEBUTTONXOFF * (this.blockList[blk].protoblock.scale / 2);
myBlock.collapseContainer.y = y + COLLAPSEBUTTONYOFF * (this.blockList[blk].protoblock.scale / 2);
}
this.checkBounds();
} else {
console.log('No container yet for block ' + myBlock.name);
}
};
this.moveBlockRelative = function (blk, dx, dy) {
// Move a block (and its label) by dx, dy.
if (this.inLongPress) {
this.saveStackButton.visible = false;
this.dismissButton.visible = false;
this.inLongPress = false;
}
var myBlock = this.blockList[blk];
if (myBlock.container != null) {
myBlock.container.x += dx;
myBlock.container.y += dy;
if (myBlock.collapseContainer != null) {
myBlock.collapseContainer.x += dx;
myBlock.collapseContainer.y += dy;
}
this.checkBounds();
} else {
console.log('No container yet for block ' + myBlock.name);
}
};
this.moveStackRelative = function (blk, dx, dy) {
this.findDragGroup(blk)
if (this.dragGroup.length > 0) {
for (var b = 0; b < this.dragGroup.length; b++) {
this.moveBlockRelative(this.dragGroup[b], dx, dy);
}
}
};
this.updateBlockText = function (blk) {
// When we create new blocks, we may not have assigned the
// value yet.
var myBlock = this.blockList[blk];
var maxLength = 8;
if (myBlock.text == null) {
return;
}
if (myBlock.name === 'loadFile') {
try {
var label = myBlock.value[0].toString();
} catch (e) {
var label = _('open file');
}
maxLength = 10;
} else if (myBlock.name === 'solfege') {
var obj = splitSolfege(myBlock.value);
var label = i18nSolfege(obj[0]);
var attr = obj[1];
if (attr !== '♮') {
label += attr;
}
} else if (myBlock.name === 'eastindiansolfege') {
var obj = splitSolfege(myBlock.value);
var label = WESTERN2EISOLFEGENAMES[obj[0]];
var attr = obj[1];
if (attr !== '♮') {
label += attr;
}
} else {
var label = myBlock.value.toString();
}
if (label.length > maxLength) {
label = label.substr(0, maxLength - 1) + '...';
}
myBlock.text.text = label;
// Make sure text is on top.
var z = myBlock.container.getNumChildren() - 1;
myBlock.container.setChildIndex(myBlock.text, z);
if (myBlock.loadComplete) {
myBlock.container.updateCache();
} else {
console.log('Load not yet complete for (' + blk + ') ' + myBlock.name);
}
};
this.findTopBlock = function (blk) {
// Find the top block in a stack.
if (blk == null) {
return null;
}
var myBlock = this.blockList[blk];
if (myBlock.connections == null) {
return blk;
}
if (myBlock.connections.length === 0) {
return blk;
}
// Test for corrupted-connection scenario.
if (myBlock.connections.length > 1 && myBlock.connections[0] != null && myBlock.connections[0] === last(myBlock.connections)) {
console.log('WARNING: CORRUPTED BLOCK DATA. Block ' + myBlock.name + ' (' + blk + ') is connected to the same block ' + this.blockList[myBlock.connections[0]].name + ' (' + myBlock.connections[0] + ') twice.');
return blk;
}
var topBlockLoop = 0;
while (myBlock.connections[0] != null) {
topBlockLoop += 1;
if (topBlockLoop > 2 * this.blockList.length) {
// Could happen if the block data is malformed.
console.log('infinite loop finding topBlock?');
console.log(this.blockList.indexOf(myBlock) + ' ' + myBlock.name);
break;
}
blk = myBlock.connections[0];
myBlock = this.blockList[blk];
}
return blk;
};
this.sameGeneration = function (firstBlk, childBlk) {
if (firstBlk == null || childBlk == null) {
return false;
}
if (firstBlk === childBlk) {
return true;
}
var myBlock = this.blockList[firstBlk];
if (myBlock.connections == null) {
return false;
}
if (myBlock.connections.length === 0) {
return false;
}
var bottomBlockLoop = 0;
while (last(myBlock.connections) != null) {
bottomBlockLoop += 1;
if (bottomBlockLoop > 2 * this.blockList.length) {
// Could happen if the block data is malformed.
console.log('infinite loop finding bottomBlock?');
break;
}
blk = last(myBlock.connections);
myBlock = this.blockList[blk];
if (blk === childBlk) {
return true;
}
}
return false;
}
this.findBottomBlock = function (blk) {
// Find the bottom block in a stack.
if (blk == null) {
return null;
}
var myBlock = this.blockList[blk];
if (myBlock.connections == null) {
return blk;
}
if (myBlock.connections.length === 0) {
return blk;
}
var bottomBlockLoop = 0;
while (last(myBlock.connections) != null) {
bottomBlockLoop += 1;
if (bottomBlockLoop > 2 * this.blockList.length) {
// Could happen if the block data is malformed.
console.log('infinite loop finding bottomBlock?');
break;
}
blk = last(myBlock.connections);
myBlock = this.blockList[blk];
}
return blk;
};
this.findStacks = function () {
// Find any blocks with null in the first connection.
this.stackList = [];
for (var i = 0; i < this.blockList.length; i++) {
if (this.blockList[i].connections[0] == null) {
this.stackList.push(i)
}
}
};
this._findClamps = function () {
// Find any clamp blocks.
this._expandablesList = [];
this.findStacks(); // We start by finding the stacks
for (var i = 0; i < this.stackList.length; i++) {
this._searchCounter = 0;
this._searchForExpandables(this.stackList[i]);
}
this._searchForArgFlow();
};
this._findTwoArgs = function () {
// Find any expandable arg blocks.
this._expandablesList = [];
for (var i = 0; i < this.blockList.length; i++) {
if (this.blockList[i].isArgBlock() && this.blockList[i].isExpandableBlock()) {
this._expandablesList.push(i);
} else if (this.blockList[i].isTwoArgBlock()) {
this._expandablesList.push(i);
}
}
};
this._searchForArgFlow = function () {
for (var blk = 0; blk < this.blockList.length; blk++) {
if (this.blockList[blk].isArgFlowClampBlock()) {
this._expandablesList.push(blk);
}
}
};
this._searchForExpandables = function (blk) {
// Find the expandable blocks below blk in a stack.
while (blk != null && this.blockList[blk] != null && !this.blockList[blk].isValueBlock()) {
// More checks for malformed or corrupted block data.
this._searchCounter += 1;
if (this._searchCounter > 2 * this.blockList.length) {
console.log('infinite loop searching for Expandables? ' + this._searchCounter);
console.log(blk + ' ' + this.blockList[blk].name);
break;
}
if (this.blockList[blk].isClampBlock()) {
this._expandablesList.push(blk);
var c = this.blockList[blk].connections.length - 2;
this._searchForExpandables(this.blockList[blk].connections[c]);
if (this.blockList[blk].name === 'ifthenelse') {
// search top clamp too
var c = 2;
this._searchForExpandables(this.blockList[blk].connections[c]);
}
} else if (this.blockList[blk].isArgClamp()) {
this._expandablesList.push(blk);
}
if (this.blockList[blk].connections.length > 1) {
blk = last(this.blockList[blk].connections);
} else {
// A value block only connects back to its parent, so
// end the search here.
blk = null;
}
}
};
this._expandTwoArgs = function () {
// Expand expandable 2-arg blocks as needed.
this._findTwoArgs();
this._adjustExpandableTwoArgBlock(this._expandablesList);
this.refreshCanvas();
};
this._expandClamps = function () {
// Expand expandable clamp blocks as needed.
this._findClamps();
this._clampBlocksToCheck = [];
for (var i = 0; i < this._expandablesList.length; i++) {
if (this.blockList[this._expandablesList[i]].name === 'ifthenelse') {
this._clampBlocksToCheck.push([this._expandablesList[i], 0]);
this._clampBlocksToCheck.push([this._expandablesList[i], 1]);
} else {
this._clampBlocksToCheck.push([this._expandablesList[i], 0]);
}
}
this._adjustExpandableClampBlock();
this.refreshCanvas();
};
this.changeDisabledStatus = function (name, flag) {
// Some blocks, e.g., sensor blocks for Butia, change their
// appearance depending upon if they have been enabled or
// disabled.
for (var blk in this.blockList) {
var myBlock = this.blockList[blk];
if (myBlock.name === name) {
myBlock.protoblock.disabled = flag;
myBlock.regenerateArtwork(false);
}
}
};
this.unhighlightAll = function () {
for (var blk in this.blockList) {
this.unhighlight(blk);
}
};
this.unhighlight = function (blk) {
if (!this.visible) {
return;
}
if (blk != null) {
var thisBlock = blk;
} else {
var thisBlock = this.highlightedBlock;
}
if (thisBlock != null) {
this.blockList[thisBlock].unhighlight();
}
if (this.highlightedBlock = thisBlock) {
this.highlightedBlock = null;
}
};
this.highlight = function (blk, unhighlight) {
if (!this.visible) {
return;
}
if (blk != null) {
if (unhighlight) {
this.unhighlight(null);
}
this.blockList[blk].highlight();
this.highlightedBlock = blk;
}
};
this.hide = function () {
for (var blk in this.blockList) {
this.blockList[blk].hide();
}
this.visible = false;
};
this.show = function () {
for (var blk in this.blockList) {
this.blockList[blk].show();
}
this.visible = true;
};
this._makeNewBlockWithConnections = function (name, blockOffset, connections, postProcess, postProcessArg, collapsed) {
if (typeof(collapsed) === 'undefined') {
collapsed = false
}
myBlock = this.makeNewBlock(name, postProcess, postProcessArg);
if (myBlock == null) {
console.log('could not make block ' + name);
return;
}
// myBlock.collapsed = !collapsed;
for (var c = 0; c < connections.length; c++) {
if (c === myBlock.docks.length) {
break;
}
if (connections[c] == null) {
myBlock.connections.push(null);
} else {
myBlock.connections.push(connections[c] + blockOffset);
}
}
};
this.makeNewBlock = function (name, postProcess, postProcessArg) {
// Create a new block
if (!name in this.protoBlockDict) {
// Should never happen: nop blocks should be substituted
console.log('makeNewBlock: no prototype for ' + name);
return null;
}
if (this.protoBlockDict[name] == null) {
// Should never happen
console.log('makeNewBlock: no prototype for ' + name);
return null;
}
// Deprecated
// If we drag in a synth block, we need to load the synth.
if (['sine', 'sawtooth', 'triangle', 'square'].indexOf(name) !== -1) {
if (_THIS_IS_MUSIC_BLOCKS_) {
this.logo.synth.loadSynth(name);
}
}
if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(name) !== -1) {
this.blockList.push(new Block(this.protoBlockDict[name], this, postProcessArg[1]));
} else if (name === 'namedarg') {
this.blockList.push(new Block(this.protoBlockDict[name], this, 'arg ' + postProcessArg[1]));
} else {
this.blockList.push(new Block(this.protoBlockDict[name], this));
}
if (last(this.blockList) == null) {
// Should never happen
console.log('failed to make protoblock for ' + name);
return null;
}
// We copy the dock because expandable blocks modify it.
var myBlock = last(this.blockList);
myBlock.copySize();
// We may need to do some postProcessing to the block
myBlock.postProcess = postProcess;
myBlock.postProcessArg = postProcessArg;
// We need a container for the block graphics.
myBlock.container = new createjs.Container();
this.stage.addChild(myBlock.container);
myBlock.container.snapToPixelEnabled = true;
myBlock.container.x = 0;
myBlock.container.y = 0;
// and we need to load the images into the container.
myBlock.imageLoad();
return myBlock;
};
this.makeBlock = function (name, arg) {
// Make a new block from a proto block.
// Called from palettes.
if (name === 'text') {
console.log('makeBlock ' + name + ' ' + arg);
}
var postProcess = null;
var postProcessArg = null;
var that = this;
var thisBlock = this.blockList.length;
if (name === 'start') {
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = that.turtles.turtleList.length;
that.turtles.addTurtle(that.blockList[thisBlock]);
};
postProcessArg = thisBlock;
} else if (name === 'drum') {
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = that.turtles.turtleList.length;
that.turtles.addDrum(that.blockList[thisBlock]);
};
postProcessArg = thisBlock;
} else if (name === 'text') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = value;
that.blockList[thisBlock].container.updateCache();
};
postProcessArg = [thisBlock, _('text')];
} else if (name === 'solfege') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = value;
that.blockList[thisBlock].container.updateCache();
};
postProcessArg = [thisBlock, 'sol'];
} else if (name === 'eastindiansolfege') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = WESTERN2EISOLFEGENAMES[value];
that.blockList[thisBlock].container.updateCache();
};
postProcessArg = [thisBlock, 'sol'];
} else if (name === 'notename') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = value;
that.blockList[thisBlock].container.updateCache();
};
postProcessArg = [thisBlock, 'G'];
} else if (name === 'drumname') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = value;
that.blockList[thisBlock].container.updateCache();
};
postProcessArg = [thisBlock, 'kick'];
} else if (name === 'voicename') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = value;
that.blockList[thisBlock].container.updateCache();
};
postProcessArg = [thisBlock, 'sine'];
} else if (name === 'modename') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = value;
that.blockList[thisBlock].container.updateCache();
};
postProcessArg = [thisBlock, 'Major'];
} else if (name === 'number') {
postProcess = function (args) {
var thisBlock = args[0];
var value = Number(args[1]);
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = value.toString();
that.blockList[thisBlock].container.updateCache();
};
postProcessArg = [thisBlock, NUMBERBLOCKDEFAULT];
} else if (name === 'media') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
if (value == null) {
that.blockList[thisBlock].image = 'images/load-media.svg';
} else {
that.blockList[thisBlock].image = null;
}
};
postProcessArg = [thisBlock, null];
} else if (name === 'camera') {
postProcess = function (args) {
console.log('post process camera ' + args[1]);
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = CAMERAVALUE;
if (value == null) {
that.blockList[thisBlock].image = 'images/camera.svg';
} else {
that.blockList[thisBlock].image = null;
}
};
postProcessArg = [thisBlock, null];
} else if (name === 'video') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = VIDEOVALUE;
if (value == null) {
that.blockList[thisBlock].image = 'images/video.svg';
} else {
that.blockList[thisBlock].image = null;
}
};
postProcessArg = [thisBlock, null];
} else if (name === 'loadFile') {
postProcess = function (args) {
that.updateBlockText(args[0]);
};
postProcessArg = [thisBlock, null];
} else if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg', 'namedarg'].indexOf(name) !== -1) {
postProcess = function (args) {
that.blockList[thisBlock].value = null;
that.blockList[thisBlock].privateData = args[1];
};
postProcessArg = [thisBlock, arg];
}
var protoFound = false;
for (var proto in that.protoBlockDict) {
if (that.protoBlockDict[proto].name === name) {
if (arg === '__NOARG__') {
that.makeNewBlock(proto, postProcess, postProcessArg);
protoFound = true;
break;
} else if (that.protoBlockDict[proto].defaults[0] === arg) {
that.makeNewBlock(proto, postProcess, postProcessArg);
protoFound = true;
break;
} else if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg', 'namedarg'].indexOf(name) !== -1) {
if (that.protoBlockDict[proto].defaults[0] === undefined) {
that.makeNewBlock(proto, postProcess, postProcessArg);
protoFound = true;
break;
}
}
}
}
if (!protoFound) {
console.log(name + ' not found!!');
}
var blk = this.blockList.length - 1;
var myBlock = this.blockList[blk];
for (var i = 0; i < myBlock.docks.length; i++) {
myBlock.connections.push(null);
}
// Attach default args if any
var cblk = blk + 1;
for (var i = 0; i < myBlock.protoblock.defaults.length; i++) {
var value = myBlock.protoblock.defaults[i];
if (myBlock.name === 'action') {
// Make sure we don't make two actions with the same name.
value = this.findUniqueActionName(_('action'));
if (value !== _('action')) {
// TODO: are there return or arg blocks?
this.newNameddoBlock(value, false, false);
this.palettes.hide();
this.palettes.updatePalettes('action');
this.palettes.show();
}
}
var that = this;
var thisBlock = this.blockList.length;
if (myBlock.docks.length > i && myBlock.docks[i + 1][2] === 'anyin') {
if (value == null) {
console.log('cannot set default value');
} else if (typeof(value) === 'string') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
var label = value.toString();
if (label.length > 8) {
label = label.substr(0, 7) + '...';
}
that.blockList[thisBlock].text.text = label;
that.blockList[thisBlock].container.updateCache();
};
this.makeNewBlock('text', postProcess, [thisBlock, value]);
} else {
postProcess = function (args) {
var thisBlock = args[0];
var value = Number(args[1]);
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = value.toString();
};
this.makeNewBlock('number', postProcess, [thisBlock, value]);
}
} else if (myBlock.docks[i + 1][2] === 'textin') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
var label = value.toString();
if (label.length > 8) {
label = label.substr(0, 7) + '...';
}
that.blockList[thisBlock].text.text = label;
};
this.makeNewBlock('text', postProcess, [thisBlock, value]);
} else if (myBlock.docks[i + 1][2] === 'solfegein') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
var label = value.toString();
that.blockList[thisBlock].text.text = label;
};
this.makeNewBlock('solfege', postProcess, [thisBlock, value]);
} else if (myBlock.docks[i + 1][2] === 'notein') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
var label = value.toString();
that.blockList[thisBlock].text.text = label;
};
this.makeNewBlock('notename', postProcess, [thisBlock, value]);
} else if (myBlock.docks[i + 1][2] === 'mediain') {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
if (value != null) {
// loadThumbnail(that, thisBlock, null);
}
};
this.makeNewBlock('media', postProcess, [thisBlock, value]);
} else if (myBlock.docks[i + 1][2] === 'filein') {
postProcess = function (blk) {
that.updateBlockText(blk);
}
this.makeNewBlock('loadFile', postProcess, thisBlock);
} else {
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.blockList[thisBlock].text.text = value.toString();
};
this.makeNewBlock('number', postProcess, [thisBlock, value]);
}
var myConnectionBlock = this.blockList[cblk + i];
myConnectionBlock.connections = [blk];
myConnectionBlock.value = value;
myBlock.connections[i + 1] = cblk + i;
}
// Generate and position the block bitmaps and labels
this.updateBlockPositions();
// console.log('Adjust Docks: ' + this.blockList[blk].name);
this.adjustDocks(blk, true);
this.refreshCanvas();
return blk;
};
this.findDragGroup = function (blk) {
// Generate a drag group from blocks connected to blk
this.dragLoopCounter = 0;
this.dragGroup = [];
this._calculateDragGroup(blk);
};
this._calculateDragGroup = function (blk) {
// Give a block, find all the blocks connected to it
this.dragLoopCounter += 1;
if (this.dragLoopCount > this.blockList.length) {
console.log('maximum loop counter exceeded in calculateDragGroup... this is bad. ' + blk);
return;
}
if (blk == null) {
console.log('null block passed to calculateDragGroup');
return;
}
var myBlock = this.blockList[blk];
// If this happens, something is really broken.
if (myBlock == null) {
console.log('null block encountered... this is bad. ' + blk);
return;
}
// As before, does these ever happen?
if (myBlock.connections == null) {
this.dragGroup = [blk];
return;
}
// Some malformed blocks might have no connections.
if (myBlock.connections.length === 0) {
this.dragGroup = [blk];
return;
}
this.dragGroup.push(blk);
for (var c = 1; c < myBlock.connections.length; c++) {
var cblk = myBlock.connections[c];
if (cblk != null) {
// Recurse
this._calculateDragGroup(cblk);
}
}
};
this.setActionProtoVisiblity = function (state) {
// By default, the nameddo protoblock is hidden.
var actionsPalette = this.palettes.dict['action'];
var stateChanged = false;
for (var blockId = 0; blockId < actionsPalette.protoList.length; blockId++) {
var block = actionsPalette.protoList[blockId];
if ('nameddo' === block.name && block.defaults.length === 0) {
if (block.hidden === state) {
block.hidden = !state;
stateChanged = true;
}
}
}
// Force an update if the name has changed.
if (stateChanged) {
this.palettes.hide();
this.palettes.updatePalettes('action');
this.palettes.show();
}
};
this.findUniqueActionName = function (name) {
// If we have a stack named 'action', make the protoblock visible.
if (name === _('action')) {
this.setActionProtoVisiblity(true);
}
// Make sure we don't make two actions with the same name.
var actionNames = [];
for (var blk = 0; blk < this.blockList.length; blk++) {
if ((this.blockList[blk].name === 'text' || this.blockList[blk].name === 'string') && !this.blockList[blk].trash) {
var c = this.blockList[blk].connections[0];
if (c != null && this.blockList[c].name === 'action' && !this.blockList[c].trash) {
actionNames.push(this.blockList[blk].value);
}
}
}
var i = 1;
var value = name;
while (actionNames.indexOf(value) !== -1) {
value = name + i.toString();
i += 1;
}
return value;
};
this._findDrumURLs = function () {
// Make sure we initialize any drum with a URL name.
for (var blk = 0; blk < this.blockList.length; blk++) {
if (this.blockList[blk].name === 'text' || this.blockList[blk].name === 'string') {
var c = this.blockList[blk].connections[0];
if (c != null && ['playdrum', 'setdrum', 'setvoice'].indexOf(this.blockList[c].name) !== -1) {
if (this.blockList[blk].value.slice(0, 4) === 'http') {
if (_THIS_IS_MUSIC_BLOCKS_) {
this.logo.synth.loadSynth(this.blockList[blk].value);
}
}
}
}
}
};
this.renameBoxes = function (oldName, newName) {
if (oldName === newName) {
return;
}
for (var blk = 0; blk < this.blockList.length; blk++) {
if (this.blockList[blk].name === 'text') {
var c = this.blockList[blk].connections[0];
if (c != null && this.blockList[c].name === 'box') {
if (this.blockList[blk].value === oldName) {
this.blockList[blk].value = newName;
this.blockList[blk].text.text = newName;
try {
this.blockList[blk].container.updateCache();
} catch (e) {
console.log(e);
}
}
}
}
}
};
this.renameNamedboxes = function (oldName, newName) {
if (oldName === newName) {
return;
}
for (var blk = 0; blk < this.blockList.length; blk++) {
if (this.blockList[blk].name === 'namedbox') {
if (this.blockList[blk].privateData === oldName) {
this.blockList[blk].privateData = newName;
this.blockList[blk].overrideName = newName;
this.blockList[blk].regenerateArtwork();
// Update label...
try {
this.blockList[blk].container.updateCache();
} catch (e) {
console.log(e);
}
}
}
}
// Update the palette
var blockPalette = this.palettes.dict['boxes'];
var nameChanged = false;
for (var blockId = 0; blockId < blockPalette.protoList.length; blockId++) {
var block = blockPalette.protoList[blockId];
if (block.name === 'namedbox' && block.defaults[0] !== _('box') && block.defaults[0] === oldName) {
// console.log('renaming ' + block.defaults[0] + ' to ' + newName);
block.defaults[0] = newName;
nameChanged = true;
}
}
// Force an update if the name has changed.
if (nameChanged) {
this.palettes.hide();
this.palettes.updatePalettes('boxes');
this.palettes.show();
}
};
this.renameDos = function (oldName, newName, skipBlock) {
if (oldName === newName) {
return;
}
// Update the blocks, do->oldName should be do->newName
// Named dos are modified in a separate function below.
for (var blk = 0; blk < this.blockList.length; blk++) {
if (blk === skipBlock) {
continue;
}
var myBlock = this.blockList[blk];
var blkParent = this.blockList[myBlock.connections[0]];
if (blkParent == null) {
continue;
}
if (['do', 'calc', 'doArg', 'calcArg', 'action'].indexOf(blkParent.name) === -1) {
continue;
}
var blockValue = myBlock.value;
if (blockValue === oldName) {
myBlock.value = newName;
var label = myBlock.value;
if (label.length > 8) {
label = label.substr(0, 7) + '...';
}
myBlock.text.text = label;
myBlock.container.updateCache();
}
}
};
this.renameNameddos = function (oldName, newName) {
if (oldName === newName) {
return;
}
// Update the blocks, do->oldName should be do->newName
for (var blk = 0; blk < this.blockList.length; blk++) {
if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(this.blockList[blk].name) !== -1) {
if (this.blockList[blk].privateData === oldName) {
this.blockList[blk].privateData = newName;
var label = newName;
if (label.length > 8) {
label = label.substr(0, 7) + '...';
}
this.blockList[blk].overrideName = label;
// console.log('regenerating artwork for ' + this.blockList[blk].name + ' block[' + blk + ']: ' + oldName + ' -> ' + label);
this.blockList[blk].regenerateArtwork();
}
}
}
// Update the palette
var actionsPalette = this.palettes.dict['action'];
var nameChanged = false;
for (var blockId = 0; blockId < actionsPalette.protoList.length; blockId++) {
var block = actionsPalette.protoList[blockId];
if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(block.name) !== -1 /* && block.defaults[0] !== _('action') */ && block.defaults[0] === oldName) {
// console.log('renaming ' + block.name + ': ' + block.defaults[0] + ' to ' + newName);
block.defaults[0] = newName;
nameChanged = true;
}
}
// Force an update if the name has changed.
if (nameChanged) {
this.palettes.hide();
this.palettes.updatePalettes('action');
this.palettes.show();
}
};
this.newStoreinBlock = function (name) {
if (name == null) {
console.log('null name passed to newStoreinBlock');
return;
} else if (name == undefined) {
console.log('undefined name passed to newStoreinBlock');
return;
} else if ('myStorein_' + name in this.protoBlockDict) {
// console.log(name + ' already in palette');
return;
}
// console.log('new storein block ' + name);
var myStoreinBlock = new ProtoBlock('storein');
this.protoBlockDict['myStorein_' + name] = myStoreinBlock;
myStoreinBlock.palette = this.palettes.dict['boxes'];
myStoreinBlock.defaults.push(name);
myStoreinBlock.defaults.push(NUMBERBLOCKDEFAULT);
myStoreinBlock.staticLabels.push(_('store in'), _('name'), _('value'));
myStoreinBlock.adjustWidthToLabel();
myStoreinBlock.twoArgBlock();
myStoreinBlock.dockTypes[1] = 'anyin';
myStoreinBlock.dockTypes[2] = 'anyin';
if (name === 'box') {
return;
}
// Add the new block to the top of the palette.
myStoreinBlock.palette.add(myStoreinBlock, true);
};
this.newNamedboxBlock = function (name) {
if (name == null) {
console.log('null name passed to newNamedboxBlock');
return;
} else if (name == undefined) {
console.log('undefined name passed to newNamedboxBlock');
return;
} else if ('myBox_' + name in this.protoBlockDict) {
return;
}
var myBoxBlock = new ProtoBlock('namedbox');
this.protoBlockDict['myBox_' + name] = myBoxBlock;
myBoxBlock.palette = this.palettes.dict['boxes'];
myBoxBlock.defaults.push(name);
myBoxBlock.staticLabels.push(name);
myBoxBlock.parameterBlock();
if (name === 'box') {
return;
}
// Add the new block to the top of the palette.
myBoxBlock.palette.add(myBoxBlock, true);
};
this._newLocalArgBlock = function (name) {
// name === 1, 2, 3, ...
var blkname = 'arg_' + name;
if ('myArg_' + name in this.protoBlockDict) {
return;
}
if (blkname in this.protoBlockDict) {
return;
}
var myNamedArgBlock = new ProtoBlock('namedarg');
this.protoBlockDict['myArg_' + blkname] = myNamedArgBlock;
myNamedArgBlock.palette = this.palettes.dict['action'];
myNamedArgBlock.defaults.push(name);
myNamedArgBlock.staticLabels.push('arg ' + name);
myNamedArgBlock.parameterBlock();
if (blkname === 'arg_1') {
return;
}
myNamedArgBlock.palette.add(myNamedArgBlock, true);
// Force regeneration of palette after adding new block.
// Add delay to avoid race condition.
var that = this;
setTimeout(function () {
that.palettes.hide();
that.palettes.updatePalettes('action');
that.palettes.show();
}, 500);
};
this._removeNamedoEntries = function (name) {
// Delete any old palette entries.
// console.log('DELETE: removing old palette entries for ' + name);
if (this.protoBlockDict['myDo_' + name]) {
// console.log('deleting myDo_' + name + ' ' + this.protoBlockDict['myDo_' + name].name);
this.protoBlockDict['myDo_' + name].hide = true;
delete this.protoBlockDict['myDo_' + name];
} else if (this.protoBlockDict['myCalc_' + name]) {
// console.log('deleting myCalc_' + name + ' ' + this.protoBlockDict['myCalc_' + name].name);
this.protoBlockDict['myCalc_' + name].hide = true;
delete this.protoBlockDict['myCalc_' + name];
} else if (this.protoBlockDict['myDoArg_' + name]) {
// console.log('deleting myDoArg_' + name + ' ' + this.protoBlockDict['myDoArg_' + name].name);
this.protoBlockDict['myDoArg_' + name].hide = true;
delete this.protoBlockDict['myDoArg_' + name];
} else if (this.protoBlockDict['myCalcArg_' + name]) {
// console.log('deleting myCalcArg_' + name + ' ' + this.protoBlockDict['myCalcArg_' + name].name);
this.protoBlockDict['myCalcArg_' + name].hide = true;
delete this.protoBlockDict['myCalcArg_' + name];
}
};
this.newNameddoBlock = function (name, hasReturn, hasArgs) {
// Depending upon the form of the associated action block, we
// want to add a named do, a named calc, a named do w/args, or
// a named calc w/args.
if (name === _('action')) {
// 'action' already has its associated palette entries.
return false;
}
if (hasReturn && hasArgs) {
this.newNamedcalcArgBlock(name);
return true;
} else if (!hasReturn && hasArgs) {
this.newNameddoArgBlock(name);
return true;
} else if (hasReturn && !hasArgs) {
this.newNamedcalcBlock(name);
return true;
} else if (this.protoBlockDict['myDo_' + name] === undefined) {
var myDoBlock = new ProtoBlock('nameddo');
this.protoBlockDict['myDo_' + name] = myDoBlock;
myDoBlock.palette = this.palettes.dict['action'];
myDoBlock.defaults.push(name);
myDoBlock.staticLabels.push(name);
myDoBlock.zeroArgBlock();
myDoBlock.palette.add(myDoBlock, true);
this.palettes.updatePalettes();
return true;
}
return false;
};
this.newNamedcalcBlock = function (name) {
if (this.protoBlockDict['myCalc_' + name] === undefined) {
// console.log('creating myCalc_' + name);
var myCalcBlock = new ProtoBlock('namedcalc');
this.protoBlockDict['myCalc_' + name] = myCalcBlock;
myCalcBlock.palette = this.palettes.dict['action'];
myCalcBlock.defaults.push(name);
myCalcBlock.staticLabels.push(name);
myCalcBlock.zeroArgBlock();
// Add the new block to the top of the palette.
myCalcBlock.palette.add(myCalcBlock, true);
// } else {
// console.log('myCalc_' + name + ' already exists.');
}
};
this.newNameddoArgBlock = function (name) {
if (this.protoBlockDict['myDoArg_' + name] === undefined) {
// console.log('creating myDoArg_' + name);
var myDoArgBlock = new ProtoBlock('nameddoArg');
this.protoBlockDict['myDoArg_' + name] = myDoArgBlock;
myDoArgBlock.palette = this.palettes.dict['action'];
myDoArgBlock.defaults.push(name);
myDoArgBlock.staticLabels.push(name);
myDoArgBlock.zeroArgBlock();
// Add the new block to the top of the palette.
myDoArgBlock.palette.add(myDoArgBlock, true);
// } else {
// console.log('myDoArg_' + name + ' already exists.');
}
};
this.newNamedcalcArgBlock = function (name) {
if (this.protoBlockDict['myCalcArg_' + name] === undefined) {
// console.log('creating myCalcArg_' + name);
var myCalcArgBlock = new ProtoBlock('namedcalcArg');
this.protoBlockDict['myCalcArg_' + name] = myCalcArgBlock;
myCalcArgBlock.palette = this.palettes.dict['action'];
myCalcArgBlock.defaults.push(name);
myCalcArgBlock.staticLabels.push(name);
myCalcArgBlock.zeroArgBlock();
// Add the new block to the top of the palette.
myCalcArgBlock.palette.add(myCalcArgBlock, true);
// } else {
// console.log('myCalcArg_' + name + ' already exists.');
}
};
this._insideArgClamp = function (blk) {
// Returns a containing arg clamp block or null
if (this.blockList[blk] == null) {
// race condition?
console.log('null block in blockList? ' + blk);
return null;
} else if (this.blockList[blk].connections[0] == null) {
return null;
} else {
var cblk = this.blockList[blk].connections[0];
if (this.blockList[cblk].isArgClamp()) {
return cblk;
} else {
return null;
}
}
};
this._insideExpandableBlock = function (blk) {
// Returns a containing expandable block or null
if (this.blockList[blk] == null) {
// race condition?
console.log('null block in blockList? ' + blk);
return null;
} else if (this.blockList[blk].connections[0] == null) {
return null;
} else {
var cblk = this.blockList[blk].connections[0];
if (this.blockList[cblk].isExpandableBlock()) {
// If it is the last connection, keep searching.
if (this.blockList[cblk].isArgFlowClampBlock()) {
return cblk;
} else if (blk === last(this.blockList[cblk].connections)) {
return this._insideExpandableBlock(cblk);
} else {
return cblk;
}
} else {
return this._insideExpandableBlock(cblk);
}
}
};
this._insideNoteBlock = function (blk) {
// Returns a containing note block or null
if (this.blockList[blk] == null) {
console.log('null block in blockList? ' + blk);
return null;
} else if (this.blockList[blk].connections[0] == null) {
return null;
} else {
var cblk = this.blockList[blk].connections[0];
if (this.blockList[cblk].isExpandableBlock()) {
// If it is the last connection, keep searching.
if (blk === last(this.blockList[cblk].connections)) {
return this._insideNoteBlock(cblk);
} else if (blk === this.blockList[cblk].connections[1]) {
// Connection 1 of a note block is not inside the clamp.
return null;
} else {
if (['newnote', 'osctime'].indexOf(this.blockList[cblk].name) !== -1) {
return cblk;
} else {
return null;
}
}
} else {
return this._insideNoteBlock(cblk);
}
}
};
this.triggerLongPress = function (myBlock) {
this.timeOut == null;
this.inLongPress = true;
var z = this.stage.getNumChildren() - 1;
// Auto-select stack for copying -- no need to actually click on
// the copy button.
var topBlock = this.findTopBlock(this.activeBlock);
this.selectedStack = topBlock;
// Copy the selectedStack.
this.selectedBlocksObj = JSON.parse(JSON.stringify(this._copyBlocksToObj()));
this.updatePasteButton();
if (myBlock.name === 'action') {
this.dismissButton.visible = true;
this.dismissButton.x = myBlock.container.x - 27;
this.dismissButton.y = myBlock.container.y - 27;
this.stage.setChildIndex(this.dismissButton, z - 1);
this.saveStackButton.visible = true;
this.saveStackButton.x = myBlock.container.x + 27;
this.saveStackButton.y = myBlock.container.y - 27;
this.stage.setChildIndex(this.saveStackButton, z - 2);
}
this.refreshCanvas();
};
this.pasteStack = function () {
// Copy a stack of blocks by creating a blockObjs and passing
// it to this.load.
if (this.selectedStack == null) {
return;
}
// First, hide the palettes as they will need updating.
for (var name in this.palettes.dict) {
this.palettes.dict[name].hideMenu(true);
}
// var blockObjs = this._copyBlocksToObj();
// this.loadNewBlocks(blockObjs);
// console.log(this.selectedBlocksObj);
this.loadNewBlocks(this.selectedBlocksObj);
};
this.saveStack = function () {
// Save a stack of blocks to local storage and the 'myblocks'
// palette by creating a blockObjs and ...
if (this.selectedStack == null) {
return;
}
this.palettes.hide();
var blockObjs = this._copyBlocksToObj();
// The first block is an action block. Its first connection is
// the block containing its label.
var nameBlk = blockObjs[0][4][1];
if (nameBlk == null) {
console.log('action not named... skipping');
} else {
console.log(blockObjs[nameBlk][1][1]);
if (typeof(blockObjs[nameBlk][1][1]) === 'string') {
var name = blockObjs[nameBlk][1][1];
} else if (typeof(blockObjs[nameBlk][1][1]) === 'number') {
var name = blockObjs[nameBlk][1][1].toString();
} else {
var name = blockObjs[nameBlk][1][1]['value'];
}
storage.macros = prepareMacroExports(name, blockObjs, this.macroDict);
if (sugarizerCompatibility.isInsideSugarizer()) {
sugarizerCompatibility.saveLocally(function () {
this.addToMyPalette(name, blockObjs);
// this.palettes.updatePalettes('myblocks');
});
} else {
this.addToMyPalette(name, blockObjs);
// this.palettes.updatePalettes('myblocks');
}
}
};
this._copyBlocksToObj = function () {
var blockObjs = [];
var blockMap = {};
this.findDragGroup(this.selectedStack);
for (var b = 0; b < this.dragGroup.length; b++) {
myBlock = this.blockList[this.dragGroup[b]];
if (b === 0) {
x = 75 - this.stage.x;
y = 75 - this.stage.y;
} else {
x = 0;
y = 0;
}
if (myBlock.isValueBlock()) {
switch (myBlock.name) {
case 'media':
blockItem = [b, [myBlock.name, null], x, y, []];
break;
default:
blockItem = [b, [myBlock.name, myBlock.value], x, y, []];
break;
}
} else if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg', 'namedarg'].indexOf(myBlock.name) !== -1) {
blockItem = [b, [myBlock.name, {'value': myBlock.privateData}], x, y, []];
} else {
blockItem = [b, myBlock.name, x, y, []];
}
blockMap[this.dragGroup[b]] = b;
blockObjs.push(blockItem);
}
for (var b = 0; b < this.dragGroup.length; b++) {
myBlock = this.blockList[this.dragGroup[b]];
for (var c = 0; c < myBlock.connections.length; c++) {
if (myBlock.connections[c] == null) {
blockObjs[b][4].push(null);
} else {
blockObjs[b][4].push(blockMap[myBlock.connections[c]]);
}
}
}
return blockObjs;
};
this.addToMyPalette = function (name, obj) {
// On the palette we store the macro as a basic block.
var myBlock = new ProtoBlock('macro_' + name);
var blkName = 'macro_' + name;
this.protoBlockDict[blkName] = myBlock;
if (!('myblocks' in this.palettes.dict)) {
this.palettes.add('myblocks');
}
myBlock.palette = this.palettes.dict['myblocks'];
myBlock.zeroArgBlock();
myBlock.staticLabels.push(_(name));
this.protoBlockDict[blkName].palette.add(this.protoBlockDict[blkName]);
};
this.loadNewBlocks = function (blockObjs) {
// Check for blocks connected to themselves,
// and for action blocks not connected to text blocks.
for (var b = 0; b < blockObjs.length; b++) {
var blkData = blockObjs[b];
for (var c in blkData[4]) {
if (blkData[4][c] === blkData[0]) {
console.log('Circular connection in block data: ' + blkData);
console.log('Punting loading of new blocks!');
console.log(blockObjs);
return;
}
}
}
// We'll need a list of existing storein and action names.
var currentActionNames = [];
var currentStoreinNames = [];
for (var b = 0; b < this.blockList.length; b++) {
if (this.blockList[b].trash) {
continue;
}
if (this.blockList[b].name === 'action') {
if (this.blockList[b].connections[1] != null) {
console.log(this.blockList[this.blockList[b].connections[1]].value);
currentActionNames.push(this.blockList[this.blockList[b].connections[1]].value);
}
} else if (this.blockList[b].name === 'storein') {
if (this.blockList[b].connections[1] != null) {
currentStoreinNames.push(this.blockList[this.blockList[b].connections[1]].value);
}
}
}
// We need to track two-arg blocks in case they need expanding.
this._checkTwoArgBlocks = [];
// And arg clamp blocks in case they need expanding.
this._checkArgClampBlocks = [];
// Don't make duplicate action names.
// Add a palette entry for any new storein blocks.
var stringNames = [];
var stringValues = {}; // label: [blocks with that label]
var actionNames = {}; // action block: label block
var storeinNames = {}; // storein block: label block
var doNames = {}; // do block: label block, nameddo block value
// action and start blocks that need to be collapsed.
this.blocksToCollapse = [];
// Scan for any new action and storein blocks to identify
// duplicates. We also need to track start and action blocks
// that may need to be collapsed.
for (var b = 0; b < blockObjs.length; b++) {
var blkData = blockObjs[b];
// blkData[1] could be a string or an object.
if (typeof(blkData[1]) === 'string') {
var name = blkData[1];
} else {
var name = blkData[1][0];
}
if (!(name in this.protoBlockDict)) {
switch (name) {
case 'hat':
name = 'action';
break;
case 'string':
name = 'text';
break;
default:
console.log('skipping ' + name);
continue;
break;
}
}
if (['arg', 'twoarg'].indexOf(this.protoBlockDict[name].style) !== -1) {
if (this.protoBlockDict[name].expandable) {
this._checkTwoArgBlocks.push(this.blockList.length + b);
}
}
// FIXME: Use tests in block.js
if (['clamp', 'argclamp', 'argclamparg', 'doubleclamp', 'argflowclamp'].indexOf(this.protoBlockDict[name].style) !== -1) {
this._checkArgClampBlocks.push(this.blockList.length + b);
}
switch (name) {
case 'text':
var key = blkData[1][1];
if (stringValues[key] === undefined) {
stringValues[key] = [];
}
stringValues[key].push(b);
break;
case 'action':
case 'hat':
if (blkData[4][1] != null) {
actionNames[b] = blkData[4][1];
}
break;
case 'storein':
if (blkData[4][1] != null) {
storeinNames[b] = blkData[4][1];
}
break;
case 'nameddo':
case 'namedcalc':
case 'nameddoArg':
case 'namedcalcArg':
doNames[b] = blkData[1][1]['value'];
break;
case 'do':
case 'stack':
if (blkData[4][1] != null) {
doNames[b] = blkData[4][1];
}
break;
default:
break;
}
switch (name) {
case 'action':
case 'pitchdrummatrix':
case 'rhythmruler':
case 'pitchstaircase':
case 'tempo':
case 'pitchslider':
case 'matrix':
case 'drum':
case 'status':
case 'start':
if (typeof(blkData[1]) === 'object' && blkData[1].length > 1 && typeof(blkData[1][1]) === 'object' && 'collapsed' in blkData[1][1]) {
if (blkData[1][1]['collapsed']) {
this.blocksToCollapse.push(this.blockList.length + b);
}
}
break;
default:
break;
}
}
var updatePalettes = false;
// Make sure new storein names have palette entries.
for (var b in storeinNames) {
var blkData = blockObjs[storeinNames[b]];
if (currentStoreinNames.indexOf(blkData[1][1]) === -1) {
if (typeof(blkData[1][1]) === 'string') {
var name = blkData[1][1];
} else {
var name = blkData[1][1]['value'];
}
// console.log('Adding new palette entries for store-in ' + name);
this.newStoreinBlock(name);
this.newNamedboxBlock(name);
updatePalettes = true;
}
}
// Make sure action names are unique.
for (var b in actionNames) {
// Is there a proto do block with this name? If so, find a
// new name.
// Name = the value of the connected label.
var blkData = blockObjs[actionNames[b]];
if (typeof(blkData[1][1]) === 'string') {
var name = blkData[1][1];
} else {
var name = blkData[1][1]['value'];
}
// If we have a stack named 'action', make the protoblock visible.
if (name === _('action')) {
this.setActionProtoVisiblity(true);
}
var oldName = name;
var i = 1;
while (currentActionNames.indexOf(name) !== -1) {
name = oldName + i.toString();
i += 1;
// Should never happen... but just in case.
if (i > this.blockList.length) {
console.log('Could not generate unique action name.');
break;
}
}
if (oldName !== name) {
// Change the name of the action...
console.log('action ' + oldName + ' is being renamed ' + name);
blkData[1][1] = {'value': name};
}
// and any do blocks
for (var d in doNames) {
var thisBlkData = blockObjs[d];
if (typeof(thisBlkData[1]) === 'string') {
var blkName = thisBlkData[1];
} else {
var blkName = thisBlkData[1][0];
}
if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(blkName) !== -1) {
if (thisBlkData[1][1]['value'] === oldName) {
thisBlkData[1][1] = {'value': name};
}
} else {
var doBlkData = blockObjs[doNames[d]];
if (typeof(doBlkData[1][1]) === 'string') {
if (doBlkData[1][1] === oldName) {
// console.log('renaming ' + oldName + ' to ' + name);
doBlkData[1][1] = name;
}
} else {
if (doBlkData[1][1]['value'] === oldName) {
// console.log('renaming ' + oldName + ' to ' + name);
doBlkData[1][1] = {'value': name};
}
}
}
}
}
if (updatePalettes) {
this.palettes.hide();
this.palettes.updatePalettes('action');
this.palettes.show();
}
// This section of the code attempts to repair imported
// code. For example, it adds missing hidden blocks and
// convert old-style notes to new-style notes.
blockObjsLength = blockObjs.length;
var extraBlocksLength = 0;
for (var b = 0; b < blockObjsLength; b++) {
if (typeof(blockObjs[b][1]) === 'object') {
var name = blockObjs[b][1][0];
} else {
var name = blockObjs[b][1];
}
switch (name) {
case 'articulation':
case 'augmented':
case 'backward':
case 'crescendo':
case 'diminished':
case 'dividebeatfactor':
case 'drift':
case 'duplicatenotes':
case 'invert1':
case 'fill':
case 'flat':
case 'hollowline':
case 'major':
case 'minor':
case 'multiplybeatfactor':
case 'note':
case 'newnote':
case 'newslur':
case 'newstaccato':
case 'newswing':
case 'newswing2':
case 'osctime':
case 'perfect':
case 'pluck':
case 'rhythmicdot':
case 'setbpm':
case 'setnotevolume2':
case 'settransposition':
case 'setvoice':
case 'sharp':
case 'skipnotes':
case 'slur':
case 'staccato':
case 'swing':
case 'tie':
case 'tuplet2':
case 'vibrato':
var len = blockObjs[b][4].length;
if (last(blockObjs[b][4]) == null) {
// If there is no next block, add a hidden block;
console.log('last connection of ' + name + ' is null: adding hidden block');
blockObjs[b][4][len - 1] = blockObjsLength + extraBlocksLength;
blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, null]]);
extraBlocksLength += 1;
} else {
var nextBlock = blockObjs[b][4][len - 1];
if (typeof(blockObjs[nextBlock][1]) === 'object') {
var nextName = blockObjs[nextBlock][1][0];
} else {
var nextName = blockObjs[nextBlock][1];
}
if (nextName !== 'hidden') {
console.log('last connection of ' + name + ' is ' + nextName + ': adding hidden block');
// If the next block is not a hidden block, add one.
blockObjs[b][4][len - 1] = blockObjsLength + extraBlocksLength;
blockObjs[nextBlock][4][0] = blockObjsLength + extraBlocksLength;
blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, nextBlock]]);
extraBlocksLength += 1;
}
}
if (['note', 'slur', 'staccato', 'swing'].indexOf(name) !== -1) {
// We need to convert to newnote style:
// (1) add a vspace to the start of the clamp of a note block.
console.log('note: ' + b);
var clampBlock = blockObjs[b][4][2];
blockObjs[b][4][2] = blockObjsLength + extraBlocksLength;
if (clampBlock == null) {
blockObjs.push([blockObjsLength + extraBlocksLength, 'vspace', 0, 0, [b, null]]);
} else {
blockObjs[clampBlock][4][0] = blockObjsLength + extraBlocksLength;
blockObjs.push([blockObjsLength + extraBlocksLength, 'vspace', 0, 0, [b, clampBlock]]);
}
extraBlocksLength += 1;
// (2) switch the first connection to divide 1 / arg.
var argBlock = blockObjs[b][4][1];
blockObjs[b][4][1] = blockObjsLength + extraBlocksLength;
if (argBlock == null) {
blockObjs.push([blockObjsLength + extraBlocksLength, 'divide', 0, 0, [b, blockObjsLength + extraBlocksLength + 1, blockObjsLength + extraBlocksLength + 2]]);
blockObjs.push([blockObjsLength + extraBlocksLength + 1, ['number', {'value': 1}], 0, 0, [blockObjsLength + extraBlocksLength]]);
blockObjs.push([blockObjsLength + extraBlocksLength + 2, ['number', {'value': 1}], 0, 0, [blockObjsLength + extraBlocksLength]]);
extraBlocksLength += 3;
} else {
blockObjs[argBlock][4][0] = blockObjsLength + extraBlocksLength;
blockObjs.push([blockObjsLength + extraBlocksLength, 'divide', 0, 0, [b, blockObjsLength + extraBlocksLength + 1, argBlock]]);
blockObjs.push([blockObjsLength + extraBlocksLength + 1, ['number', {'value': 1}], 0, 0, [blockObjsLength + extraBlocksLength]]);
extraBlocksLength += 2;
}
// (3) create a newnote block instead.
if (typeof(blockObjs[b][1]) === 'object') {
blockObjs[b][1][0] = 'new' + name;
} else {
blockObjs[b][1] = 'new' + name;
}
}
break;
case 'action':
// Ensure that there is a hidden block as the first
// block in the child flow (connection 2) of an action
// block (required to make the backward block function
// propperly).
var len = blockObjs[b][4].length;
if (blockObjs[b][4][2] == null) {
// If there is no child flow block, add a hidden block;
console.log('last connection of ' + name + ' is null: adding hidden block');
blockObjs[b][4][2] = blockObjsLength + extraBlocksLength;
blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, null]]);
extraBlocksLength += 1;
} else {
var nextBlock = blockObjs[b][4][2];
if (typeof(blockObjs[nextBlock][1]) === 'object') {
var nextName = blockObjs[nextBlock][1][0];
} else {
var nextName = blockObjs[nextBlock][1];
}
if (nextName !== 'hidden') {
console.log('last connection of ' + name + ' is ' + nextName + ': adding hidden block');
// If the next block is not a hidden block, add one.
blockObjs[b][4][2] = blockObjsLength + extraBlocksLength;
blockObjs[nextBlock][4][0] = blockObjsLength + extraBlocksLength;
blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, nextBlock]]);
extraBlocksLength += 1;
}
}
break;
default:
break;
}
}
// Append to the current set of blocks.
this._adjustTheseStacks = [];
this._adjustTheseDocks = [];
this._loadCounter = blockObjs.length;
// We add new blocks to the end of the block list.
var blockOffset = this.blockList.length;
var firstBlock = this.blockList.length;
for (var b = 0; b < this._loadCounter; b++) {
var thisBlock = blockOffset + b;
var blkData = blockObjs[b];
if (typeof(blkData[1]) === 'object') {
if (blkData[1].length === 1) {
var blkInfo = [blkData[1][0], {'value': null}];
} else if (['number', 'string'].indexOf(typeof(blkData[1][1])) !== -1) {
var blkInfo = [blkData[1][0], {'value': blkData[1][1]}];
if (COLLAPSABLES.indexOf(blkData[1][0]) !== -1) {
blkInfo[1]['collapsed'] = false;
}
} else {
var blkInfo = blkData[1];
}
} else {
var blkInfo = [blkData[1], {'value': null}];
if (COLLAPSABLES.indexOf(blkData[1]) !== -1) {
blkInfo[1]['collapsed'] = false;
}
}
var name = blkInfo[0];
var collapsed = false;
if (COLLAPSABLES.indexOf(name) !== -1) {
collapsed = blkInfo[1]['collapsed'];
}
if (blkInfo[1] == null) {
var value = null;
} else {
var value = blkInfo[1]['value'];
}
if (name in NAMEDICT) {
name = NAMEDICT[name];
}
var that = this;
// A few special cases.
switch (name) {
// Only add 'collapsed' arg to start, action blocks.
case 'start':
blkData[4][0] = null;
blkData[4][2] = null;
postProcess = function (args) {
var thisBlock = args[0];
var blkInfo = args[1];
that.blockList[thisBlock].value = that.turtles.turtleList.length;
that.turtles.addTurtle(that.blockList[thisBlock], blkInfo);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, blkInfo[1]], collapsed);
break;
case 'drum':
blkData[4][0] = null;
blkData[4][2] = null;
postProcess = function (args) {
var thisBlock = args[0];
var blkInfo = args[1];
that.blockList[thisBlock].value = that.turtles.turtleList.length;
that.turtles.addDrum(that.blockList[thisBlock], blkInfo);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, blkInfo[1]], collapsed);
if (_THIS_IS_MUSIC_BLOCKS_) {
// Load the synth for this drum
this.logo.synth.loadSynth('kick');
}
break;
case 'action':
case 'hat':
blkData[4][0] = null;
blkData[4][3] = null;
this._makeNewBlockWithConnections('action', blockOffset, blkData[4], null, null, collapsed);
break;
// Named boxes and dos need private data.
case 'namedbox':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].privateData = value;
that.blockList[thisBlock].value = null;
};
this._makeNewBlockWithConnections('namedbox', blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'namedarg':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].privateData = value;
that.blockList[thisBlock].value = null;
};
this._makeNewBlockWithConnections('namedarg', blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'namedcalc':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].privateData = value;
that.blockList[thisBlock].value = null;
};
this._makeNewBlockWithConnections('namedcalc', blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'nameddo':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].privateData = value;
that.blockList[thisBlock].value = null;
};
this._makeNewBlockWithConnections('nameddo', blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
// Arg clamps may need extra slots added.
case 'doArg':
postProcess = function (args) {
var thisBlock = args[0];
var extraSlots = args[1].length - 4;
if (extraSlots > 0) {
var slotList = that.blockList[thisBlock].argClampSlots;
for (var i = 0; i < extraSlots; i++) {
slotList.push(1);
that._newLocalArgBlock(slotList.length);
that.blockList[thisBlock].connections.push(null);
}
that.blockList[thisBlock].updateArgSlots(slotList);
for (var i = 0; i < args[1].length; i++) {
if (args[1][i] != null) {
that.blockList[thisBlock].connections[i] = args[1][i] + firstBlock;
} else {
that.blockList[thisBlock].connections[i] = args[1][i];
}
}
}
that._checkArgClampBlocks.push(thisBlock);
};
this._makeNewBlockWithConnections('doArg', blockOffset, blkData[4], postProcess, [thisBlock, blkData[4]]);
break;
case 'nameddoArg':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].privateData = value;
that.blockList[thisBlock].value = null;
var extraSlots = args[2].length - 3;
if (extraSlots > 0) {
var slotList = that.blockList[thisBlock].argClampSlots;
for (var i = 0; i < extraSlots; i++) {
slotList.push(1);
that._newLocalArgBlock(slotList.length);
that.blockList[thisBlock].connections.push(null);
}
that.blockList[thisBlock].updateArgSlots(slotList);
for (var i = 0; i < args[2].length; i++) {
if (args[2][i] != null) {
that.blockList[thisBlock].connections[i] = args[2][i] + firstBlock;
} else {
that.blockList[thisBlock].connections[i] = args[2][i];
}
}
}
that._checkArgClampBlocks.push(thisBlock);
};
this._makeNewBlockWithConnections('nameddoArg', blockOffset, blkData[4], postProcess, [thisBlock, value, blkData[4]]);
break;
case 'calcArg':
postProcess = function (args) {
var thisBlock = args[0];
var extraSlots = args[1].length - 3;
if (extraSlots > 0) {
var slotList = that.blockList[thisBlock].argClampSlots;
for (var i = 0; i < extraSlots; i++) {
slotList.push(1);
that._newLocalArgBlock(slotList.length);
that.blockList[thisBlock].connections.push(null);
}
that.blockList[thisBlock].updateArgSlots(slotList);
for (var i = 0; i < args[1].length; i++) {
if (args[1][i] != null) {
that.blockList[thisBlock].connections[i] = args[1][i] + firstBlock;
} else {
that.blockList[thisBlock].connections[i] = args[1][i];
}
}
}
that._checkArgClampBlocks.push(thisBlock);
};
this._makeNewBlockWithConnections('calcArg', blockOffset, blkData[4], postProcess, [thisBlock, blkData[4]]);
break;
case 'namedcalcArg':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].privateData = value;
that.blockList[thisBlock].value = null;
var extraSlots = args[2].length - 2;
if (extraSlots > 0) {
var slotList = that.blockList[thisBlock].argClampSlots;
for (var i = 0; i < extraSlots; i++) {
slotList.push(1);
that._newLocalArgBlock(slotList.length);
that.blockList[thisBlock].connections.push(null);
}
that.blockList[thisBlock].updateArgSlots(slotList);
for (var i = 0; i < args[2].length; i++) {
if (args[2][i] != null) {
that.blockList[thisBlock].connections[i] = args[2][i] + firstBlock;
} else {
that.blockList[thisBlock].connections[i] = args[2][i];
}
}
}
that._checkArgClampBlocks.push(thisBlock);
};
this._makeNewBlockWithConnections('namedcalcArg', blockOffset, blkData[4], postProcess, [thisBlock, value, blkData[4]]);
break;
// Value blocks need a default value set.
case 'number':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = Number(value);
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'text':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'solfege':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'eastindiansolfege':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'notename':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'modename':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'drumname':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
if (_THIS_IS_MUSIC_BLOCKS_) {
// Load the synth for this drum
this.logo.synth.loadSynth(getDrumSynthName(value));
}
break;
case 'voicename':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
if (_THIS_IS_MUSIC_BLOCKS_) {
// Load the synth for this voice
this.logo.synth.loadSynth(getVoiceSynthName(value));
}
break;
case 'media':
// Load a thumbnail into a media blocks.
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = value;
if (value != null) {
// Load artwork onto media block.
that.blockList[thisBlock].loadThumbnail(null);
}
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'camera':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = CAMERAVALUE;
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
case 'video':
postProcess = function (args) {
var thisBlock = args[0];
var value = args[1];
that.blockList[thisBlock].value = VIDEOVALUE;
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
// Define some constants for legacy blocks for
// backward compatibility with Python projects.
case 'red':
case 'black':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = 0;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'white':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = 100;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'orange':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = 10;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'yellow':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = 20;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'green':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = 40;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'blue':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = 70;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'leftpos':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = -(canvas.width / 2);
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'rightpos':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = (canvas.width / 2);
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'toppos':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = (canvas.height / 2);
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'botpos':
case 'bottompos':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = -(canvas.height / 2);
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'width':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = canvas.width;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'height':
postProcess = function (thisBlock) {
that.blockList[thisBlock].value = canvas.height;
that.updateBlockText(thisBlock);
};
this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
break;
case 'loadFile':
postProcess = function (args) {
that.blockList[args[0]].value = args[1];
that.updateBlockText(args[0]);
};
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
break;
default:
// Check that name is in the proto list
if (!name in this.protoBlockDict || this.protoBlockDict[name] == null) {
// Lots of assumptions here.
// TODO: figure out if it is a flow or an arg block.
// Substitute a NOP block for an unknown block.
n = blkData[4].length;
console.log(n + ': substituting nop block for ' + name);
switch (n) {
case 1:
name = 'nopValueBlock';
break;
case 2:
name = 'nopZeroArgBlock';
break;
case 3:
name = 'nopOneArgBlock';
break;
case 4:
name = 'nopTwoArgBlock';
break;
case 5:
default:
name = 'nopThreeArgBlock';
break;
}
}
this._makeNewBlockWithConnections(name, blockOffset, blkData[4], null);
break;
}
if (thisBlock === this.blockList.length - 1) {
if (this.blockList[thisBlock].connections[0] == null) {
this.blockList[thisBlock].container.x = blkData[2];
this.blockList[thisBlock].container.y = blkData[3];
this._adjustTheseDocks.push(thisBlock);
if (blkData[4][0] == null) {
this._adjustTheseStacks.push(thisBlock);
}
if (blkData[2] < 0 || blkData[3] < 0 || blkData[2] > canvas.width || blkData[3] > canvas.height) {
this._homeButtonContainers[0].visible = true;
this._homeButtonContainers[1].visible = false;
}
}
}
}
};
this.cleanupAfterLoad = function (name) {
// If all the blocks are loaded, we can make the final adjustments.
this._loadCounter -= 1;
if (this._loadCounter > 0) {
return;
}
this._findDrumURLs();
this.updateBlockPositions();
this._cleanupStacks();
for (var i = 0; i < this.blocksToCollapse.length; i++) {
this.blockList[this.blocksToCollapse[i]].collapseToggle();
}
this.blocksToCollapse = [];
for (var blk = 0; blk < this.blockList.length; blk++) {
if (this.blockList[blk].collapseContainer != null) {
this.blockList[blk].collapseContainer.x = this.blockList[blk].container.x + COLLAPSEBUTTONXOFF * (this.blockList[blk].protoblock.scale / 2);
this.blockList[blk].collapseContainer.y = this.blockList[blk].container.y + COLLAPSEBUTTONYOFF * (this.blockList[blk].protoblock.scale / 2);
}
}
this.refreshCanvas();
// Do a final check on the action and boxes palettes.
var updatePalettes = false;
for (var blk = 0; blk < this.blockList.length; blk++) {
if (!this.blockList[blk].trash && this.blockList[blk].name === 'action') {
var myBlock = this.blockList[blk];
var arg = null;
var c = myBlock.connections[1];
if (c != null && this.blockList[c].value !== _('action')) {
if (this.newNameddoBlock(this.blockList[c].value, this.actionHasReturn(blk), this.actionHasArgs(blk))) {
updatePalettes = true;
}
}
}
}
if (updatePalettes) {
this.palettes.hide();
this.palettes.updatePalettes('action');
// this.palettes.dict['action'].hide();
this.palettes.show();
}
var updatePalettes = false;
for (var blk = 0; blk < this.blockList.length; blk++) {
if (!this.blockList[blk].trash && this.blockList[blk].name === 'storein') {
var myBlock = this.blockList[blk];
var arg = null;
var c = myBlock.connections[1];
if (c != null && this.blockList[c].value !== _('box')) {
var name = this.blockList[c].value;
// Is there an old block with this name still around?
if (this.protoBlockDict['myStorein_' + name] == undefined) {
console.log('adding new storein block ' + name);
this.newNamedboxBlock(this.blockList[c].value);
this.newStoreinBlock(this.blockList[c].value);
updatePalettes = true;
}
}
}
}
if (updatePalettes) {
// Do this update on a slight delay so as not to collide with
// the actions update.
var that = this;
setTimeout(function () {
that.palettes.hide();
that.palettes.updatePalettes('boxes');
// that.palettes.dict['boxes'].hide();
that.palettes.show();
}, 1500);
}
console.log("Finished block loading");
var myCustomEvent = new Event('finishedLoading');
document.dispatchEvent(myCustomEvent);
};
this._cleanupStacks = function () {
if (this._checkArgClampBlocks.length > 0) {
// We make multiple passes because we need to account for nesting.
// FIXME: needs to be interwoven with TwoArgBlocks check.
for (var i = 0; i < this._checkArgClampBlocks.length; i++) {
for (var b = 0; b < this._checkArgClampBlocks.length; b++) {
this._adjustArgClampBlock([this._checkArgClampBlocks[b]]);
}
}
}
if (this._checkTwoArgBlocks.length > 0) {
// We make multiple passes because we need to account for nesting.
for (var i = 0; i < this._checkTwoArgBlocks.length; i++) {
for (var b = 0; b < this._checkTwoArgBlocks.length; b++) {
this._adjustExpandableTwoArgBlock([this._checkTwoArgBlocks[b]]);
}
}
}
for (var blk = 0; blk < this._adjustTheseDocks.length; blk++) {
// console.log('Adjust Docks: ' + this.blockList[this._adjustTheseDocks[blk]].name);
this.adjustDocks(this._adjustTheseDocks[blk], true);
// blockBlocks._expandTwoArgs();
this._expandClamps();
}
for (var blk = 0; blk < this._adjustTheseStacks.length; blk++) {
// console.log('Adjust Stack: ' + this.blockList[this._adjustTheseStacks[blk]].name);
this.raiseStackToTop(this._adjustTheseStacks[blk]);
}
};
this.actionHasReturn = function (blk) {
// Look for a return block in an action stack.
if (this.blockList[blk].name !== 'action') {
return false;
}
this.findDragGroup(blk);
for (var b = 0; b < this.dragGroup.length; b++) {
if (this.blockList[this.dragGroup[b]].name === 'return') {
return true;
}
}
return false;
};
this.actionHasArgs = function (blk) {
// Look for an arg blocks in an action stack.
if (this.blockList[blk].name !== 'action') {
return false;
}
this.findDragGroup(blk);
for (var b = 0; b < this.dragGroup.length; b++) {
if (this.blockList[this.dragGroup[b]].name === 'arg' || this.blockList[this.dragGroup[b]].name === 'namedarg') {
return true;
}
}
return false;
};
this.raiseStackToTop = function (blk) {
// Move the stack associated with blk to the top.
var topBlk = this.findTopBlock(blk);
this.findDragGroup(topBlk);
var z = this.stage.getNumChildren() - 1;
for (var b = 0; b < this.dragGroup.length; b++) {
this.stage.setChildIndex(this.blockList[this.dragGroup[b]].container, z);
z -= 1;
}
this.refreshCanvas;
};
this.deleteActionBlock = function (myBlock) {
var actionArg = this.blockList[myBlock.connections[1]];
if (actionArg) {
var actionName = actionArg.value;
for (var blk = 0; blk < this.blockList.length; blk++) {
var myBlock = this.blockList[blk];
var blkParent = this.blockList[myBlock.connections[0]];
if (blkParent == null) {
continue;
}
if (['namedcalc', 'calc', 'nameddo', 'do', 'action'].indexOf(blkParent.name) !== -1) {
continue;
}
var blockValue = myBlock.value;
if (blockValue === _('action')) {
continue;
}
if (blockValue === actionName) {
blkParent.hide();
myBlock.hide();
myBlock.trash = true;
blkParent.trash = true;
}
}
// Avoid palette refreash race condition.
this.deleteActionTimeout += 500;
var timeout = this.deleteActionTimeout;
var that = this;
setTimeout(function () {
that.deleteActionTimeout -= 500;
that.palettes.removeActionPrototype(actionName);
}, timeout);
}
};
this.sendStackToTrash = function (myBlock) {
// First, hide the palettes as they will need updating.
for (var name in this.palettes.dict) {
this.palettes.dict[name].hideMenu(true);
}
this.refreshCanvas();
var thisBlock = this.blockList.indexOf(myBlock);
// Add this block to the list of blocks in the trash so we can
// undo this action.
this.trashStacks.push(thisBlock);
// Disconnect block.
var parentBlock = myBlock.connections[0];
if (parentBlock != null) {
for (var c in this.blockList[parentBlock].connections) {
if (this.blockList[parentBlock].connections[c] === thisBlock) {
this.blockList[parentBlock].connections[c] = null;
break;
}
}
myBlock.connections[0] = null;
// Add default block if user deletes all blocks from inside the note block
this.addDefaultBlock(parentBlock, thisBlock);
}
if (myBlock.name === 'start' || myBlock.name === 'drum') {
turtle = myBlock.value;
var turtleNotInTrash = 0;
for (var i = 0; i < this.turtles.turtleList.length; i++) {
if (!this.turtles.turtleList[i].trash) {
turtleNotInTrash += 1;
}
}
if (turtle != null && turtleNotInTrash > 1) {
console.log('putting turtle ' + turtle + ' in the trash');
this.turtles.turtleList[turtle].trash = true;
this.turtles.turtleList[turtle].container.visible = false;
} else {
this.errorMsg("You must always have at least one start block");
console.log('null turtle');
return;
}
} else if (myBlock.name === 'action') {
if (!myBlock.trash) {
this.deleteActionBlock(myBlock);
}
}
// put drag group in trash
this.findDragGroup(thisBlock);
for (var b = 0; b < this.dragGroup.length; b++) {
var blk = this.dragGroup[b];
// console.log('putting ' + this.blockList[blk].name + ' in the trash');
this.blockList[blk].trash = true;
this.blockList[blk].hide();
this.refreshCanvas();
}
// Adjust the stack from which we just deleted blocks.
if (parentBlock != null) {
var topBlk = this.findTopBlock(parentBlock);
this.findDragGroup(topBlk);
// We need to track two-arg blocks in case they need expanding.
this._checkTwoArgBlocks = [];
// And arg clamp blocks in case they need expanding.
this._checkArgClampBlocks = [];
for (var b = 0; b < this.dragGroup.length; b++) {
var blk = this.dragGroup[b];
var myBlock = this.blockList[blk];
if (myBlock.isTwoArgBlock()) {
this._checkTwoArgBlocks.push(blk);
} else if (myBlock.isArgBlock() && myBlock.isExpandableBlock() || myBlock.isArgClamp()) {
this._checkTwoArgBlocks.push(blk);
} else if (['clamp', 'argclamp', 'argclamparg', 'doubleclamp', 'argflowclamp'].indexOf(myBlock.protoblock.style) !== -1) {
this._checkArgClampBlocks.push(blk);
}
}
this._cleanupStacks();
this.refreshCanvas();
}
};
return this;
};