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

  1. // Copyright (c) 2014-17 Walter Bender
  2. //
  3. // This program is free software; you can redistribute it and/or
  4. // modify it under the terms of the The GNU Affero General Public
  5. // License as published by the Free Software Foundation; either
  6. // version 3 of the License, or (at your option) any later version.
  7. //
  8. // You should have received a copy of the GNU Affero General Public
  9. // License along with this library; if not, write to the Free Software
  10. // Foundation, 51 Franklin Street, Suite 500 Boston, MA 02110-1335 USA
  11. // Minimum distance (squared) between two docks required before
  12. // connecting them.
  13. const MINIMUMDOCKDISTANCE = 400;
  14. // Special value flags to uniquely identify these media blocks.
  15. const CAMERAVALUE = '##__CAMERA__##';
  16. const VIDEOVALUE = '##__VIDEO__##';
  17. // Blocks holds the list of blocks and most of the block-associated
  18. // methods, since most block manipulations are inter-block.
  19. function Blocks () {
  20. if (sugarizerCompatibility.isInsideSugarizer()) {
  21. storage = sugarizerCompatibility.data;
  22. } else {
  23. storage = localStorage;
  24. }
  25. this.canvas = null;
  26. this.stage = null;
  27. this.refreshCanvas = null;
  28. this.trashcan = null;
  29. this.updateStage = null;
  30. this.getStageScale = null;
  31. // We keep a list of stacks in the trash.
  32. this.trashStacks = [];
  33. // We keep a dictionary for the proto blocks,
  34. this.protoBlockDict = {}
  35. // and a list of the blocks we create.
  36. this.blockList = [];
  37. // Track the time with mouse down.
  38. this.mouseDownTime = 0;
  39. this.longPressTimeout = null;
  40. // "Copy stack" selects a stack for pasting. Are we selecting?
  41. this.selectingStack = false;
  42. // and what did we select?
  43. this.selectedStack = null;
  44. // and a copy of the selected stack for pasting.
  45. this.selectedBlocksObj = null;
  46. // If we somehow have a malformed block database (for example,
  47. // from importing a corrupted datafile, we need to avoid infinite
  48. // loops while crawling the block list.
  49. this._loopCounter = 0;
  50. this._sizeCounter = 0;
  51. this._searchCounter = 0;
  52. // We need a reference to the palettes.
  53. this.palettes = null;
  54. // Which block, if any, is highlighted?
  55. this.highlightedBlock = null;
  56. // Which block, if any, is active?
  57. this.activeBlock = null;
  58. // Are the blocks visible?
  59. this.visible = true;
  60. // The group of blocks being dragged or moved together
  61. this.dragGroup = [];
  62. // The blocks at the tops of stacks
  63. this.stackList = [];
  64. // The blocks that need expanding
  65. this._expandablesList = [];
  66. // Number of blocks to load
  67. this._loadCounter = 0;
  68. // Stacks of blocks that need adjusting as blocks are repositioned
  69. // due to expanding and contracting or insertion into the flow.
  70. this._adjustTheseDocks = [];
  71. // Blocks that need collapsing after load.
  72. this.blocksToCollapse = [];
  73. // Arg blocks that need expanding after load.
  74. this._checkTwoArgBlocks = [];
  75. // Arg clamp blocks that need expanding after load.
  76. this._checkArgClampBlocks = [];
  77. // Clamp blocks that need expanding after load.
  78. this._clampBlocksToCheck = [];
  79. // We need to keep track of certain classes of blocks that exhibit
  80. // different types of behavior.
  81. // Blocks with parts that expand, e.g.,
  82. this._expandableBlocks = [];
  83. // Blocks that contain child flows of blocks
  84. this.clampBlocks = [];
  85. this.doubleExpandable = [];
  86. this.argClampBlocks = [];
  87. // Blocks that are used as arguments to other blocks
  88. this.argBlocks = [];
  89. // Blocks that return values
  90. this.valueBlocks = [];
  91. // Two-arg blocks with two arguments (expandable).
  92. this.twoArgBlocks = [];
  93. // Blocks that don't run when clicked.
  94. this.noRunBlocks = [];
  95. this._homeButtonContainers = [];
  96. this.blockScale = DEFAULTBLOCKSCALE;
  97. // We need to know if we are processing a copy or save stack command.
  98. this.inLongPress = false;
  99. // We stage deletion of prototype action blocks on the palette so
  100. // as to avoid palette refresh race conditions.
  101. this.deleteActionTimeout = 0;
  102. this.setCanvas = function (canvas) {
  103. this.canvas = canvas;
  104. return this;
  105. };
  106. this.setStage = function (stage) {
  107. this.stage = stage;
  108. return this;
  109. };
  110. this.setRefreshCanvas = function (refreshCanvas) {
  111. this.refreshCanvas = refreshCanvas;
  112. return this;
  113. };
  114. this.setTrashcan = function (trashcan) {
  115. this.trashcan = trashcan;
  116. return this;
  117. };
  118. this.setUpdateStage = function (updateStage) {
  119. this.updateStage = updateStage;
  120. return this;
  121. };
  122. this.setGetStageScale = function (getStageScale) {
  123. this.getStageScale = getStageScale;
  124. return this;
  125. };
  126. // Change the scale of the blocks (and the protoblocks on the palette).
  127. this.setBlockScale = function (scale) {
  128. console.log('New block scale is ' + scale);
  129. this.blockScale = scale;
  130. // Regenerate all of the artwork at the new scale.
  131. for (var blk = 0; blk < this.blockList.length; blk++) {
  132. // if (!this.blockList[blk].trash) {
  133. this.blockList[blk].resize(scale);
  134. // }
  135. }
  136. this.findStacks();
  137. for (var stack = 0; stack < this.stackList.length; stack++) {
  138. // console.log('Adjust Docks: ' + this.blockList[this.stackList[stack]].name);
  139. this.adjustDocks(this.stackList[stack], true);
  140. }
  141. // We reset the protoblock scale on the palettes, but don't
  142. // modify the palettes themselves.
  143. for (var palette in this.palettes.dict) {
  144. for (var blk = 0; blk < this.palettes.dict[palette].protoList.length; blk++) {
  145. this.palettes.dict[palette].protoList[blk].scale = scale;
  146. }
  147. }
  148. };
  149. // We need access to the msg block...
  150. this.setMsgText = function (msgText) {
  151. this.msgText = msgText;
  152. };
  153. // and the Error msg function.
  154. this.setErrorMsg = function (errorMsg) {
  155. this.errorMsg = errorMsg;
  156. return this;
  157. };
  158. // We need access to the macro dictionary because we add to it.
  159. this.setMacroDictionary = function (obj) {
  160. this.macroDict = obj;
  161. return this;
  162. };
  163. // We need access to the turtles list because we associate a
  164. // turtle with each start block.
  165. this.setTurtles = function (turtles) {
  166. this.turtles = turtles;
  167. return this;
  168. };
  169. // We need to access the "pseudo-Logo interpreter" when we click
  170. // on blocks.
  171. this.setLogo = function (logo) {
  172. this.logo = logo;
  173. return this;
  174. };
  175. // The scale of the graphics is determined by screen size.
  176. this.setScale = function (scale) {
  177. this.blockScale = scale;
  178. return this;
  179. };
  180. // Toggle state of collapsible blocks.
  181. this.toggleCollapsibles = function () {
  182. for (var blk in this.blockList) {
  183. var myBlock = this.blockList[blk];
  184. if (COLLAPSABLES.indexOf(myBlock.name) !== -1 && !myBlock.trash) {
  185. myBlock.collapseToggle();
  186. }
  187. }
  188. };
  189. // We need access to the go-home buttons and boundary.
  190. this.setHomeContainers = function (containers, boundary) {
  191. this._homeButtonContainers = containers;
  192. this.boundary = boundary;
  193. return this;
  194. };
  195. // set up copy/paste, dismiss, and copy-stack buttons
  196. this.makeCopyPasteButtons = function (makeButton, updatePasteButton) {
  197. var that = this;
  198. this.updatePasteButton = updatePasteButton;
  199. this.dismissButton = makeButton('cancel-button', '', 0, 0, 55, 0, this.stage);
  200. this.dismissButton.visible = false;
  201. this.saveStackButton = makeButton('save-blocks-button', _('Save stack'), 0, 0, 55, 0, this.stage);
  202. this.saveStackButton.visible = false;
  203. this.dismissButton.on('click', function (event) {
  204. that.saveStackButton.visible = false;
  205. that.dismissButton.visible = false;
  206. that.inLongPress = false;
  207. that.refreshCanvas();
  208. });
  209. this.saveStackButton.on('click', function (event) {
  210. // Only invoked from action blocks.
  211. var topBlock = that.findTopBlock(that.activeBlock);
  212. that.inLongPress = false;
  213. that.selectedStack = topBlock;
  214. that.saveStackButton.visible = false;
  215. that.dismissButton.visible = false;
  216. that.saveStack();
  217. that.refreshCanvas();
  218. });
  219. };
  220. // Walk through all of the proto blocks in order to make lists of
  221. // any blocks that need special treatment.
  222. this.findBlockTypes = function () {
  223. for (var proto in this.protoBlockDict) {
  224. if (this.protoBlockDict[proto].expandable) {
  225. this._expandableBlocks.push(this.protoBlockDict[proto].name);
  226. }
  227. if (this.protoBlockDict[proto].style === 'clamp') {
  228. this.clampBlocks.push(this.protoBlockDict[proto].name);
  229. }
  230. if (this.protoBlockDict[proto].style === 'argclamp') {
  231. this.argClampBlocks.push(this.protoBlockDict[proto].name);
  232. }
  233. if (this.protoBlockDict[proto].style === 'argflowclamp') {
  234. this.clampBlocks.push(this.protoBlockDict[proto].name);
  235. }
  236. if (this.protoBlockDict[proto].style === 'argclamparg') {
  237. this.argClampBlocks.push(this.protoBlockDict[proto].name);
  238. this.argBlocks.push(this.protoBlockDict[proto].name);
  239. }
  240. if (this.protoBlockDict[proto].style === 'twoarg') {
  241. this.twoArgBlocks.push(this.protoBlockDict[proto].name);
  242. }
  243. if (this.protoBlockDict[proto].style === 'arg') {
  244. this.argBlocks.push(this.protoBlockDict[proto].name);
  245. }
  246. if (this.protoBlockDict[proto].style === 'value') {
  247. this.argBlocks.push(this.protoBlockDict[proto].name);
  248. this.valueBlocks.push(this.protoBlockDict[proto].name);
  249. }
  250. if (this.protoBlockDict[proto].style === 'doubleclamp') {
  251. this.doubleExpandable.push(this.protoBlockDict[proto].name);
  252. }
  253. }
  254. };
  255. this._actionBlock = function (name) {
  256. return ['do', 'doArg', 'calc', 'calcArg'].indexOf(name) !== -1;
  257. };
  258. this._namedActionBlock = function (name) {
  259. return ['nameddo', 'nameddoArg', 'namedcalc', 'namedcalcArg'].indexOf(name) !== -1;
  260. };
  261. // Adjust the docking postions of all blocks in the current drag
  262. // group.
  263. this._adjustBlockPositions = function () {
  264. if (this.dragGroup.length < 2) {
  265. return;
  266. }
  267. // console.log('Adjust Docks: ' + this.blockList[this.dragGroup[0]].name);
  268. this.adjustDocks(this.dragGroup[0], true)
  269. };
  270. // Adjust the size of the clamp in an expandable block when blocks
  271. // are inserted into (or removed from) the child flow. This is a
  272. // common operation for start and action blocks, but also for
  273. // repeat, forever, if, etc.
  274. this._adjustExpandableClampBlock = function () {
  275. if (this._clampBlocksToCheck.length === 0) {
  276. return;
  277. }
  278. var obj = this._clampBlocksToCheck.pop();
  279. var blk = obj[0];
  280. var clamp = obj[1];
  281. var myBlock = this.blockList[blk];
  282. if (myBlock.isArgFlowClampBlock()) {
  283. // Make sure myBlock is a clamp block.
  284. } else if (myBlock.isArgBlock() || myBlock.isTwoArgBlock()) {
  285. return;
  286. } else if (myBlock.isArgClamp()) {
  287. // We handle ArgClamp blocks elsewhere.
  288. this._adjustArgClampBlock([blk]);
  289. return;
  290. }
  291. function clampAdjuster(blocks, blk, myBlock, clamp) {
  292. // First we need to count up the number of (and size of) the
  293. // blocks inside the clamp; The child flow is usually the
  294. // second-to-last argument.
  295. if (myBlock.isArgFlowClampBlock()) {
  296. var c = 1; // 0: outie; and 1: child flow
  297. } else if (clamp === 0) {
  298. var c = myBlock.connections.length - 2;
  299. } else { // e.g., Bottom clamp in if-then-else
  300. var c = myBlock.connections.length - 3;
  301. }
  302. blocks._sizeCounter = 0;
  303. var childFlowSize = 1;
  304. if (c > 0 && myBlock.connections[c] != null) {
  305. childFlowSize = Math.max(blocks._getStackSize(myBlock.connections[c]), 1);
  306. }
  307. // Adjust the clamp size to match the size of the child
  308. // flow.
  309. var plusMinus = childFlowSize - myBlock.clampCount[clamp];
  310. if (plusMinus !== 0) {
  311. if (!(childFlowSize === 0 && myBlock.clampCount[clamp] === 1)) {
  312. myBlock.updateSlots(clamp, plusMinus);
  313. }
  314. }
  315. // Recurse through the list.
  316. setTimeout(function () {
  317. if (blocks._clampBlocksToCheck.length > 0) {
  318. blocks._adjustExpandableClampBlock();
  319. }
  320. }, 250);
  321. };
  322. clampAdjuster(this, blk, myBlock, clamp);
  323. };
  324. // Returns the block size.
  325. this._getBlockSize = function (blk) {
  326. var myBlock = this.blockList[blk];
  327. return myBlock.size;
  328. };
  329. // Adjust the slot sizes of arg clamps.
  330. this._adjustArgClampBlock = function (argBlocksToCheck) {
  331. if (argBlocksToCheck.length === 0) {
  332. return;
  333. }
  334. var blk = argBlocksToCheck.pop();
  335. var myBlock = this.blockList[blk];
  336. // Which connection do we start with?
  337. if (['doArg', 'calcArg'].indexOf(myBlock.name) !== -1) {
  338. var ci = 2;
  339. } else {
  340. var ci = 1;
  341. }
  342. // Get the current slot list.
  343. var slotList = myBlock.argClampSlots;
  344. var update = false;
  345. // Determine the size of each argument.
  346. for (var i = 0; i < slotList.length; i++) {
  347. var c = myBlock.connections[ci + i];
  348. var size = 1; // Minimum size
  349. if (c != null) {
  350. size = Math.max(this._getBlockSize(c), 1);
  351. }
  352. if (slotList[i] !== size) {
  353. slotList[i] = size;
  354. update = true;
  355. }
  356. }
  357. if (update) {
  358. myBlock.updateArgSlots(slotList);
  359. }
  360. };
  361. // We also adjust the size of twoarg blocks. It is similar to how
  362. // we adjust clamps, but enough different that it is in its own
  363. // function.
  364. this._adjustExpandableTwoArgBlock = function (argBlocksToCheck) {
  365. if (argBlocksToCheck.length === 0) {
  366. return;
  367. }
  368. var blk = argBlocksToCheck.pop();
  369. var myBlock = this.blockList[blk];
  370. // Determine the size of the first argument.
  371. var c = myBlock.connections[1];
  372. var firstArgumentSize = 1; // Minimum size
  373. if (c != null) {
  374. firstArgumentSize = Math.max(this._getBlockSize(c), 1);
  375. }
  376. // Expand/contract block by plusMinus.
  377. var plusMinus = firstArgumentSize - myBlock.clampCount[0];
  378. if (plusMinus !== 0) {
  379. if (!(firstArgumentSize === 0)) {
  380. myBlock.updateSlots(0, plusMinus);
  381. }
  382. }
  383. };
  384. this._addRemoveVspaceBlock = function (blk) {
  385. var myBlock = this.blockList[blk];
  386. var c = myBlock.connections[myBlock.connections.length - 2];
  387. var secondArgumentSize = 1;
  388. if (c != null) {
  389. var secondArgumentSize = Math.max(this._getBlockSize(c), 1);
  390. }
  391. var that = this;
  392. var vSpaceCount = howManyVSpaceBlocksBelow(blk);
  393. if (secondArgumentSize < vSpaceCount + 1) {
  394. // Remove a vspace block
  395. var n = Math.abs(secondArgumentSize - vSpaceCount - 1);
  396. for (var i = 0; i < n; i++) {
  397. var lastConnection = myBlock.connections.length - 1;
  398. var vspaceBlock = this.blockList[myBlock.connections[lastConnection]];
  399. var nextBlockIndex = vspaceBlock.connections[1];
  400. myBlock.connections[lastConnection] = nextBlockIndex;
  401. if (nextBlockIndex != null) {
  402. this.blockList[nextBlockIndex].connections[0] = blk;
  403. }
  404. vspaceBlock.connections = [null, null];
  405. vspaceBlock.trash = true;
  406. vspaceBlock.hide();
  407. }
  408. } else if (secondArgumentSize > vSpaceCount + 1) {
  409. // Add vspace blocks
  410. var n = secondArgumentSize - vSpaceCount - 1;
  411. var nextBlock = last(myBlock.connections);
  412. var thisBlock = myBlock;
  413. var newPos = this.blockList.length;
  414. var that = this;
  415. function vspaceAdjuster(args) { // nextBlock, vspace, i, n
  416. var thisBlock = args[0];
  417. var nextBlock = args[1];
  418. var vspace = args[2];
  419. var i = args[3];
  420. var n = args[4];
  421. var vspaceBlock = that.blockList[vspace];
  422. var lastDock = last(thisBlock.docks);
  423. var dx = lastDock[0] - vspaceBlock.docks[0][0];
  424. var dy = lastDock[1] - vspaceBlock.docks[0][1];
  425. vspaceBlock.container.x = thisBlock.container.x + dx;
  426. vspaceBlock.container.y = thisBlock.container.y + dy;
  427. vspaceBlock.connections[0] = that.blockList.indexOf(thisBlock);
  428. vspaceBlock.connections[1] = nextBlock;
  429. thisBlock.connections[thisBlock.connections.length - 1] = vspace;
  430. if (nextBlock) {
  431. that.blockList[nextBlock].connections[0] = vspace;
  432. }
  433. if (i + 1 < n) {
  434. var newPos = that.blockList.length;
  435. thisBlock = last(that.blockList);
  436. nextBlock = last(thisBlock.connections);
  437. that._makeNewBlockWithConnections('vspace', newPos, [null, null], vspaceAdjuster, [thisBlock, nextBlock, newPos, i + 1, n]);
  438. }
  439. };
  440. this._makeNewBlockWithConnections('vspace', newPos, [null, null], vspaceAdjuster, [thisBlock, nextBlock, newPos, 0, n]);
  441. };
  442. function howManyVSpaceBlocksBelow(blk) {
  443. // Need to know how many vspace blocks are below the block
  444. // we're checking against.
  445. var nextBlock = last(that.blockList[blk].connections);
  446. if (nextBlock && that.blockList[nextBlock].name === 'vspace') {
  447. return 1 + howManyVSpaceBlocksBelow(nextBlock);
  448. // Recurse until it isn't a vspace
  449. }
  450. return 0;
  451. };
  452. };
  453. this._getStackSize = function (blk) {
  454. // How many block units in this stack?
  455. var size = 0;
  456. this._sizeCounter += 1;
  457. if (this._sizeCounter > this.blockList.length * 2) {
  458. console.log('Infinite loop encountered detecting size of expandable block? ' + blk);
  459. return size;
  460. }
  461. if (blk == null) {
  462. return size;
  463. }
  464. var myBlock = this.blockList[blk];
  465. if (myBlock == null) {
  466. console.log('Something very broken in _getStackSize.');
  467. }
  468. if (myBlock.isClampBlock()) {
  469. var c = myBlock.connections.length - 2;
  470. var csize = 0;
  471. if (c > 0) {
  472. var cblk = myBlock.connections[c];
  473. if (cblk != null) {
  474. csize = this._getStackSize(cblk);
  475. }
  476. if (csize === 0) {
  477. size = 1; // minimum of 1 slot in clamp
  478. } else {
  479. size = csize;
  480. }
  481. }
  482. if (myBlock.isDoubleClampBlock()) {
  483. var c = myBlock.connections.length - 3;
  484. var csize = 0;
  485. if (c > 0) {
  486. var cblk = myBlock.connections[c];
  487. if (cblk != null) {
  488. csize = this._getStackSize(cblk);
  489. }
  490. if (csize === 0) {
  491. size += 1; // minimum of 1 slot in clamp
  492. } else {
  493. size += csize;
  494. }
  495. }
  496. }
  497. // add top and bottom of clamp
  498. size += myBlock.size;
  499. } else {
  500. size = myBlock.size;
  501. }
  502. // check on any connected block
  503. if (!myBlock.isValueBlock()) {
  504. var cblk = last(myBlock.connections);
  505. if (cblk != null) {
  506. size += this._getStackSize(cblk);
  507. }
  508. }
  509. return size;
  510. };
  511. this.adjustDocks = function (blk, resetLoopCounter) {
  512. // Give a block, adjust the dock positions
  513. // of all of the blocks connected to it
  514. var myBlock = this.blockList[blk];
  515. // For when we come in from makeBlock
  516. if (resetLoopCounter != null) {
  517. this._loopCounter = 0;
  518. }
  519. // These checks are to test for malformed data. All blocks
  520. // should have connections.
  521. if (myBlock == null) {
  522. console.log('Saw a null block: ' + blk);
  523. return;
  524. }
  525. if (myBlock.connections == null) {
  526. console.log('Saw a block with null connections: ' + blk);
  527. return;
  528. }
  529. if (myBlock.connections.length === 0) {
  530. console.log('Saw a block with [] connections: ' + blk);
  531. return;
  532. }
  533. // Value blocks only have one dock.
  534. if (myBlock.docks.length === 1) {
  535. return;
  536. }
  537. this._loopCounter += 1;
  538. if (this._loopCounter > this.blockList.length * 2) {
  539. console.log('Infinite loop encountered while adjusting docks: ' + blk + ' ' + this.blockList);
  540. return;
  541. }
  542. // Walk through each connection except the parent block; the
  543. // exception being the parent block of boolean 2arg blocks,
  544. // since the dock[0] position can change.
  545. if (myBlock.isTwoArgBooleanBlock()) {
  546. var start = 0;
  547. } else {
  548. var start = 1;
  549. }
  550. for (var c = start; c < myBlock.connections.length; c++) {
  551. // Get the dock position for this connection.
  552. var bdock = myBlock.docks[c];
  553. // Find the connecting block.
  554. var cblk = myBlock.connections[c];
  555. // Nothing connected here so continue to the next connection.
  556. if (cblk == null) {
  557. continue;
  558. }
  559. // Another database integrety check.
  560. if (this.blockList[cblk] == null) {
  561. console.log('This is not good: we encountered a null block: ' + cblk);
  562. continue;
  563. }
  564. // Find the dock position in the connected block.
  565. var foundMatch = false;
  566. for (var b = 0; b < this.blockList[cblk].connections.length; b++) {
  567. if (this.blockList[cblk].connections[b] === blk) {
  568. foundMatch = true;
  569. break
  570. }
  571. }
  572. // Yet another database integrety check.
  573. if (!foundMatch) {
  574. console.log('Did not find match for ' + myBlock.name + ' (' + blk + ') and ' + this.blockList[cblk].name + ' (' + cblk + ')');
  575. console.log(myBlock.connections);
  576. console.log(this.blockList[cblk].connections);
  577. break;
  578. }
  579. var cdock = this.blockList[cblk].docks[b];
  580. if (c > 0) {
  581. // Move the connected block...
  582. var dx = bdock[0] - cdock[0];
  583. var dy = bdock[1] - cdock[1];
  584. if (myBlock.container == null) {
  585. console.log('Does this ever happen any more?')
  586. } else {
  587. var nx = myBlock.container.x + dx;
  588. var ny = myBlock.container.y + dy;
  589. }
  590. this._moveBlock(cblk, nx, ny);
  591. } else {
  592. // or it's parent.
  593. var dx = cdock[0] - bdock[0];
  594. var dy = cdock[1] - bdock[1];
  595. var nx = this.blockList[cblk].container.x + dx;
  596. var ny = this.blockList[cblk].container.y + dy;
  597. this._moveBlock(blk, nx, ny);
  598. }
  599. if (c > 0) {
  600. // Recurse on connected blocks.
  601. this.adjustDocks(cblk, true);
  602. }
  603. }
  604. };
  605. this.addDefaultBlock = function (parentblk, oldBlock, skipOldBlock) {
  606. // Add an action name whenever the user removes the name from
  607. // an action block. Add a box name whenever the user removes
  608. // the name from a storein block. Add a Silence block
  609. // whenever the user removes all the blocks from a Note block.
  610. if (parentblk == null) {
  611. return;
  612. }
  613. if (this.blockList[parentblk].name === 'action') {
  614. var cblk = this.blockList[parentblk].connections[1];
  615. if (cblk == null) {
  616. var that = this;
  617. postProcess = function (args) {
  618. var parentblk = args[0];
  619. var oldBlock = args[1];
  620. var blk = that.blockList.length - 1;
  621. that.blockList[parentblk].connections[1] = blk;
  622. that.blockList[blk].value = that.findUniqueActionName(_('action'));
  623. var label = that.blockList[blk].value;
  624. if (label.length > 8) {
  625. label = label.substr(0, 7) + '...';
  626. }
  627. that.blockList[blk].text.text = label;
  628. that.blockList[blk].container.updateCache();
  629. if (that.blockList[blk].value !== that.blockList[oldBlock].value) {
  630. that.newNameddoBlock(that.blockList[blk].value, that.actionHasReturn(parentblk), that.actionHasArgs(parentblk));
  631. var blockPalette = that.palettes.dict['action'];
  632. for (var b = 0; b < blockPalette.protoList.length; b++) {
  633. var protoblock = blockPalette.protoList[b];
  634. if (protoblock.name === 'nameddo' && protoblock.defaults[0] === that.blockList[oldBlock].value) {
  635. setTimeout(function () {
  636. blockPalette.remove(protoblock, that.blockList[oldBlock].value);
  637. delete that.protoBlockDict['myDo_' + that.blockList[oldBlock].value];
  638. that.palettes.hide();
  639. that.palettes.updatePalettes('action');
  640. that.palettes.show();
  641. }, 500);
  642. break;
  643. }
  644. }
  645. that.renameNameddos(that.blockList[oldBlock].value, that.blockList[blk].value);
  646. if (skipOldBlock) {
  647. that.renameDos(that.blockList[oldBlock].value, that.blockList[blk].value, oldBlock);
  648. } else {
  649. that.renameDos(that.blockList[oldBlock].value, that.blockList[blk].value);
  650. }
  651. }
  652. that.adjustDocks(parentblk, true);
  653. };
  654. this._makeNewBlockWithConnections('text', 0, [parentblk], postProcess, [parentblk, oldBlock], false);
  655. }
  656. } else if (this.blockList[parentblk].name === 'storein') {
  657. var cblk = this.blockList[parentblk].connections[1];
  658. if (cblk == null) {
  659. var that = this;
  660. postProcess = function (args) {
  661. var parentblk = args[0];
  662. var oldBlock = args[1];
  663. var blk = that.blockList.length - 1;
  664. that.blockList[parentblk].connections[1] = blk;
  665. that.blockList[blk].value = _('box');
  666. var label = that.blockList[blk].value;
  667. if (label.length > 8) {
  668. label = label.substr(0, 7) + '...';
  669. }
  670. that.blockList[blk].text.text = label;
  671. that.blockList[blk].container.updateCache();
  672. that.adjustDocks(parentblk, true);
  673. };
  674. this._makeNewBlockWithConnections('text', 0, [parentblk], postProcess, [parentblk, oldBlock], false);
  675. }
  676. } else if (['newnote', 'osctime'].indexOf(this.blockList[parentblk].name) !== -1) {
  677. var cblk = this.blockList[parentblk].connections[2];
  678. if (cblk == null) {
  679. var blkname = 'vspace';
  680. var newVspaceBlock = this.makeBlock(blkname, '__NOARG__');
  681. this.blockList[parentblk].connections[2] = newVspaceBlock;
  682. this.blockList[newVspaceBlock].connections[0] = parentblk;
  683. var blkname = 'rest2';
  684. var newSilenceBlock = this.makeBlock(blkname, '__NOARG__');
  685. this.blockList[newSilenceBlock].connections[0] = newVspaceBlock;
  686. this.blockList[newSilenceBlock].connections[1] = null;
  687. this.blockList[newVspaceBlock].connections[1] = newSilenceBlock;
  688. } else if (this.blockList[cblk].name === 'vspace' && this.blockList[cblk].connections[1] == null) {
  689. var blkname = 'rest2';
  690. var newSilenceBlock = this.makeBlock(blkname, '__NOARG__');
  691. this.blockList[newSilenceBlock].connections[0] = cblk;
  692. this.blockList[newSilenceBlock].connections[1] = null;
  693. this.blockList[cblk].connections[1] = newSilenceBlock;
  694. }
  695. }
  696. };
  697. this.deleteNextDefault = function (thisBlock) {
  698. // Remove the Silence block from a Note block if another block
  699. // is inserted above the silence block.
  700. var thisBlockobj = this.blockList[thisBlock];
  701. for (var i = 1; i < thisBlockobj.connections.length; i++) {
  702. if (thisBlockobj.connections[i] && this.blockList[thisBlockobj.connections[i]].name === 'rest2') {
  703. var silenceBlock = thisBlockobj.connections[i];
  704. var silenceBlockobj = this.blockList[silenceBlock];
  705. silenceBlockobj.hide();
  706. silenceBlockobj.trash = true;
  707. this.blockList[thisBlock].connections[i] = silenceBlockobj.connections[1];
  708. break;
  709. }
  710. }
  711. };
  712. this.deletePreviousDefault = function (thisBlock) {
  713. // Remove the Silence block from a Note block if another block
  714. // is inserted just after the Silence block.
  715. var thisBlockobj = this.blockList[thisBlock];
  716. if (thisBlockobj && this.blockList[thisBlockobj.connections[0]] && this.blockList[thisBlockobj.connections[0]].name === 'rest2') {
  717. var silenceBlock = thisBlockobj.connections[0];
  718. var silenceBlockobj = this.blockList[silenceBlock];
  719. silenceBlockobj.hide();
  720. silenceBlockobj.trash = true;
  721. for (var i = 0; i < this.blockList[silenceBlockobj.connections[0]].connections.length; i++) {
  722. if (this.blockList[silenceBlockobj.connections[0]].connections[i] === silenceBlock) {
  723. this.blockList[silenceBlockobj.connections[0]].connections[i] = thisBlock;
  724. break;
  725. }
  726. }
  727. thisBlockobj.connections[0] = silenceBlockobj.connections[0];
  728. }
  729. return thisBlockobj.connections[0];
  730. };
  731. this.blockMoved = function (thisBlock) {
  732. // When a block is moved, we have lots of things to check:
  733. // (0) Is it inside of a expandable block?
  734. // Is it an arg inside an arg clamp?
  735. // (1) Is it an arg block connected to a two-arg block?
  736. // (2) Disconnect its connection[0];
  737. // (3) Look for a new connection;
  738. // Is it potentially an arg inside an arg clamp?
  739. // (4) Is it an arg block connected to a 2-arg block?
  740. // (5) Is it a pitch block being inserted or removed from
  741. // a Note clamp? In which case, we may have to remove
  742. // or add a silence block.
  743. // (6) Is it the name of an action block? In which case we
  744. // need to check to see if we need to rename it.
  745. // (7) Is it the name of a storein block? In which case we
  746. // need to check to see if we need to add a palette entry.
  747. // (8) And we need to recheck if it inside of a expandable block.
  748. // Find any containing expandable blocks.
  749. this._clampBlocksToCheck = [];
  750. if (thisBlock == null) {
  751. console.log('blockMoved called with null block.');
  752. return;
  753. }
  754. var blk = this._insideExpandableBlock(thisBlock);
  755. var expandableLoopCounter = 0;
  756. var parentblk = null;
  757. if (blk != null) {
  758. parentblk = blk;
  759. }
  760. var actionCheck = false;
  761. while (blk != null) {
  762. expandableLoopCounter += 1;
  763. if (expandableLoopCounter > 2 * this.blockList.length) {
  764. console.log('Infinite loop encountered checking for expandables?');
  765. break;
  766. }
  767. this._clampBlocksToCheck.push([blk, 0]);
  768. blk = this._insideExpandableBlock(blk);
  769. }
  770. this._checkTwoArgBlocks = [];
  771. var checkArgBlocks = [];
  772. var myBlock = this.blockList[thisBlock];
  773. if (myBlock == null) {
  774. console.log('null block found in blockMoved method: ' + thisBlock);
  775. return;
  776. }
  777. var c = myBlock.connections[0];
  778. if (c != null) {
  779. var cBlock = this.blockList[c];
  780. }
  781. // If it is an arg block, where is it coming from?
  782. if (myBlock.isArgBlock() && c != null) {
  783. // We care about twoarg (2arg) blocks with
  784. // connections to the first arg;
  785. if (this.blockList[c].isTwoArgBlock() || this.blockList[c].isArgClamp()) {
  786. if (cBlock.connections[1] === thisBlock) {
  787. this._checkTwoArgBlocks.push(c);
  788. }
  789. } else if (this.blockList[c].isArgBlock() && this.blockList[c].isExpandableBlock() || this.blockList[c].isArgClamp()) {
  790. if (cBlock.connections[1] === thisBlock) {
  791. this._checkTwoArgBlocks.push(c);
  792. }
  793. }
  794. }
  795. // Disconnect from connection[0] (both sides of the connection).
  796. if (c != null) {
  797. // Disconnect both ends of the connection.
  798. for (var i = 1; i < cBlock.connections.length; i++) {
  799. if (cBlock.connections[i] === thisBlock) {
  800. cBlock.connections[i] = null;
  801. break;
  802. }
  803. }
  804. myBlock.connections[0] = null;
  805. this.raiseStackToTop(thisBlock);
  806. }
  807. // Look for a new connection.
  808. var x1 = myBlock.container.x + myBlock.docks[0][0];
  809. var y1 = myBlock.container.y + myBlock.docks[0][1];
  810. // Find the nearest dock; if it is close enough, make the
  811. // connection.
  812. var newBlock = null;
  813. var newConnection = null;
  814. var min = (MINIMUMDOCKDISTANCE/DEFAULTBLOCKSCALE) * this.blockScale;
  815. var blkType = myBlock.docks[0][2];
  816. // Is the added block above the silence block or below?
  817. var insertAfterDefault = true;
  818. for (var b = 0; b < this.blockList.length; b++) {
  819. // Don't connect to yourself.
  820. if (b === thisBlock) {
  821. continue;
  822. }
  823. // Don't connect to a collapsed block.
  824. if (this.blockList[b].collapsed) {
  825. continue;
  826. }
  827. // Don't connect to a block in the trash.
  828. if (this.blockList[b].trash) {
  829. continue;
  830. }
  831. for (var i = 1; i < this.blockList[b].connections.length; i++) {
  832. // When converting from Python projects to JS format,
  833. // sometimes extra null connections are added. We need
  834. // to ignore them.
  835. if (i === this.blockList[b].docks.length) {
  836. break;
  837. }
  838. if ((i === this.blockList[b].connections.length - 1) && (this.blockList[b].connections[i] != null) && (this.blockList[this.blockList[b].connections[i]].isNoHitBlock())) {
  839. // Don't break the connection between a block and
  840. // a hidden block below it.
  841. continue;
  842. } 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())) {
  843. // Don't break the connection betweem a backward
  844. // block and a hidden block attached to its clamp.
  845. continue;
  846. } else if (this.blockList[b].name === 'action' && (i === 2) && (this.blockList[b].connections[2] != null) && (this.blockList[this.blockList[b].connections[2]].isNoHitBlock())) {
  847. // Don't break the connection betweem an action
  848. // block and a hidden block attached to its clamp.
  849. continue;
  850. }
  851. // Look for available connections.
  852. if (this._testConnectionType(blkType, this.blockList[b].docks[i][2])) {
  853. var x2 = this.blockList[b].container.x + this.blockList[b].docks[i][0];
  854. var y2 = this.blockList[b].container.y + this.blockList[b].docks[i][1];
  855. var dist = (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1);
  856. if (dist < min) {
  857. newBlock = b;
  858. newConnection = i;
  859. min = dist;
  860. }
  861. } else {
  862. // TODO: bounce away from illegal connection?
  863. // only if the distance was small
  864. // console.log('cannot not connect these two block types');
  865. }
  866. }
  867. }
  868. if (newBlock != null) {
  869. // We found a match.
  870. myBlock.connections[0] = newBlock;
  871. var connection = this.blockList[newBlock].connections[newConnection];
  872. if (connection == null) {
  873. if (this.blockList[newBlock].isArgClamp()) {
  874. // If it is an arg clamp, we may have to adjust
  875. // the slot size.
  876. if ((this.blockList[newBlock].name === 'doArg' || this.blockList[newBlock].name === 'calcArg') && newConnection === 1) {
  877. } else if (['doArg', 'nameddoArg'].indexOf(this.blockList[newBlock].name) !== -1 && newConnection === this.blockList[newBlock].connections.length - 1) {
  878. } else {
  879. // Get the size of the block we are inserting
  880. // adding.
  881. var size = this._getBlockSize(thisBlock);
  882. // console.log('inserting block of size ' + size + ' to arg clamp ' + this.blockList[newBlock].name);
  883. // Get the current slot list.
  884. var slotList = this.blockList[newBlock].argClampSlots;
  885. // Which slot is this block in?
  886. if (['doArg', 'calcArg'].indexOf(this.blockList[newBlock].name) !== -1) {
  887. var si = newConnection - 2;
  888. } else {
  889. var si = newConnection - 1;
  890. }
  891. if (slotList[si] !== size) {
  892. slotList[si] = size;
  893. this.blockList[newBlock].updateArgSlots(slotList);
  894. }
  895. }
  896. }
  897. } else {
  898. // Three scenarios in which we may be overriding an
  899. // existing connection:
  900. // (1) if it is an argClamp, add a new slot below the
  901. // current block;
  902. // (2) if it is an arg block, replace it; or
  903. // (3) if it is a flow block, insert it into the flow.
  904. // A few corner cases: Whenever we connect (or disconnect)
  905. // from an action block (c[1] arg), we need to ensure we have
  906. // a unique action name; Whenever we connect to a newnote
  907. // block (c[2] flow), we need to ensure we have either a silence
  908. // block or a pitch block. And if we are connecting to a
  909. // storein block, we need to ensure that there is a palette
  910. // entry for the new namedbox.
  911. insertAfterDefault = false;
  912. if (this.blockList[newBlock].isArgClamp()) {
  913. if ((this.blockList[newBlock].name === 'doArg' || this.blockList[newBlock].name === 'calcArg') && newConnection === 1) {
  914. // If it is the action name then treat it like
  915. // a standard replacement.
  916. this.blockList[connection].connections[0] = null;
  917. this.findDragGroup(connection);
  918. for (var c = 0; c < this.dragGroup.length; c++) {
  919. this.moveBlockRelative(this.dragGroup[c], 40, 40);
  920. }
  921. } else if (['doArg', 'nameddoArg'].indexOf(this.blockList[newBlock].name) !== -1 && newConnection === this.blockList[newBlock].connections.length - 1) {
  922. // If it is the bottom of the flow, insert as
  923. // usual.
  924. var bottom = this.findBottomBlock(thisBlock);
  925. this.blockList[connection].connections[0] = bottom;
  926. this.blockList[bottom].connections[this.blockList[bottom].connections.length - 1] = connection;
  927. } else {
  928. // Move the block in the current slot down one
  929. // slot (cascading and creating a new slot if
  930. // necessary).
  931. // Get the size of the block we are inserting adding.
  932. var size = this._getBlockSize(thisBlock);
  933. // Get the current slot list.
  934. var slotList = this.blockList[newBlock].argClampSlots;
  935. // Which slot is this block in?
  936. var ci = this.blockList[newBlock].connections.indexOf(connection);
  937. if (['doArg', 'calcArg'].indexOf(this.blockList[newBlock].name) !== -1) {
  938. var si = ci - 2;
  939. } else {
  940. var si = ci - 1;
  941. }
  942. var emptySlot = null;
  943. var emptyConnection = null;
  944. // Is there an empty slot below?
  945. for (var emptySlot = si; emptySlot < slotList.length; emptySlot++) {
  946. if (this.blockList[newBlock].connections[ci + emptySlot - si] == null) {
  947. emptyConnection = ci + emptySlot - si;
  948. break;
  949. }
  950. }
  951. if (emptyConnection == null) {
  952. slotList.push(1);
  953. this._newLocalArgBlock(slotList.length);
  954. emptyConnection = ci + emptySlot - si;
  955. this.blockList[newBlock].connections.push(null);
  956. // Slide everything down one slot.
  957. for (var i = slotList.length - 1; i > si + 1; i--) {
  958. slotList[i] = slotList[i - 1];
  959. }
  960. for (var i = this.blockList[newBlock].connections.length - 1; i > ci + 1; i--) {
  961. this.blockList[newBlock].connections[i] = this.blockList[newBlock].connections[i - 1];
  962. }
  963. }
  964. // The new block is added below the current
  965. // connection...
  966. newConnection += 1;
  967. // Set its slot size too.
  968. slotList[si + 1] = size;
  969. this.blockList[newBlock].updateArgSlots(slotList);
  970. }
  971. } else if (myBlock.isArgBlock()) {
  972. this.blockList[connection].connections[0] = null;
  973. this.findDragGroup(connection);
  974. for (var c = 0; c < this.dragGroup.length; c++) {
  975. this.moveBlockRelative(this.dragGroup[c], 40, 40);
  976. }
  977. // We need to rename the action stack.
  978. if (this.blockList[newBlock].name === 'action') {
  979. actionCheck = true;
  980. if (myBlock.value !== this.blockList[connection].value) {
  981. // Temporarily disconnect to ensure we don't
  982. // find myBlock when looking for a unique name.
  983. var c = myBlock.connections[0];
  984. myBlock.connections[0] = null;
  985. var name = this.findUniqueActionName(myBlock.value);
  986. myBlock.connections[0] = c;
  987. if (name !== myBlock.value) {
  988. myBlock.value = name;
  989. var label = name;
  990. if (label.length > 8) {
  991. label = label.substr(0, 7) + '...';
  992. }
  993. myBlock.text.text = label;
  994. myBlock.container.updateCache();
  995. }
  996. var that = this;
  997. setTimeout(function () {
  998. // A previously disconnected name may have left
  999. // an entry in the palette we need to remove.
  1000. var name = that.blockList[connection].value;
  1001. if (that.protoBlockDict['myDo_' + name] != undefined) {
  1002. delete that.protoBlockDict['myDo_' + name];
  1003. that.palettes.dict['action'].hideMenu(true);
  1004. }
  1005. that.newNameddoBlock(myBlock.value, that.actionHasReturn(newBlock), that.actionHasArgs(newBlock));
  1006. var blockPalette = that.palettes.dict['action'];
  1007. for (var b = 0; b < blockPalette.protoList.length; b++) {
  1008. var protoblock = blockPalette.protoList[b];
  1009. if (protoblock.name === 'nameddo' && protoblock.staticLabels[0] === that.blockList[connection].value) {
  1010. setTimeout(function () {
  1011. blockPalette.remove(protoblock, that.blockList[connection].value);
  1012. delete that.protoBlockDict['myDo_' + that.blockList[connection].value];
  1013. that.palettes.hide();
  1014. that.palettes.updatePalettes('action');
  1015. that.palettes.show();
  1016. }, 500);
  1017. break;
  1018. }
  1019. }
  1020. that.renameNameddos(that.blockList[connection].value, myBlock.value);
  1021. that.renameDos(that.blockList[connection].value, myBlock.value);
  1022. }, 750);
  1023. }
  1024. } else if (this.blockList[newBlock].name === 'storein') {
  1025. // We may need to add new storein and namedo
  1026. // blocks to the palette.
  1027. if (myBlock.value !== 'box') {
  1028. this.newStoreinBlock(myBlock.value);
  1029. this.newNamedboxBlock(myBlock.value);
  1030. var that = this;
  1031. setTimeout(function () {
  1032. that.palettes.hide();
  1033. that.palettes.updatePalettes('boxes');
  1034. that.palettes.show();
  1035. }, 500);
  1036. }
  1037. }
  1038. } else if (!this.blockList[thisBlock].isArgFlowClampBlock()) {
  1039. var bottom = this.findBottomBlock(thisBlock);
  1040. this.blockList[connection].connections[0] = bottom;
  1041. this.blockList[bottom].connections[this.blockList[bottom].connections.length - 1] = connection;
  1042. }
  1043. }
  1044. this.blockList[newBlock].connections[newConnection] = thisBlock;
  1045. // Remove the silence block (if it is present) after
  1046. // adding a new block inside of a note block.
  1047. if (this._insideNoteBlock(thisBlock) != null) {
  1048. // If blocks are inserted above the silence block.
  1049. if (insertAfterDefault) {
  1050. newBlock = this.deletePreviousDefault(thisBlock);
  1051. } else {
  1052. this.deleteNextDefault(bottom);
  1053. }
  1054. }
  1055. // If we attached a name to an action block, check to see
  1056. // if we need to rename it.
  1057. if (this.blockList[newBlock].name === 'action' && !actionCheck) {
  1058. // Is there already another action block with this name?
  1059. for (var b = 0; b < this.blockList.length; b++) {
  1060. if (b === newBlock) continue;
  1061. if (this.blockList[b].name === 'action') {
  1062. if (this.blockList[b].connections[1] != null) {
  1063. if (this.blockList[this.blockList[b].connections[1]].value === this.blockList[thisBlock].value) {
  1064. this.blockList[thisBlock].value = this.findUniqueActionName(this.blockList[thisBlock].value);
  1065. var label = this.blockList[thisBlock].value;
  1066. if (label.length > 8) {
  1067. label = label.substr(0, 7) + '...';
  1068. }
  1069. this.blockList[thisBlock].text.text = label;
  1070. this.blockList[thisBlock].container.updateCache();
  1071. this.newNameddoBlock(this.blockList[thisBlock].value, this.actionHasReturn(b), this.actionHasArgs(b));
  1072. this.setActionProtoVisiblity(false);
  1073. }
  1074. }
  1075. }
  1076. }
  1077. }
  1078. // console.log('Adjust Docks: ' + this.blockList[newBlock].name);
  1079. this.adjustDocks(newBlock, true);
  1080. // TODO: some graphical feedback re new connection?
  1081. }
  1082. // If it is an arg block, where is it coming from?
  1083. // FIXME: improve mechanism for testing block types.
  1084. if ((myBlock.isArgBlock() || myBlock.name === 'calcArg' || myBlock.name === 'namedcalcArg') && newBlock != null) {
  1085. // We care about twoarg blocks with connections to the
  1086. // first arg;
  1087. if (this.blockList[newBlock].isTwoArgBlock()) {
  1088. if (this.blockList[newBlock].connections[1] === thisBlock) {
  1089. if (this._checkTwoArgBlocks.indexOf(newBlock) === -1) {
  1090. this._checkTwoArgBlocks.push(newBlock);
  1091. }
  1092. }
  1093. } else if (this.blockList[newBlock].isArgBlock() && this.blockList[newBlock].isExpandableBlock()) {
  1094. if (this.blockList[newBlock].connections[1] === thisBlock) {
  1095. if (this._checkTwoArgBlocks.indexOf(newBlock) === -1) {
  1096. this._checkTwoArgBlocks.push(newBlock);
  1097. }
  1098. }
  1099. }
  1100. // We also care about the second-to-last connection to an
  1101. // arg block.
  1102. var n = this.blockList[newBlock].connections.length;
  1103. if (this.blockList[newBlock].connections[n - 2] === thisBlock) {
  1104. // Only flow blocks, but not ArgClamps
  1105. if (!this.blockList[newBlock].isArgClamp() && this.blockList[newBlock].docks[n - 1][2] === 'in') {
  1106. checkArgBlocks.push(newBlock);
  1107. }
  1108. }
  1109. }
  1110. this.addDefaultBlock(parentblk, thisBlock, actionCheck);
  1111. // Put block adjustments inside a slight delay to make the
  1112. // addition/substraction of vspace and changes of block shape
  1113. // appear less abrupt (and it can be a little racy).
  1114. var that = this;
  1115. setTimeout(function () {
  1116. // If we changed the contents of a arg block, we may need a vspace.
  1117. if (checkArgBlocks.length > 0) {
  1118. for (var i = 0; i < checkArgBlocks.length; i++) {
  1119. that._addRemoveVspaceBlock(checkArgBlocks[i]);
  1120. }
  1121. }
  1122. // If we changed the contents of a two-arg block, we need to
  1123. // adjust it.
  1124. if (that._checkTwoArgBlocks.length > 0) {
  1125. that._adjustExpandableTwoArgBlock(that._checkTwoArgBlocks);
  1126. }
  1127. // First, adjust the docks for any blocks that may have
  1128. // had a vspace added.
  1129. for (var i = 0; i < checkArgBlocks.length; i++) {
  1130. // console.log('Adjust Docks: ' + this.blockList[checkArgBlocks[i]].name);
  1131. that.adjustDocks(checkArgBlocks[i], true);
  1132. }
  1133. // Next, recheck if the connection is inside of a
  1134. // expandable block.
  1135. var blk = that._insideExpandableBlock(thisBlock);
  1136. var expandableLoopCounter = 0;
  1137. while (blk != null) {
  1138. // Extra check for malformed data.
  1139. expandableLoopCounter += 1;
  1140. if (expandableLoopCounter > 2 * that.blockList.length) {
  1141. console.log('Infinite loop checking for expandables?');
  1142. console.log(that.blockList);
  1143. break;
  1144. }
  1145. if (that.blockList[blk].name === 'ifthenelse') {
  1146. that._clampBlocksToCheck.push([blk, 0]);
  1147. that._clampBlocksToCheck.push([blk, 1]);
  1148. } else {
  1149. that._clampBlocksToCheck.push([blk, 0]);
  1150. }
  1151. blk = that._insideExpandableBlock(blk);
  1152. }
  1153. that._adjustExpandableClampBlock();
  1154. that.refreshCanvas();
  1155. }, 250);
  1156. };
  1157. this._testConnectionType = function (type1, type2) {
  1158. // Can these two blocks dock?
  1159. if (type1 === 'in' && type2 === 'out') {
  1160. return true;
  1161. }
  1162. if (type1 === 'out' && type2 === 'in') {
  1163. return true;
  1164. }
  1165. if (type1 === 'numberin' && ['numberout', 'anyout'].indexOf(type2) !== -1) {
  1166. return true;
  1167. }
  1168. if (['numberout', 'anyout'].indexOf(type1) !== -1 && type2 === 'numberin') {
  1169. return true;
  1170. }
  1171. if (type1 === 'textin' && ['textout', 'anyout'].indexOf(type2) !== -1) {
  1172. return true;
  1173. }
  1174. if (['textout', 'anyout'].indexOf(type1) !== -1 && type2 === 'textin') {
  1175. return true;
  1176. }
  1177. if (type1 === 'booleanout' && type2 === 'booleanin') {
  1178. return true;
  1179. }
  1180. if (type1 === 'booleanin' && type2 === 'booleanout') {
  1181. return true;
  1182. }
  1183. if (type1 === 'mediain' && type2 === 'mediaout') {
  1184. return true;
  1185. }
  1186. if (type1 === 'mediaout' && type2 === 'mediain') {
  1187. return true;
  1188. }
  1189. if (type1 === 'mediain' && type2 === 'textout') {
  1190. return true;
  1191. }
  1192. if (type2 === 'mediain' && type1 === 'textout') {
  1193. return true;
  1194. }
  1195. if (type1 === 'filein' && type2 === 'fileout') {
  1196. return true;
  1197. }
  1198. if (type1 === 'fileout' && type2 === 'filein') {
  1199. return true;
  1200. }
  1201. if (type1 === 'solfegein' && ['anyout', 'solfegeout', 'textout', 'noteout', 'numberout'].indexOf(type2) !== -1) {
  1202. return true;
  1203. }
  1204. if (type2 === 'solfegein' && ['anyout', 'solfegeout', 'textout', 'noteout', 'numberout'].indexOf(type1) !== -1) {
  1205. return true;
  1206. }
  1207. if (type1 === 'notein' && ['solfegeout', 'textout', 'noteout'].indexOf(type2) !== -1) {
  1208. return true;
  1209. }
  1210. if (type2 === 'notein' && ['solfegeout', 'textout', 'noteout'].indexOf(type1) !== -1) {
  1211. return true;
  1212. }
  1213. if (type1 === 'anyin' && ['textout', 'mediaout', 'numberout', 'anyout', 'fileout', 'solfegeout', 'noteout'].indexOf(type2) !== -1) {
  1214. return true;
  1215. }
  1216. if (type2 === 'anyin' && ['textout', 'mediaout', 'numberout', 'anyout', 'fileout', 'solfegeout', 'noteout'].indexOf(type1) !== -1) {
  1217. return true;
  1218. }
  1219. return false;
  1220. };
  1221. this.updateBlockPositions = function () {
  1222. // Create the block image if it doesn't yet exist.
  1223. for (var blk = 0; blk < this.blockList.length; blk++) {
  1224. this._moveBlock(blk, this.blockList[blk].container.x, this.blockList[blk].container.y);
  1225. }
  1226. };
  1227. this.bringToTop = function () {
  1228. // Move all the blocks to the top layer of the stage
  1229. this._adjustTheseStacks = [];
  1230. for (var blk in this.blockList) {
  1231. var myBlock = this.blockList[blk];
  1232. /*
  1233. this.stage.removeChild(myBlock.container);
  1234. this.stage.addChild(myBlock.container);
  1235. if (myBlock.collapseContainer != null) {
  1236. this.stage.removeChild(myBlock.collapseContainer);
  1237. this.stage.addChild(myBlock.collapseContainer);
  1238. }
  1239. */
  1240. if (myBlock.connections[0] == null) {
  1241. this._adjustTheseStacks.push(blk);
  1242. }
  1243. }
  1244. for (var blk = 0; blk < this._adjustTheseStacks.length; blk++) {
  1245. // console.log('Adjust Stack: ' + this.blockList[this._adjustTheseStacks[blk]].name);
  1246. this.raiseStackToTop(this._adjustTheseStacks[blk]);
  1247. }
  1248. this.refreshCanvas();
  1249. };
  1250. this.checkBounds = function () {
  1251. var onScreen = true;
  1252. for (var blk = 0; blk < this.blockList.length; blk++) {
  1253. if (this.blockList[blk].connections[0] == null) {
  1254. if (this.blockList[blk].offScreen(this.boundary)) {
  1255. this._homeButtonContainers[0].visible = true;
  1256. this._homeButtonContainers[1].visible = false;
  1257. this.boundary.show();
  1258. onScreen = false;
  1259. break;
  1260. }
  1261. }
  1262. }
  1263. if (onScreen) {
  1264. this._homeButtonContainers[0].visible = false;
  1265. this._homeButtonContainers[1].visible = true;
  1266. this.boundary.hide();
  1267. }
  1268. };
  1269. this._moveBlock = function (blk, x, y) {
  1270. // Move a block (and its label) to x, y.
  1271. var myBlock = this.blockList[blk];
  1272. if (myBlock.container != null) {
  1273. myBlock.container.x = x;
  1274. myBlock.container.y = y;
  1275. if (myBlock.collapseContainer != null) {
  1276. myBlock.collapseContainer.x = x + COLLAPSEBUTTONXOFF * (this.blockList[blk].protoblock.scale / 2);
  1277. myBlock.collapseContainer.y = y + COLLAPSEBUTTONYOFF * (this.blockList[blk].protoblock.scale / 2);
  1278. }
  1279. this.checkBounds();
  1280. } else {
  1281. console.log('No container yet for block ' + myBlock.name);
  1282. }
  1283. };
  1284. this.moveBlockRelative = function (blk, dx, dy) {
  1285. // Move a block (and its label) by dx, dy.
  1286. if (this.inLongPress) {
  1287. this.saveStackButton.visible = false;
  1288. this.dismissButton.visible = false;
  1289. this.inLongPress = false;
  1290. }
  1291. var myBlock = this.blockList[blk];
  1292. if (myBlock.container != null) {
  1293. myBlock.container.x += dx;
  1294. myBlock.container.y += dy;
  1295. if (myBlock.collapseContainer != null) {
  1296. myBlock.collapseContainer.x += dx;
  1297. myBlock.collapseContainer.y += dy;
  1298. }
  1299. this.checkBounds();
  1300. } else {
  1301. console.log('No container yet for block ' + myBlock.name);
  1302. }
  1303. };
  1304. this.moveStackRelative = function (blk, dx, dy) {
  1305. this.findDragGroup(blk)
  1306. if (this.dragGroup.length > 0) {
  1307. for (var b = 0; b < this.dragGroup.length; b++) {
  1308. this.moveBlockRelative(this.dragGroup[b], dx, dy);
  1309. }
  1310. }
  1311. };
  1312. this.updateBlockText = function (blk) {
  1313. // When we create new blocks, we may not have assigned the
  1314. // value yet.
  1315. var myBlock = this.blockList[blk];
  1316. var maxLength = 8;
  1317. if (myBlock.text == null) {
  1318. return;
  1319. }
  1320. if (myBlock.name === 'loadFile') {
  1321. try {
  1322. var label = myBlock.value[0].toString();
  1323. } catch (e) {
  1324. var label = _('open file');
  1325. }
  1326. maxLength = 10;
  1327. } else if (myBlock.name === 'solfege') {
  1328. var obj = splitSolfege(myBlock.value);
  1329. var label = i18nSolfege(obj[0]);
  1330. var attr = obj[1];
  1331. if (attr !== '♮') {
  1332. label += attr;
  1333. }
  1334. } else if (myBlock.name === 'eastindiansolfege') {
  1335. var obj = splitSolfege(myBlock.value);
  1336. var label = WESTERN2EISOLFEGENAMES[obj[0]];
  1337. var attr = obj[1];
  1338. if (attr !== '♮') {
  1339. label += attr;
  1340. }
  1341. } else {
  1342. var label = myBlock.value.toString();
  1343. }
  1344. if (label.length > maxLength) {
  1345. label = label.substr(0, maxLength - 1) + '...';
  1346. }
  1347. myBlock.text.text = label;
  1348. // Make sure text is on top.
  1349. var z = myBlock.container.getNumChildren() - 1;
  1350. myBlock.container.setChildIndex(myBlock.text, z);
  1351. if (myBlock.loadComplete) {
  1352. myBlock.container.updateCache();
  1353. } else {
  1354. console.log('Load not yet complete for (' + blk + ') ' + myBlock.name);
  1355. }
  1356. };
  1357. this.findTopBlock = function (blk) {
  1358. // Find the top block in a stack.
  1359. if (blk == null) {
  1360. return null;
  1361. }
  1362. var myBlock = this.blockList[blk];
  1363. if (myBlock.connections == null) {
  1364. return blk;
  1365. }
  1366. if (myBlock.connections.length === 0) {
  1367. return blk;
  1368. }
  1369. // Test for corrupted-connection scenario.
  1370. if (myBlock.connections.length > 1 && myBlock.connections[0] != null && myBlock.connections[0] === last(myBlock.connections)) {
  1371. 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.');
  1372. return blk;
  1373. }
  1374. var topBlockLoop = 0;
  1375. while (myBlock.connections[0] != null) {
  1376. topBlockLoop += 1;
  1377. if (topBlockLoop > 2 * this.blockList.length) {
  1378. // Could happen if the block data is malformed.
  1379. console.log('infinite loop finding topBlock?');
  1380. console.log(this.blockList.indexOf(myBlock) + ' ' + myBlock.name);
  1381. break;
  1382. }
  1383. blk = myBlock.connections[0];
  1384. myBlock = this.blockList[blk];
  1385. }
  1386. return blk;
  1387. };
  1388. this.sameGeneration = function (firstBlk, childBlk) {
  1389. if (firstBlk == null || childBlk == null) {
  1390. return false;
  1391. }
  1392. if (firstBlk === childBlk) {
  1393. return true;
  1394. }
  1395. var myBlock = this.blockList[firstBlk];
  1396. if (myBlock.connections == null) {
  1397. return false;
  1398. }
  1399. if (myBlock.connections.length === 0) {
  1400. return false;
  1401. }
  1402. var bottomBlockLoop = 0;
  1403. while (last(myBlock.connections) != null) {
  1404. bottomBlockLoop += 1;
  1405. if (bottomBlockLoop > 2 * this.blockList.length) {
  1406. // Could happen if the block data is malformed.
  1407. console.log('infinite loop finding bottomBlock?');
  1408. break;
  1409. }
  1410. blk = last(myBlock.connections);
  1411. myBlock = this.blockList[blk];
  1412. if (blk === childBlk) {
  1413. return true;
  1414. }
  1415. }
  1416. return false;
  1417. }
  1418. this.findBottomBlock = function (blk) {
  1419. // Find the bottom block in a stack.
  1420. if (blk == null) {
  1421. return null;
  1422. }
  1423. var myBlock = this.blockList[blk];
  1424. if (myBlock.connections == null) {
  1425. return blk;
  1426. }
  1427. if (myBlock.connections.length === 0) {
  1428. return blk;
  1429. }
  1430. var bottomBlockLoop = 0;
  1431. while (last(myBlock.connections) != null) {
  1432. bottomBlockLoop += 1;
  1433. if (bottomBlockLoop > 2 * this.blockList.length) {
  1434. // Could happen if the block data is malformed.
  1435. console.log('infinite loop finding bottomBlock?');
  1436. break;
  1437. }
  1438. blk = last(myBlock.connections);
  1439. myBlock = this.blockList[blk];
  1440. }
  1441. return blk;
  1442. };
  1443. this.findStacks = function () {
  1444. // Find any blocks with null in the first connection.
  1445. this.stackList = [];
  1446. for (var i = 0; i < this.blockList.length; i++) {
  1447. if (this.blockList[i].connections[0] == null) {
  1448. this.stackList.push(i)
  1449. }
  1450. }
  1451. };
  1452. this._findClamps = function () {
  1453. // Find any clamp blocks.
  1454. this._expandablesList = [];
  1455. this.findStacks(); // We start by finding the stacks
  1456. for (var i = 0; i < this.stackList.length; i++) {
  1457. this._searchCounter = 0;
  1458. this._searchForExpandables(this.stackList[i]);
  1459. }
  1460. this._searchForArgFlow();
  1461. };
  1462. this._findTwoArgs = function () {
  1463. // Find any expandable arg blocks.
  1464. this._expandablesList = [];
  1465. for (var i = 0; i < this.blockList.length; i++) {
  1466. if (this.blockList[i].isArgBlock() && this.blockList[i].isExpandableBlock()) {
  1467. this._expandablesList.push(i);
  1468. } else if (this.blockList[i].isTwoArgBlock()) {
  1469. this._expandablesList.push(i);
  1470. }
  1471. }
  1472. };
  1473. this._searchForArgFlow = function () {
  1474. for (var blk = 0; blk < this.blockList.length; blk++) {
  1475. if (this.blockList[blk].isArgFlowClampBlock()) {
  1476. this._expandablesList.push(blk);
  1477. }
  1478. }
  1479. };
  1480. this._searchForExpandables = function (blk) {
  1481. // Find the expandable blocks below blk in a stack.
  1482. while (blk != null && this.blockList[blk] != null && !this.blockList[blk].isValueBlock()) {
  1483. // More checks for malformed or corrupted block data.
  1484. this._searchCounter += 1;
  1485. if (this._searchCounter > 2 * this.blockList.length) {
  1486. console.log('infinite loop searching for Expandables? ' + this._searchCounter);
  1487. console.log(blk + ' ' + this.blockList[blk].name);
  1488. break;
  1489. }
  1490. if (this.blockList[blk].isClampBlock()) {
  1491. this._expandablesList.push(blk);
  1492. var c = this.blockList[blk].connections.length - 2;
  1493. this._searchForExpandables(this.blockList[blk].connections[c]);
  1494. if (this.blockList[blk].name === 'ifthenelse') {
  1495. // search top clamp too
  1496. var c = 2;
  1497. this._searchForExpandables(this.blockList[blk].connections[c]);
  1498. }
  1499. } else if (this.blockList[blk].isArgClamp()) {
  1500. this._expandablesList.push(blk);
  1501. }
  1502. if (this.blockList[blk].connections.length > 1) {
  1503. blk = last(this.blockList[blk].connections);
  1504. } else {
  1505. // A value block only connects back to its parent, so
  1506. // end the search here.
  1507. blk = null;
  1508. }
  1509. }
  1510. };
  1511. this._expandTwoArgs = function () {
  1512. // Expand expandable 2-arg blocks as needed.
  1513. this._findTwoArgs();
  1514. this._adjustExpandableTwoArgBlock(this._expandablesList);
  1515. this.refreshCanvas();
  1516. };
  1517. this._expandClamps = function () {
  1518. // Expand expandable clamp blocks as needed.
  1519. this._findClamps();
  1520. this._clampBlocksToCheck = [];
  1521. for (var i = 0; i < this._expandablesList.length; i++) {
  1522. if (this.blockList[this._expandablesList[i]].name === 'ifthenelse') {
  1523. this._clampBlocksToCheck.push([this._expandablesList[i], 0]);
  1524. this._clampBlocksToCheck.push([this._expandablesList[i], 1]);
  1525. } else {
  1526. this._clampBlocksToCheck.push([this._expandablesList[i], 0]);
  1527. }
  1528. }
  1529. this._adjustExpandableClampBlock();
  1530. this.refreshCanvas();
  1531. };
  1532. this.changeDisabledStatus = function (name, flag) {
  1533. // Some blocks, e.g., sensor blocks for Butia, change their
  1534. // appearance depending upon if they have been enabled or
  1535. // disabled.
  1536. for (var blk in this.blockList) {
  1537. var myBlock = this.blockList[blk];
  1538. if (myBlock.name === name) {
  1539. myBlock.protoblock.disabled = flag;
  1540. myBlock.regenerateArtwork(false);
  1541. }
  1542. }
  1543. };
  1544. this.unhighlightAll = function () {
  1545. for (var blk in this.blockList) {
  1546. this.unhighlight(blk);
  1547. }
  1548. };
  1549. this.unhighlight = function (blk) {
  1550. if (!this.visible) {
  1551. return;
  1552. }
  1553. if (blk != null) {
  1554. var thisBlock = blk;
  1555. } else {
  1556. var thisBlock = this.highlightedBlock;
  1557. }
  1558. if (thisBlock != null) {
  1559. this.blockList[thisBlock].unhighlight();
  1560. }
  1561. if (this.highlightedBlock = thisBlock) {
  1562. this.highlightedBlock = null;
  1563. }
  1564. };
  1565. this.highlight = function (blk, unhighlight) {
  1566. if (!this.visible) {
  1567. return;
  1568. }
  1569. if (blk != null) {
  1570. if (unhighlight) {
  1571. this.unhighlight(null);
  1572. }
  1573. this.blockList[blk].highlight();
  1574. this.highlightedBlock = blk;
  1575. }
  1576. };
  1577. this.hide = function () {
  1578. for (var blk in this.blockList) {
  1579. this.blockList[blk].hide();
  1580. }
  1581. this.visible = false;
  1582. };
  1583. this.show = function () {
  1584. for (var blk in this.blockList) {
  1585. this.blockList[blk].show();
  1586. }
  1587. this.visible = true;
  1588. };
  1589. this._makeNewBlockWithConnections = function (name, blockOffset, connections, postProcess, postProcessArg, collapsed) {
  1590. if (typeof(collapsed) === 'undefined') {
  1591. collapsed = false
  1592. }
  1593. myBlock = this.makeNewBlock(name, postProcess, postProcessArg);
  1594. if (myBlock == null) {
  1595. console.log('could not make block ' + name);
  1596. return;
  1597. }
  1598. // myBlock.collapsed = !collapsed;
  1599. for (var c = 0; c < connections.length; c++) {
  1600. if (c === myBlock.docks.length) {
  1601. break;
  1602. }
  1603. if (connections[c] == null) {
  1604. myBlock.connections.push(null);
  1605. } else {
  1606. myBlock.connections.push(connections[c] + blockOffset);
  1607. }
  1608. }
  1609. };
  1610. this.makeNewBlock = function (name, postProcess, postProcessArg) {
  1611. // Create a new block
  1612. if (!name in this.protoBlockDict) {
  1613. // Should never happen: nop blocks should be substituted
  1614. console.log('makeNewBlock: no prototype for ' + name);
  1615. return null;
  1616. }
  1617. if (this.protoBlockDict[name] == null) {
  1618. // Should never happen
  1619. console.log('makeNewBlock: no prototype for ' + name);
  1620. return null;
  1621. }
  1622. // Deprecated
  1623. // If we drag in a synth block, we need to load the synth.
  1624. if (['sine', 'sawtooth', 'triangle', 'square'].indexOf(name) !== -1) {
  1625. if (_THIS_IS_MUSIC_BLOCKS_) {
  1626. this.logo.synth.loadSynth(name);
  1627. }
  1628. }
  1629. if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(name) !== -1) {
  1630. this.blockList.push(new Block(this.protoBlockDict[name], this, postProcessArg[1]));
  1631. } else if (name === 'namedarg') {
  1632. this.blockList.push(new Block(this.protoBlockDict[name], this, 'arg ' + postProcessArg[1]));
  1633. } else {
  1634. this.blockList.push(new Block(this.protoBlockDict[name], this));
  1635. }
  1636. if (last(this.blockList) == null) {
  1637. // Should never happen
  1638. console.log('failed to make protoblock for ' + name);
  1639. return null;
  1640. }
  1641. // We copy the dock because expandable blocks modify it.
  1642. var myBlock = last(this.blockList);
  1643. myBlock.copySize();
  1644. // We may need to do some postProcessing to the block
  1645. myBlock.postProcess = postProcess;
  1646. myBlock.postProcessArg = postProcessArg;
  1647. // We need a container for the block graphics.
  1648. myBlock.container = new createjs.Container();
  1649. this.stage.addChild(myBlock.container);
  1650. myBlock.container.snapToPixelEnabled = true;
  1651. myBlock.container.x = 0;
  1652. myBlock.container.y = 0;
  1653. // and we need to load the images into the container.
  1654. myBlock.imageLoad();
  1655. return myBlock;
  1656. };
  1657. this.makeBlock = function (name, arg) {
  1658. // Make a new block from a proto block.
  1659. // Called from palettes.
  1660. if (name === 'text') {
  1661. console.log('makeBlock ' + name + ' ' + arg);
  1662. }
  1663. var postProcess = null;
  1664. var postProcessArg = null;
  1665. var that = this;
  1666. var thisBlock = this.blockList.length;
  1667. if (name === 'start') {
  1668. postProcess = function (thisBlock) {
  1669. that.blockList[thisBlock].value = that.turtles.turtleList.length;
  1670. that.turtles.addTurtle(that.blockList[thisBlock]);
  1671. };
  1672. postProcessArg = thisBlock;
  1673. } else if (name === 'drum') {
  1674. postProcess = function (thisBlock) {
  1675. that.blockList[thisBlock].value = that.turtles.turtleList.length;
  1676. that.turtles.addDrum(that.blockList[thisBlock]);
  1677. };
  1678. postProcessArg = thisBlock;
  1679. } else if (name === 'text') {
  1680. postProcess = function (args) {
  1681. var thisBlock = args[0];
  1682. var value = args[1];
  1683. that.blockList[thisBlock].value = value;
  1684. that.blockList[thisBlock].text.text = value;
  1685. that.blockList[thisBlock].container.updateCache();
  1686. };
  1687. postProcessArg = [thisBlock, _('text')];
  1688. } else if (name === 'solfege') {
  1689. postProcess = function (args) {
  1690. var thisBlock = args[0];
  1691. var value = args[1];
  1692. that.blockList[thisBlock].value = value;
  1693. that.blockList[thisBlock].text.text = value;
  1694. that.blockList[thisBlock].container.updateCache();
  1695. };
  1696. postProcessArg = [thisBlock, 'sol'];
  1697. } else if (name === 'eastindiansolfege') {
  1698. postProcess = function (args) {
  1699. var thisBlock = args[0];
  1700. var value = args[1];
  1701. that.blockList[thisBlock].value = value;
  1702. that.blockList[thisBlock].text.text = WESTERN2EISOLFEGENAMES[value];
  1703. that.blockList[thisBlock].container.updateCache();
  1704. };
  1705. postProcessArg = [thisBlock, 'sol'];
  1706. } else if (name === 'notename') {
  1707. postProcess = function (args) {
  1708. var thisBlock = args[0];
  1709. var value = args[1];
  1710. that.blockList[thisBlock].value = value;
  1711. that.blockList[thisBlock].text.text = value;
  1712. that.blockList[thisBlock].container.updateCache();
  1713. };
  1714. postProcessArg = [thisBlock, 'G'];
  1715. } else if (name === 'drumname') {
  1716. postProcess = function (args) {
  1717. var thisBlock = args[0];
  1718. var value = args[1];
  1719. that.blockList[thisBlock].value = value;
  1720. that.blockList[thisBlock].text.text = value;
  1721. that.blockList[thisBlock].container.updateCache();
  1722. };
  1723. postProcessArg = [thisBlock, 'kick'];
  1724. } else if (name === 'voicename') {
  1725. postProcess = function (args) {
  1726. var thisBlock = args[0];
  1727. var value = args[1];
  1728. that.blockList[thisBlock].value = value;
  1729. that.blockList[thisBlock].text.text = value;
  1730. that.blockList[thisBlock].container.updateCache();
  1731. };
  1732. postProcessArg = [thisBlock, 'sine'];
  1733. } else if (name === 'modename') {
  1734. postProcess = function (args) {
  1735. var thisBlock = args[0];
  1736. var value = args[1];
  1737. that.blockList[thisBlock].value = value;
  1738. that.blockList[thisBlock].text.text = value;
  1739. that.blockList[thisBlock].container.updateCache();
  1740. };
  1741. postProcessArg = [thisBlock, 'Major'];
  1742. } else if (name === 'number') {
  1743. postProcess = function (args) {
  1744. var thisBlock = args[0];
  1745. var value = Number(args[1]);
  1746. that.blockList[thisBlock].value = value;
  1747. that.blockList[thisBlock].text.text = value.toString();
  1748. that.blockList[thisBlock].container.updateCache();
  1749. };
  1750. postProcessArg = [thisBlock, NUMBERBLOCKDEFAULT];
  1751. } else if (name === 'media') {
  1752. postProcess = function (args) {
  1753. var thisBlock = args[0];
  1754. var value = args[1];
  1755. that.blockList[thisBlock].value = value;
  1756. if (value == null) {
  1757. that.blockList[thisBlock].image = 'images/load-media.svg';
  1758. } else {
  1759. that.blockList[thisBlock].image = null;
  1760. }
  1761. };
  1762. postProcessArg = [thisBlock, null];
  1763. } else if (name === 'camera') {
  1764. postProcess = function (args) {
  1765. console.log('post process camera ' + args[1]);
  1766. var thisBlock = args[0];
  1767. var value = args[1];
  1768. that.blockList[thisBlock].value = CAMERAVALUE;
  1769. if (value == null) {
  1770. that.blockList[thisBlock].image = 'images/camera.svg';
  1771. } else {
  1772. that.blockList[thisBlock].image = null;
  1773. }
  1774. };
  1775. postProcessArg = [thisBlock, null];
  1776. } else if (name === 'video') {
  1777. postProcess = function (args) {
  1778. var thisBlock = args[0];
  1779. var value = args[1];
  1780. that.blockList[thisBlock].value = VIDEOVALUE;
  1781. if (value == null) {
  1782. that.blockList[thisBlock].image = 'images/video.svg';
  1783. } else {
  1784. that.blockList[thisBlock].image = null;
  1785. }
  1786. };
  1787. postProcessArg = [thisBlock, null];
  1788. } else if (name === 'loadFile') {
  1789. postProcess = function (args) {
  1790. that.updateBlockText(args[0]);
  1791. };
  1792. postProcessArg = [thisBlock, null];
  1793. } else if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg', 'namedarg'].indexOf(name) !== -1) {
  1794. postProcess = function (args) {
  1795. that.blockList[thisBlock].value = null;
  1796. that.blockList[thisBlock].privateData = args[1];
  1797. };
  1798. postProcessArg = [thisBlock, arg];
  1799. }
  1800. var protoFound = false;
  1801. for (var proto in that.protoBlockDict) {
  1802. if (that.protoBlockDict[proto].name === name) {
  1803. if (arg === '__NOARG__') {
  1804. that.makeNewBlock(proto, postProcess, postProcessArg);
  1805. protoFound = true;
  1806. break;
  1807. } else if (that.protoBlockDict[proto].defaults[0] === arg) {
  1808. that.makeNewBlock(proto, postProcess, postProcessArg);
  1809. protoFound = true;
  1810. break;
  1811. } else if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg', 'namedarg'].indexOf(name) !== -1) {
  1812. if (that.protoBlockDict[proto].defaults[0] === undefined) {
  1813. that.makeNewBlock(proto, postProcess, postProcessArg);
  1814. protoFound = true;
  1815. break;
  1816. }
  1817. }
  1818. }
  1819. }
  1820. if (!protoFound) {
  1821. console.log(name + ' not found!!');
  1822. }
  1823. var blk = this.blockList.length - 1;
  1824. var myBlock = this.blockList[blk];
  1825. for (var i = 0; i < myBlock.docks.length; i++) {
  1826. myBlock.connections.push(null);
  1827. }
  1828. // Attach default args if any
  1829. var cblk = blk + 1;
  1830. for (var i = 0; i < myBlock.protoblock.defaults.length; i++) {
  1831. var value = myBlock.protoblock.defaults[i];
  1832. if (myBlock.name === 'action') {
  1833. // Make sure we don't make two actions with the same name.
  1834. value = this.findUniqueActionName(_('action'));
  1835. if (value !== _('action')) {
  1836. // TODO: are there return or arg blocks?
  1837. this.newNameddoBlock(value, false, false);
  1838. this.palettes.hide();
  1839. this.palettes.updatePalettes('action');
  1840. this.palettes.show();
  1841. }
  1842. }
  1843. var that = this;
  1844. var thisBlock = this.blockList.length;
  1845. if (myBlock.docks.length > i && myBlock.docks[i + 1][2] === 'anyin') {
  1846. if (value == null) {
  1847. console.log('cannot set default value');
  1848. } else if (typeof(value) === 'string') {
  1849. postProcess = function (args) {
  1850. var thisBlock = args[0];
  1851. var value = args[1];
  1852. that.blockList[thisBlock].value = value;
  1853. var label = value.toString();
  1854. if (label.length > 8) {
  1855. label = label.substr(0, 7) + '...';
  1856. }
  1857. that.blockList[thisBlock].text.text = label;
  1858. that.blockList[thisBlock].container.updateCache();
  1859. };
  1860. this.makeNewBlock('text', postProcess, [thisBlock, value]);
  1861. } else {
  1862. postProcess = function (args) {
  1863. var thisBlock = args[0];
  1864. var value = Number(args[1]);
  1865. that.blockList[thisBlock].value = value;
  1866. that.blockList[thisBlock].text.text = value.toString();
  1867. };
  1868. this.makeNewBlock('number', postProcess, [thisBlock, value]);
  1869. }
  1870. } else if (myBlock.docks[i + 1][2] === 'textin') {
  1871. postProcess = function (args) {
  1872. var thisBlock = args[0];
  1873. var value = args[1];
  1874. that.blockList[thisBlock].value = value;
  1875. var label = value.toString();
  1876. if (label.length > 8) {
  1877. label = label.substr(0, 7) + '...';
  1878. }
  1879. that.blockList[thisBlock].text.text = label;
  1880. };
  1881. this.makeNewBlock('text', postProcess, [thisBlock, value]);
  1882. } else if (myBlock.docks[i + 1][2] === 'solfegein') {
  1883. postProcess = function (args) {
  1884. var thisBlock = args[0];
  1885. var value = args[1];
  1886. that.blockList[thisBlock].value = value;
  1887. var label = value.toString();
  1888. that.blockList[thisBlock].text.text = label;
  1889. };
  1890. this.makeNewBlock('solfege', postProcess, [thisBlock, value]);
  1891. } else if (myBlock.docks[i + 1][2] === 'notein') {
  1892. postProcess = function (args) {
  1893. var thisBlock = args[0];
  1894. var value = args[1];
  1895. that.blockList[thisBlock].value = value;
  1896. var label = value.toString();
  1897. that.blockList[thisBlock].text.text = label;
  1898. };
  1899. this.makeNewBlock('notename', postProcess, [thisBlock, value]);
  1900. } else if (myBlock.docks[i + 1][2] === 'mediain') {
  1901. postProcess = function (args) {
  1902. var thisBlock = args[0];
  1903. var value = args[1];
  1904. that.blockList[thisBlock].value = value;
  1905. if (value != null) {
  1906. // loadThumbnail(that, thisBlock, null);
  1907. }
  1908. };
  1909. this.makeNewBlock('media', postProcess, [thisBlock, value]);
  1910. } else if (myBlock.docks[i + 1][2] === 'filein') {
  1911. postProcess = function (blk) {
  1912. that.updateBlockText(blk);
  1913. }
  1914. this.makeNewBlock('loadFile', postProcess, thisBlock);
  1915. } else {
  1916. postProcess = function (args) {
  1917. var thisBlock = args[0];
  1918. var value = args[1];
  1919. that.blockList[thisBlock].value = value;
  1920. that.blockList[thisBlock].text.text = value.toString();
  1921. };
  1922. this.makeNewBlock('number', postProcess, [thisBlock, value]);
  1923. }
  1924. var myConnectionBlock = this.blockList[cblk + i];
  1925. myConnectionBlock.connections = [blk];
  1926. myConnectionBlock.value = value;
  1927. myBlock.connections[i + 1] = cblk + i;
  1928. }
  1929. // Generate and position the block bitmaps and labels
  1930. this.updateBlockPositions();
  1931. // console.log('Adjust Docks: ' + this.blockList[blk].name);
  1932. this.adjustDocks(blk, true);
  1933. this.refreshCanvas();
  1934. return blk;
  1935. };
  1936. this.findDragGroup = function (blk) {
  1937. // Generate a drag group from blocks connected to blk
  1938. this.dragLoopCounter = 0;
  1939. this.dragGroup = [];
  1940. this._calculateDragGroup(blk);
  1941. };
  1942. this._calculateDragGroup = function (blk) {
  1943. // Give a block, find all the blocks connected to it
  1944. this.dragLoopCounter += 1;
  1945. if (this.dragLoopCount > this.blockList.length) {
  1946. console.log('maximum loop counter exceeded in calculateDragGroup... this is bad. ' + blk);
  1947. return;
  1948. }
  1949. if (blk == null) {
  1950. console.log('null block passed to calculateDragGroup');
  1951. return;
  1952. }
  1953. var myBlock = this.blockList[blk];
  1954. // If this happens, something is really broken.
  1955. if (myBlock == null) {
  1956. console.log('null block encountered... this is bad. ' + blk);
  1957. return;
  1958. }
  1959. // As before, does these ever happen?
  1960. if (myBlock.connections == null) {
  1961. this.dragGroup = [blk];
  1962. return;
  1963. }
  1964. // Some malformed blocks might have no connections.
  1965. if (myBlock.connections.length === 0) {
  1966. this.dragGroup = [blk];
  1967. return;
  1968. }
  1969. this.dragGroup.push(blk);
  1970. for (var c = 1; c < myBlock.connections.length; c++) {
  1971. var cblk = myBlock.connections[c];
  1972. if (cblk != null) {
  1973. // Recurse
  1974. this._calculateDragGroup(cblk);
  1975. }
  1976. }
  1977. };
  1978. this.setActionProtoVisiblity = function (state) {
  1979. // By default, the nameddo protoblock is hidden.
  1980. var actionsPalette = this.palettes.dict['action'];
  1981. var stateChanged = false;
  1982. for (var blockId = 0; blockId < actionsPalette.protoList.length; blockId++) {
  1983. var block = actionsPalette.protoList[blockId];
  1984. if ('nameddo' === block.name && block.defaults.length === 0) {
  1985. if (block.hidden === state) {
  1986. block.hidden = !state;
  1987. stateChanged = true;
  1988. }
  1989. }
  1990. }
  1991. // Force an update if the name has changed.
  1992. if (stateChanged) {
  1993. this.palettes.hide();
  1994. this.palettes.updatePalettes('action');
  1995. this.palettes.show();
  1996. }
  1997. };
  1998. this.findUniqueActionName = function (name) {
  1999. // If we have a stack named 'action', make the protoblock visible.
  2000. if (name === _('action')) {
  2001. this.setActionProtoVisiblity(true);
  2002. }
  2003. // Make sure we don't make two actions with the same name.
  2004. var actionNames = [];
  2005. for (var blk = 0; blk < this.blockList.length; blk++) {
  2006. if ((this.blockList[blk].name === 'text' || this.blockList[blk].name === 'string') && !this.blockList[blk].trash) {
  2007. var c = this.blockList[blk].connections[0];
  2008. if (c != null && this.blockList[c].name === 'action' && !this.blockList[c].trash) {
  2009. actionNames.push(this.blockList[blk].value);
  2010. }
  2011. }
  2012. }
  2013. var i = 1;
  2014. var value = name;
  2015. while (actionNames.indexOf(value) !== -1) {
  2016. value = name + i.toString();
  2017. i += 1;
  2018. }
  2019. return value;
  2020. };
  2021. this._findDrumURLs = function () {
  2022. // Make sure we initialize any drum with a URL name.
  2023. for (var blk = 0; blk < this.blockList.length; blk++) {
  2024. if (this.blockList[blk].name === 'text' || this.blockList[blk].name === 'string') {
  2025. var c = this.blockList[blk].connections[0];
  2026. if (c != null && ['playdrum', 'setdrum', 'setvoice'].indexOf(this.blockList[c].name) !== -1) {
  2027. if (this.blockList[blk].value.slice(0, 4) === 'http') {
  2028. if (_THIS_IS_MUSIC_BLOCKS_) {
  2029. this.logo.synth.loadSynth(this.blockList[blk].value);
  2030. }
  2031. }
  2032. }
  2033. }
  2034. }
  2035. };
  2036. this.renameBoxes = function (oldName, newName) {
  2037. if (oldName === newName) {
  2038. return;
  2039. }
  2040. for (var blk = 0; blk < this.blockList.length; blk++) {
  2041. if (this.blockList[blk].name === 'text') {
  2042. var c = this.blockList[blk].connections[0];
  2043. if (c != null && this.blockList[c].name === 'box') {
  2044. if (this.blockList[blk].value === oldName) {
  2045. this.blockList[blk].value = newName;
  2046. this.blockList[blk].text.text = newName;
  2047. try {
  2048. this.blockList[blk].container.updateCache();
  2049. } catch (e) {
  2050. console.log(e);
  2051. }
  2052. }
  2053. }
  2054. }
  2055. }
  2056. };
  2057. this.renameNamedboxes = function (oldName, newName) {
  2058. if (oldName === newName) {
  2059. return;
  2060. }
  2061. for (var blk = 0; blk < this.blockList.length; blk++) {
  2062. if (this.blockList[blk].name === 'namedbox') {
  2063. if (this.blockList[blk].privateData === oldName) {
  2064. this.blockList[blk].privateData = newName;
  2065. this.blockList[blk].overrideName = newName;
  2066. this.blockList[blk].regenerateArtwork();
  2067. // Update label...
  2068. try {
  2069. this.blockList[blk].container.updateCache();
  2070. } catch (e) {
  2071. console.log(e);
  2072. }
  2073. }
  2074. }
  2075. }
  2076. // Update the palette
  2077. var blockPalette = this.palettes.dict['boxes'];
  2078. var nameChanged = false;
  2079. for (var blockId = 0; blockId < blockPalette.protoList.length; blockId++) {
  2080. var block = blockPalette.protoList[blockId];
  2081. if (block.name === 'namedbox' && block.defaults[0] !== _('box') && block.defaults[0] === oldName) {
  2082. // console.log('renaming ' + block.defaults[0] + ' to ' + newName);
  2083. block.defaults[0] = newName;
  2084. nameChanged = true;
  2085. }
  2086. }
  2087. // Force an update if the name has changed.
  2088. if (nameChanged) {
  2089. this.palettes.hide();
  2090. this.palettes.updatePalettes('boxes');
  2091. this.palettes.show();
  2092. }
  2093. };
  2094. this.renameDos = function (oldName, newName, skipBlock) {
  2095. if (oldName === newName) {
  2096. return;
  2097. }
  2098. // Update the blocks, do->oldName should be do->newName
  2099. // Named dos are modified in a separate function below.
  2100. for (var blk = 0; blk < this.blockList.length; blk++) {
  2101. if (blk === skipBlock) {
  2102. continue;
  2103. }
  2104. var myBlock = this.blockList[blk];
  2105. var blkParent = this.blockList[myBlock.connections[0]];
  2106. if (blkParent == null) {
  2107. continue;
  2108. }
  2109. if (['do', 'calc', 'doArg', 'calcArg', 'action'].indexOf(blkParent.name) === -1) {
  2110. continue;
  2111. }
  2112. var blockValue = myBlock.value;
  2113. if (blockValue === oldName) {
  2114. myBlock.value = newName;
  2115. var label = myBlock.value;
  2116. if (label.length > 8) {
  2117. label = label.substr(0, 7) + '...';
  2118. }
  2119. myBlock.text.text = label;
  2120. myBlock.container.updateCache();
  2121. }
  2122. }
  2123. };
  2124. this.renameNameddos = function (oldName, newName) {
  2125. if (oldName === newName) {
  2126. return;
  2127. }
  2128. // Update the blocks, do->oldName should be do->newName
  2129. for (var blk = 0; blk < this.blockList.length; blk++) {
  2130. if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(this.blockList[blk].name) !== -1) {
  2131. if (this.blockList[blk].privateData === oldName) {
  2132. this.blockList[blk].privateData = newName;
  2133. var label = newName;
  2134. if (label.length > 8) {
  2135. label = label.substr(0, 7) + '...';
  2136. }
  2137. this.blockList[blk].overrideName = label;
  2138. // console.log('regenerating artwork for ' + this.blockList[blk].name + ' block[' + blk + ']: ' + oldName + ' -> ' + label);
  2139. this.blockList[blk].regenerateArtwork();
  2140. }
  2141. }
  2142. }
  2143. // Update the palette
  2144. var actionsPalette = this.palettes.dict['action'];
  2145. var nameChanged = false;
  2146. for (var blockId = 0; blockId < actionsPalette.protoList.length; blockId++) {
  2147. var block = actionsPalette.protoList[blockId];
  2148. if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(block.name) !== -1 /* && block.defaults[0] !== _('action') */ && block.defaults[0] === oldName) {
  2149. // console.log('renaming ' + block.name + ': ' + block.defaults[0] + ' to ' + newName);
  2150. block.defaults[0] = newName;
  2151. nameChanged = true;
  2152. }
  2153. }
  2154. // Force an update if the name has changed.
  2155. if (nameChanged) {
  2156. this.palettes.hide();
  2157. this.palettes.updatePalettes('action');
  2158. this.palettes.show();
  2159. }
  2160. };
  2161. this.newStoreinBlock = function (name) {
  2162. if (name == null) {
  2163. console.log('null name passed to newStoreinBlock');
  2164. return;
  2165. } else if (name == undefined) {
  2166. console.log('undefined name passed to newStoreinBlock');
  2167. return;
  2168. } else if ('myStorein_' + name in this.protoBlockDict) {
  2169. // console.log(name + ' already in palette');
  2170. return;
  2171. }
  2172. // console.log('new storein block ' + name);
  2173. var myStoreinBlock = new ProtoBlock('storein');
  2174. this.protoBlockDict['myStorein_' + name] = myStoreinBlock;
  2175. myStoreinBlock.palette = this.palettes.dict['boxes'];
  2176. myStoreinBlock.defaults.push(name);
  2177. myStoreinBlock.defaults.push(NUMBERBLOCKDEFAULT);
  2178. myStoreinBlock.staticLabels.push(_('store in'), _('name'), _('value'));
  2179. myStoreinBlock.adjustWidthToLabel();
  2180. myStoreinBlock.twoArgBlock();
  2181. myStoreinBlock.dockTypes[1] = 'anyin';
  2182. myStoreinBlock.dockTypes[2] = 'anyin';
  2183. if (name === 'box') {
  2184. return;
  2185. }
  2186. // Add the new block to the top of the palette.
  2187. myStoreinBlock.palette.add(myStoreinBlock, true);
  2188. };
  2189. this.newNamedboxBlock = function (name) {
  2190. if (name == null) {
  2191. console.log('null name passed to newNamedboxBlock');
  2192. return;
  2193. } else if (name == undefined) {
  2194. console.log('undefined name passed to newNamedboxBlock');
  2195. return;
  2196. } else if ('myBox_' + name in this.protoBlockDict) {
  2197. return;
  2198. }
  2199. var myBoxBlock = new ProtoBlock('namedbox');
  2200. this.protoBlockDict['myBox_' + name] = myBoxBlock;
  2201. myBoxBlock.palette = this.palettes.dict['boxes'];
  2202. myBoxBlock.defaults.push(name);
  2203. myBoxBlock.staticLabels.push(name);
  2204. myBoxBlock.parameterBlock();
  2205. if (name === 'box') {
  2206. return;
  2207. }
  2208. // Add the new block to the top of the palette.
  2209. myBoxBlock.palette.add(myBoxBlock, true);
  2210. };
  2211. this._newLocalArgBlock = function (name) {
  2212. // name === 1, 2, 3, ...
  2213. var blkname = 'arg_' + name;
  2214. if ('myArg_' + name in this.protoBlockDict) {
  2215. return;
  2216. }
  2217. if (blkname in this.protoBlockDict) {
  2218. return;
  2219. }
  2220. var myNamedArgBlock = new ProtoBlock('namedarg');
  2221. this.protoBlockDict['myArg_' + blkname] = myNamedArgBlock;
  2222. myNamedArgBlock.palette = this.palettes.dict['action'];
  2223. myNamedArgBlock.defaults.push(name);
  2224. myNamedArgBlock.staticLabels.push('arg ' + name);
  2225. myNamedArgBlock.parameterBlock();
  2226. if (blkname === 'arg_1') {
  2227. return;
  2228. }
  2229. myNamedArgBlock.palette.add(myNamedArgBlock, true);
  2230. // Force regeneration of palette after adding new block.
  2231. // Add delay to avoid race condition.
  2232. var that = this;
  2233. setTimeout(function () {
  2234. that.palettes.hide();
  2235. that.palettes.updatePalettes('action');
  2236. that.palettes.show();
  2237. }, 500);
  2238. };
  2239. this._removeNamedoEntries = function (name) {
  2240. // Delete any old palette entries.
  2241. // console.log('DELETE: removing old palette entries for ' + name);
  2242. if (this.protoBlockDict['myDo_' + name]) {
  2243. // console.log('deleting myDo_' + name + ' ' + this.protoBlockDict['myDo_' + name].name);
  2244. this.protoBlockDict['myDo_' + name].hide = true;
  2245. delete this.protoBlockDict['myDo_' + name];
  2246. } else if (this.protoBlockDict['myCalc_' + name]) {
  2247. // console.log('deleting myCalc_' + name + ' ' + this.protoBlockDict['myCalc_' + name].name);
  2248. this.protoBlockDict['myCalc_' + name].hide = true;
  2249. delete this.protoBlockDict['myCalc_' + name];
  2250. } else if (this.protoBlockDict['myDoArg_' + name]) {
  2251. // console.log('deleting myDoArg_' + name + ' ' + this.protoBlockDict['myDoArg_' + name].name);
  2252. this.protoBlockDict['myDoArg_' + name].hide = true;
  2253. delete this.protoBlockDict['myDoArg_' + name];
  2254. } else if (this.protoBlockDict['myCalcArg_' + name]) {
  2255. // console.log('deleting myCalcArg_' + name + ' ' + this.protoBlockDict['myCalcArg_' + name].name);
  2256. this.protoBlockDict['myCalcArg_' + name].hide = true;
  2257. delete this.protoBlockDict['myCalcArg_' + name];
  2258. }
  2259. };
  2260. this.newNameddoBlock = function (name, hasReturn, hasArgs) {
  2261. // Depending upon the form of the associated action block, we
  2262. // want to add a named do, a named calc, a named do w/args, or
  2263. // a named calc w/args.
  2264. if (name === _('action')) {
  2265. // 'action' already has its associated palette entries.
  2266. return false;
  2267. }
  2268. if (hasReturn && hasArgs) {
  2269. this.newNamedcalcArgBlock(name);
  2270. return true;
  2271. } else if (!hasReturn && hasArgs) {
  2272. this.newNameddoArgBlock(name);
  2273. return true;
  2274. } else if (hasReturn && !hasArgs) {
  2275. this.newNamedcalcBlock(name);
  2276. return true;
  2277. } else if (this.protoBlockDict['myDo_' + name] === undefined) {
  2278. var myDoBlock = new ProtoBlock('nameddo');
  2279. this.protoBlockDict['myDo_' + name] = myDoBlock;
  2280. myDoBlock.palette = this.palettes.dict['action'];
  2281. myDoBlock.defaults.push(name);
  2282. myDoBlock.staticLabels.push(name);
  2283. myDoBlock.zeroArgBlock();
  2284. myDoBlock.palette.add(myDoBlock, true);
  2285. this.palettes.updatePalettes();
  2286. return true;
  2287. }
  2288. return false;
  2289. };
  2290. this.newNamedcalcBlock = function (name) {
  2291. if (this.protoBlockDict['myCalc_' + name] === undefined) {
  2292. // console.log('creating myCalc_' + name);
  2293. var myCalcBlock = new ProtoBlock('namedcalc');
  2294. this.protoBlockDict['myCalc_' + name] = myCalcBlock;
  2295. myCalcBlock.palette = this.palettes.dict['action'];
  2296. myCalcBlock.defaults.push(name);
  2297. myCalcBlock.staticLabels.push(name);
  2298. myCalcBlock.zeroArgBlock();
  2299. // Add the new block to the top of the palette.
  2300. myCalcBlock.palette.add(myCalcBlock, true);
  2301. // } else {
  2302. // console.log('myCalc_' + name + ' already exists.');
  2303. }
  2304. };
  2305. this.newNameddoArgBlock = function (name) {
  2306. if (this.protoBlockDict['myDoArg_' + name] === undefined) {
  2307. // console.log('creating myDoArg_' + name);
  2308. var myDoArgBlock = new ProtoBlock('nameddoArg');
  2309. this.protoBlockDict['myDoArg_' + name] = myDoArgBlock;
  2310. myDoArgBlock.palette = this.palettes.dict['action'];
  2311. myDoArgBlock.defaults.push(name);
  2312. myDoArgBlock.staticLabels.push(name);
  2313. myDoArgBlock.zeroArgBlock();
  2314. // Add the new block to the top of the palette.
  2315. myDoArgBlock.palette.add(myDoArgBlock, true);
  2316. // } else {
  2317. // console.log('myDoArg_' + name + ' already exists.');
  2318. }
  2319. };
  2320. this.newNamedcalcArgBlock = function (name) {
  2321. if (this.protoBlockDict['myCalcArg_' + name] === undefined) {
  2322. // console.log('creating myCalcArg_' + name);
  2323. var myCalcArgBlock = new ProtoBlock('namedcalcArg');
  2324. this.protoBlockDict['myCalcArg_' + name] = myCalcArgBlock;
  2325. myCalcArgBlock.palette = this.palettes.dict['action'];
  2326. myCalcArgBlock.defaults.push(name);
  2327. myCalcArgBlock.staticLabels.push(name);
  2328. myCalcArgBlock.zeroArgBlock();
  2329. // Add the new block to the top of the palette.
  2330. myCalcArgBlock.palette.add(myCalcArgBlock, true);
  2331. // } else {
  2332. // console.log('myCalcArg_' + name + ' already exists.');
  2333. }
  2334. };
  2335. this._insideArgClamp = function (blk) {
  2336. // Returns a containing arg clamp block or null
  2337. if (this.blockList[blk] == null) {
  2338. // race condition?
  2339. console.log('null block in blockList? ' + blk);
  2340. return null;
  2341. } else if (this.blockList[blk].connections[0] == null) {
  2342. return null;
  2343. } else {
  2344. var cblk = this.blockList[blk].connections[0];
  2345. if (this.blockList[cblk].isArgClamp()) {
  2346. return cblk;
  2347. } else {
  2348. return null;
  2349. }
  2350. }
  2351. };
  2352. this._insideExpandableBlock = function (blk) {
  2353. // Returns a containing expandable block or null
  2354. if (this.blockList[blk] == null) {
  2355. // race condition?
  2356. console.log('null block in blockList? ' + blk);
  2357. return null;
  2358. } else if (this.blockList[blk].connections[0] == null) {
  2359. return null;
  2360. } else {
  2361. var cblk = this.blockList[blk].connections[0];
  2362. if (this.blockList[cblk].isExpandableBlock()) {
  2363. // If it is the last connection, keep searching.
  2364. if (this.blockList[cblk].isArgFlowClampBlock()) {
  2365. return cblk;
  2366. } else if (blk === last(this.blockList[cblk].connections)) {
  2367. return this._insideExpandableBlock(cblk);
  2368. } else {
  2369. return cblk;
  2370. }
  2371. } else {
  2372. return this._insideExpandableBlock(cblk);
  2373. }
  2374. }
  2375. };
  2376. this._insideNoteBlock = function (blk) {
  2377. // Returns a containing note block or null
  2378. if (this.blockList[blk] == null) {
  2379. console.log('null block in blockList? ' + blk);
  2380. return null;
  2381. } else if (this.blockList[blk].connections[0] == null) {
  2382. return null;
  2383. } else {
  2384. var cblk = this.blockList[blk].connections[0];
  2385. if (this.blockList[cblk].isExpandableBlock()) {
  2386. // If it is the last connection, keep searching.
  2387. if (blk === last(this.blockList[cblk].connections)) {
  2388. return this._insideNoteBlock(cblk);
  2389. } else if (blk === this.blockList[cblk].connections[1]) {
  2390. // Connection 1 of a note block is not inside the clamp.
  2391. return null;
  2392. } else {
  2393. if (['newnote', 'osctime'].indexOf(this.blockList[cblk].name) !== -1) {
  2394. return cblk;
  2395. } else {
  2396. return null;
  2397. }
  2398. }
  2399. } else {
  2400. return this._insideNoteBlock(cblk);
  2401. }
  2402. }
  2403. };
  2404. this.triggerLongPress = function (myBlock) {
  2405. this.timeOut == null;
  2406. this.inLongPress = true;
  2407. var z = this.stage.getNumChildren() - 1;
  2408. // Auto-select stack for copying -- no need to actually click on
  2409. // the copy button.
  2410. var topBlock = this.findTopBlock(this.activeBlock);
  2411. this.selectedStack = topBlock;
  2412. // Copy the selectedStack.
  2413. this.selectedBlocksObj = JSON.parse(JSON.stringify(this._copyBlocksToObj()));
  2414. this.updatePasteButton();
  2415. if (myBlock.name === 'action') {
  2416. this.dismissButton.visible = true;
  2417. this.dismissButton.x = myBlock.container.x - 27;
  2418. this.dismissButton.y = myBlock.container.y - 27;
  2419. this.stage.setChildIndex(this.dismissButton, z - 1);
  2420. this.saveStackButton.visible = true;
  2421. this.saveStackButton.x = myBlock.container.x + 27;
  2422. this.saveStackButton.y = myBlock.container.y - 27;
  2423. this.stage.setChildIndex(this.saveStackButton, z - 2);
  2424. }
  2425. this.refreshCanvas();
  2426. };
  2427. this.pasteStack = function () {
  2428. // Copy a stack of blocks by creating a blockObjs and passing
  2429. // it to this.load.
  2430. if (this.selectedStack == null) {
  2431. return;
  2432. }
  2433. // First, hide the palettes as they will need updating.
  2434. for (var name in this.palettes.dict) {
  2435. this.palettes.dict[name].hideMenu(true);
  2436. }
  2437. // var blockObjs = this._copyBlocksToObj();
  2438. // this.loadNewBlocks(blockObjs);
  2439. // console.log(this.selectedBlocksObj);
  2440. this.loadNewBlocks(this.selectedBlocksObj);
  2441. };
  2442. this.saveStack = function () {
  2443. // Save a stack of blocks to local storage and the 'myblocks'
  2444. // palette by creating a blockObjs and ...
  2445. if (this.selectedStack == null) {
  2446. return;
  2447. }
  2448. this.palettes.hide();
  2449. var blockObjs = this._copyBlocksToObj();
  2450. // The first block is an action block. Its first connection is
  2451. // the block containing its label.
  2452. var nameBlk = blockObjs[0][4][1];
  2453. if (nameBlk == null) {
  2454. console.log('action not named... skipping');
  2455. } else {
  2456. console.log(blockObjs[nameBlk][1][1]);
  2457. if (typeof(blockObjs[nameBlk][1][1]) === 'string') {
  2458. var name = blockObjs[nameBlk][1][1];
  2459. } else if (typeof(blockObjs[nameBlk][1][1]) === 'number') {
  2460. var name = blockObjs[nameBlk][1][1].toString();
  2461. } else {
  2462. var name = blockObjs[nameBlk][1][1]['value'];
  2463. }
  2464. storage.macros = prepareMacroExports(name, blockObjs, this.macroDict);
  2465. if (sugarizerCompatibility.isInsideSugarizer()) {
  2466. sugarizerCompatibility.saveLocally(function () {
  2467. this.addToMyPalette(name, blockObjs);
  2468. // this.palettes.updatePalettes('myblocks');
  2469. });
  2470. } else {
  2471. this.addToMyPalette(name, blockObjs);
  2472. // this.palettes.updatePalettes('myblocks');
  2473. }
  2474. }
  2475. };
  2476. this._copyBlocksToObj = function () {
  2477. var blockObjs = [];
  2478. var blockMap = {};
  2479. this.findDragGroup(this.selectedStack);
  2480. for (var b = 0; b < this.dragGroup.length; b++) {
  2481. myBlock = this.blockList[this.dragGroup[b]];
  2482. if (b === 0) {
  2483. x = 75 - this.stage.x;
  2484. y = 75 - this.stage.y;
  2485. } else {
  2486. x = 0;
  2487. y = 0;
  2488. }
  2489. if (myBlock.isValueBlock()) {
  2490. switch (myBlock.name) {
  2491. case 'media':
  2492. blockItem = [b, [myBlock.name, null], x, y, []];
  2493. break;
  2494. default:
  2495. blockItem = [b, [myBlock.name, myBlock.value], x, y, []];
  2496. break;
  2497. }
  2498. } else if (['namedbox', 'nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg', 'namedarg'].indexOf(myBlock.name) !== -1) {
  2499. blockItem = [b, [myBlock.name, {'value': myBlock.privateData}], x, y, []];
  2500. } else {
  2501. blockItem = [b, myBlock.name, x, y, []];
  2502. }
  2503. blockMap[this.dragGroup[b]] = b;
  2504. blockObjs.push(blockItem);
  2505. }
  2506. for (var b = 0; b < this.dragGroup.length; b++) {
  2507. myBlock = this.blockList[this.dragGroup[b]];
  2508. for (var c = 0; c < myBlock.connections.length; c++) {
  2509. if (myBlock.connections[c] == null) {
  2510. blockObjs[b][4].push(null);
  2511. } else {
  2512. blockObjs[b][4].push(blockMap[myBlock.connections[c]]);
  2513. }
  2514. }
  2515. }
  2516. return blockObjs;
  2517. };
  2518. this.addToMyPalette = function (name, obj) {
  2519. // On the palette we store the macro as a basic block.
  2520. var myBlock = new ProtoBlock('macro_' + name);
  2521. var blkName = 'macro_' + name;
  2522. this.protoBlockDict[blkName] = myBlock;
  2523. if (!('myblocks' in this.palettes.dict)) {
  2524. this.palettes.add('myblocks');
  2525. }
  2526. myBlock.palette = this.palettes.dict['myblocks'];
  2527. myBlock.zeroArgBlock();
  2528. myBlock.staticLabels.push(_(name));
  2529. this.protoBlockDict[blkName].palette.add(this.protoBlockDict[blkName]);
  2530. };
  2531. this.loadNewBlocks = function (blockObjs) {
  2532. // Check for blocks connected to themselves,
  2533. // and for action blocks not connected to text blocks.
  2534. for (var b = 0; b < blockObjs.length; b++) {
  2535. var blkData = blockObjs[b];
  2536. for (var c in blkData[4]) {
  2537. if (blkData[4][c] === blkData[0]) {
  2538. console.log('Circular connection in block data: ' + blkData);
  2539. console.log('Punting loading of new blocks!');
  2540. console.log(blockObjs);
  2541. return;
  2542. }
  2543. }
  2544. }
  2545. // We'll need a list of existing storein and action names.
  2546. var currentActionNames = [];
  2547. var currentStoreinNames = [];
  2548. for (var b = 0; b < this.blockList.length; b++) {
  2549. if (this.blockList[b].trash) {
  2550. continue;
  2551. }
  2552. if (this.blockList[b].name === 'action') {
  2553. if (this.blockList[b].connections[1] != null) {
  2554. console.log(this.blockList[this.blockList[b].connections[1]].value);
  2555. currentActionNames.push(this.blockList[this.blockList[b].connections[1]].value);
  2556. }
  2557. } else if (this.blockList[b].name === 'storein') {
  2558. if (this.blockList[b].connections[1] != null) {
  2559. currentStoreinNames.push(this.blockList[this.blockList[b].connections[1]].value);
  2560. }
  2561. }
  2562. }
  2563. // We need to track two-arg blocks in case they need expanding.
  2564. this._checkTwoArgBlocks = [];
  2565. // And arg clamp blocks in case they need expanding.
  2566. this._checkArgClampBlocks = [];
  2567. // Don't make duplicate action names.
  2568. // Add a palette entry for any new storein blocks.
  2569. var stringNames = [];
  2570. var stringValues = {}; // label: [blocks with that label]
  2571. var actionNames = {}; // action block: label block
  2572. var storeinNames = {}; // storein block: label block
  2573. var doNames = {}; // do block: label block, nameddo block value
  2574. // action and start blocks that need to be collapsed.
  2575. this.blocksToCollapse = [];
  2576. // Scan for any new action and storein blocks to identify
  2577. // duplicates. We also need to track start and action blocks
  2578. // that may need to be collapsed.
  2579. for (var b = 0; b < blockObjs.length; b++) {
  2580. var blkData = blockObjs[b];
  2581. // blkData[1] could be a string or an object.
  2582. if (typeof(blkData[1]) === 'string') {
  2583. var name = blkData[1];
  2584. } else {
  2585. var name = blkData[1][0];
  2586. }
  2587. if (!(name in this.protoBlockDict)) {
  2588. switch (name) {
  2589. case 'hat':
  2590. name = 'action';
  2591. break;
  2592. case 'string':
  2593. name = 'text';
  2594. break;
  2595. default:
  2596. console.log('skipping ' + name);
  2597. continue;
  2598. break;
  2599. }
  2600. }
  2601. if (['arg', 'twoarg'].indexOf(this.protoBlockDict[name].style) !== -1) {
  2602. if (this.protoBlockDict[name].expandable) {
  2603. this._checkTwoArgBlocks.push(this.blockList.length + b);
  2604. }
  2605. }
  2606. // FIXME: Use tests in block.js
  2607. if (['clamp', 'argclamp', 'argclamparg', 'doubleclamp', 'argflowclamp'].indexOf(this.protoBlockDict[name].style) !== -1) {
  2608. this._checkArgClampBlocks.push(this.blockList.length + b);
  2609. }
  2610. switch (name) {
  2611. case 'text':
  2612. var key = blkData[1][1];
  2613. if (stringValues[key] === undefined) {
  2614. stringValues[key] = [];
  2615. }
  2616. stringValues[key].push(b);
  2617. break;
  2618. case 'action':
  2619. case 'hat':
  2620. if (blkData[4][1] != null) {
  2621. actionNames[b] = blkData[4][1];
  2622. }
  2623. break;
  2624. case 'storein':
  2625. if (blkData[4][1] != null) {
  2626. storeinNames[b] = blkData[4][1];
  2627. }
  2628. break;
  2629. case 'nameddo':
  2630. case 'namedcalc':
  2631. case 'nameddoArg':
  2632. case 'namedcalcArg':
  2633. doNames[b] = blkData[1][1]['value'];
  2634. break;
  2635. case 'do':
  2636. case 'stack':
  2637. if (blkData[4][1] != null) {
  2638. doNames[b] = blkData[4][1];
  2639. }
  2640. break;
  2641. default:
  2642. break;
  2643. }
  2644. switch (name) {
  2645. case 'action':
  2646. case 'pitchdrummatrix':
  2647. case 'rhythmruler':
  2648. case 'pitchstaircase':
  2649. case 'tempo':
  2650. case 'pitchslider':
  2651. case 'matrix':
  2652. case 'drum':
  2653. case 'status':
  2654. case 'start':
  2655. if (typeof(blkData[1]) === 'object' && blkData[1].length > 1 && typeof(blkData[1][1]) === 'object' && 'collapsed' in blkData[1][1]) {
  2656. if (blkData[1][1]['collapsed']) {
  2657. this.blocksToCollapse.push(this.blockList.length + b);
  2658. }
  2659. }
  2660. break;
  2661. default:
  2662. break;
  2663. }
  2664. }
  2665. var updatePalettes = false;
  2666. // Make sure new storein names have palette entries.
  2667. for (var b in storeinNames) {
  2668. var blkData = blockObjs[storeinNames[b]];
  2669. if (currentStoreinNames.indexOf(blkData[1][1]) === -1) {
  2670. if (typeof(blkData[1][1]) === 'string') {
  2671. var name = blkData[1][1];
  2672. } else {
  2673. var name = blkData[1][1]['value'];
  2674. }
  2675. // console.log('Adding new palette entries for store-in ' + name);
  2676. this.newStoreinBlock(name);
  2677. this.newNamedboxBlock(name);
  2678. updatePalettes = true;
  2679. }
  2680. }
  2681. // Make sure action names are unique.
  2682. for (var b in actionNames) {
  2683. // Is there a proto do block with this name? If so, find a
  2684. // new name.
  2685. // Name = the value of the connected label.
  2686. var blkData = blockObjs[actionNames[b]];
  2687. if (typeof(blkData[1][1]) === 'string') {
  2688. var name = blkData[1][1];
  2689. } else {
  2690. var name = blkData[1][1]['value'];
  2691. }
  2692. // If we have a stack named 'action', make the protoblock visible.
  2693. if (name === _('action')) {
  2694. this.setActionProtoVisiblity(true);
  2695. }
  2696. var oldName = name;
  2697. var i = 1;
  2698. while (currentActionNames.indexOf(name) !== -1) {
  2699. name = oldName + i.toString();
  2700. i += 1;
  2701. // Should never happen... but just in case.
  2702. if (i > this.blockList.length) {
  2703. console.log('Could not generate unique action name.');
  2704. break;
  2705. }
  2706. }
  2707. if (oldName !== name) {
  2708. // Change the name of the action...
  2709. console.log('action ' + oldName + ' is being renamed ' + name);
  2710. blkData[1][1] = {'value': name};
  2711. }
  2712. // and any do blocks
  2713. for (var d in doNames) {
  2714. var thisBlkData = blockObjs[d];
  2715. if (typeof(thisBlkData[1]) === 'string') {
  2716. var blkName = thisBlkData[1];
  2717. } else {
  2718. var blkName = thisBlkData[1][0];
  2719. }
  2720. if (['nameddo', 'namedcalc', 'nameddoArg', 'namedcalcArg'].indexOf(blkName) !== -1) {
  2721. if (thisBlkData[1][1]['value'] === oldName) {
  2722. thisBlkData[1][1] = {'value': name};
  2723. }
  2724. } else {
  2725. var doBlkData = blockObjs[doNames[d]];
  2726. if (typeof(doBlkData[1][1]) === 'string') {
  2727. if (doBlkData[1][1] === oldName) {
  2728. // console.log('renaming ' + oldName + ' to ' + name);
  2729. doBlkData[1][1] = name;
  2730. }
  2731. } else {
  2732. if (doBlkData[1][1]['value'] === oldName) {
  2733. // console.log('renaming ' + oldName + ' to ' + name);
  2734. doBlkData[1][1] = {'value': name};
  2735. }
  2736. }
  2737. }
  2738. }
  2739. }
  2740. if (updatePalettes) {
  2741. this.palettes.hide();
  2742. this.palettes.updatePalettes('action');
  2743. this.palettes.show();
  2744. }
  2745. // This section of the code attempts to repair imported
  2746. // code. For example, it adds missing hidden blocks and
  2747. // convert old-style notes to new-style notes.
  2748. blockObjsLength = blockObjs.length;
  2749. var extraBlocksLength = 0;
  2750. for (var b = 0; b < blockObjsLength; b++) {
  2751. if (typeof(blockObjs[b][1]) === 'object') {
  2752. var name = blockObjs[b][1][0];
  2753. } else {
  2754. var name = blockObjs[b][1];
  2755. }
  2756. switch (name) {
  2757. case 'articulation':
  2758. case 'augmented':
  2759. case 'backward':
  2760. case 'crescendo':
  2761. case 'diminished':
  2762. case 'dividebeatfactor':
  2763. case 'drift':
  2764. case 'duplicatenotes':
  2765. case 'invert1':
  2766. case 'fill':
  2767. case 'flat':
  2768. case 'hollowline':
  2769. case 'major':
  2770. case 'minor':
  2771. case 'multiplybeatfactor':
  2772. case 'note':
  2773. case 'newnote':
  2774. case 'newslur':
  2775. case 'newstaccato':
  2776. case 'newswing':
  2777. case 'newswing2':
  2778. case 'osctime':
  2779. case 'perfect':
  2780. case 'pluck':
  2781. case 'rhythmicdot':
  2782. case 'setbpm':
  2783. case 'setnotevolume2':
  2784. case 'settransposition':
  2785. case 'setvoice':
  2786. case 'sharp':
  2787. case 'skipnotes':
  2788. case 'slur':
  2789. case 'staccato':
  2790. case 'swing':
  2791. case 'tie':
  2792. case 'tuplet2':
  2793. case 'vibrato':
  2794. var len = blockObjs[b][4].length;
  2795. if (last(blockObjs[b][4]) == null) {
  2796. // If there is no next block, add a hidden block;
  2797. console.log('last connection of ' + name + ' is null: adding hidden block');
  2798. blockObjs[b][4][len - 1] = blockObjsLength + extraBlocksLength;
  2799. blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, null]]);
  2800. extraBlocksLength += 1;
  2801. } else {
  2802. var nextBlock = blockObjs[b][4][len - 1];
  2803. if (typeof(blockObjs[nextBlock][1]) === 'object') {
  2804. var nextName = blockObjs[nextBlock][1][0];
  2805. } else {
  2806. var nextName = blockObjs[nextBlock][1];
  2807. }
  2808. if (nextName !== 'hidden') {
  2809. console.log('last connection of ' + name + ' is ' + nextName + ': adding hidden block');
  2810. // If the next block is not a hidden block, add one.
  2811. blockObjs[b][4][len - 1] = blockObjsLength + extraBlocksLength;
  2812. blockObjs[nextBlock][4][0] = blockObjsLength + extraBlocksLength;
  2813. blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, nextBlock]]);
  2814. extraBlocksLength += 1;
  2815. }
  2816. }
  2817. if (['note', 'slur', 'staccato', 'swing'].indexOf(name) !== -1) {
  2818. // We need to convert to newnote style:
  2819. // (1) add a vspace to the start of the clamp of a note block.
  2820. console.log('note: ' + b);
  2821. var clampBlock = blockObjs[b][4][2];
  2822. blockObjs[b][4][2] = blockObjsLength + extraBlocksLength;
  2823. if (clampBlock == null) {
  2824. blockObjs.push([blockObjsLength + extraBlocksLength, 'vspace', 0, 0, [b, null]]);
  2825. } else {
  2826. blockObjs[clampBlock][4][0] = blockObjsLength + extraBlocksLength;
  2827. blockObjs.push([blockObjsLength + extraBlocksLength, 'vspace', 0, 0, [b, clampBlock]]);
  2828. }
  2829. extraBlocksLength += 1;
  2830. // (2) switch the first connection to divide 1 / arg.
  2831. var argBlock = blockObjs[b][4][1];
  2832. blockObjs[b][4][1] = blockObjsLength + extraBlocksLength;
  2833. if (argBlock == null) {
  2834. blockObjs.push([blockObjsLength + extraBlocksLength, 'divide', 0, 0, [b, blockObjsLength + extraBlocksLength + 1, blockObjsLength + extraBlocksLength + 2]]);
  2835. blockObjs.push([blockObjsLength + extraBlocksLength + 1, ['number', {'value': 1}], 0, 0, [blockObjsLength + extraBlocksLength]]);
  2836. blockObjs.push([blockObjsLength + extraBlocksLength + 2, ['number', {'value': 1}], 0, 0, [blockObjsLength + extraBlocksLength]]);
  2837. extraBlocksLength += 3;
  2838. } else {
  2839. blockObjs[argBlock][4][0] = blockObjsLength + extraBlocksLength;
  2840. blockObjs.push([blockObjsLength + extraBlocksLength, 'divide', 0, 0, [b, blockObjsLength + extraBlocksLength + 1, argBlock]]);
  2841. blockObjs.push([blockObjsLength + extraBlocksLength + 1, ['number', {'value': 1}], 0, 0, [blockObjsLength + extraBlocksLength]]);
  2842. extraBlocksLength += 2;
  2843. }
  2844. // (3) create a newnote block instead.
  2845. if (typeof(blockObjs[b][1]) === 'object') {
  2846. blockObjs[b][1][0] = 'new' + name;
  2847. } else {
  2848. blockObjs[b][1] = 'new' + name;
  2849. }
  2850. }
  2851. break;
  2852. case 'action':
  2853. // Ensure that there is a hidden block as the first
  2854. // block in the child flow (connection 2) of an action
  2855. // block (required to make the backward block function
  2856. // propperly).
  2857. var len = blockObjs[b][4].length;
  2858. if (blockObjs[b][4][2] == null) {
  2859. // If there is no child flow block, add a hidden block;
  2860. console.log('last connection of ' + name + ' is null: adding hidden block');
  2861. blockObjs[b][4][2] = blockObjsLength + extraBlocksLength;
  2862. blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, null]]);
  2863. extraBlocksLength += 1;
  2864. } else {
  2865. var nextBlock = blockObjs[b][4][2];
  2866. if (typeof(blockObjs[nextBlock][1]) === 'object') {
  2867. var nextName = blockObjs[nextBlock][1][0];
  2868. } else {
  2869. var nextName = blockObjs[nextBlock][1];
  2870. }
  2871. if (nextName !== 'hidden') {
  2872. console.log('last connection of ' + name + ' is ' + nextName + ': adding hidden block');
  2873. // If the next block is not a hidden block, add one.
  2874. blockObjs[b][4][2] = blockObjsLength + extraBlocksLength;
  2875. blockObjs[nextBlock][4][0] = blockObjsLength + extraBlocksLength;
  2876. blockObjs.push([blockObjsLength + extraBlocksLength, 'hidden', 0, 0, [b, nextBlock]]);
  2877. extraBlocksLength += 1;
  2878. }
  2879. }
  2880. break;
  2881. default:
  2882. break;
  2883. }
  2884. }
  2885. // Append to the current set of blocks.
  2886. this._adjustTheseStacks = [];
  2887. this._adjustTheseDocks = [];
  2888. this._loadCounter = blockObjs.length;
  2889. // We add new blocks to the end of the block list.
  2890. var blockOffset = this.blockList.length;
  2891. var firstBlock = this.blockList.length;
  2892. for (var b = 0; b < this._loadCounter; b++) {
  2893. var thisBlock = blockOffset + b;
  2894. var blkData = blockObjs[b];
  2895. if (typeof(blkData[1]) === 'object') {
  2896. if (blkData[1].length === 1) {
  2897. var blkInfo = [blkData[1][0], {'value': null}];
  2898. } else if (['number', 'string'].indexOf(typeof(blkData[1][1])) !== -1) {
  2899. var blkInfo = [blkData[1][0], {'value': blkData[1][1]}];
  2900. if (COLLAPSABLES.indexOf(blkData[1][0]) !== -1) {
  2901. blkInfo[1]['collapsed'] = false;
  2902. }
  2903. } else {
  2904. var blkInfo = blkData[1];
  2905. }
  2906. } else {
  2907. var blkInfo = [blkData[1], {'value': null}];
  2908. if (COLLAPSABLES.indexOf(blkData[1]) !== -1) {
  2909. blkInfo[1]['collapsed'] = false;
  2910. }
  2911. }
  2912. var name = blkInfo[0];
  2913. var collapsed = false;
  2914. if (COLLAPSABLES.indexOf(name) !== -1) {
  2915. collapsed = blkInfo[1]['collapsed'];
  2916. }
  2917. if (blkInfo[1] == null) {
  2918. var value = null;
  2919. } else {
  2920. var value = blkInfo[1]['value'];
  2921. }
  2922. if (name in NAMEDICT) {
  2923. name = NAMEDICT[name];
  2924. }
  2925. var that = this;
  2926. // A few special cases.
  2927. switch (name) {
  2928. // Only add 'collapsed' arg to start, action blocks.
  2929. case 'start':
  2930. blkData[4][0] = null;
  2931. blkData[4][2] = null;
  2932. postProcess = function (args) {
  2933. var thisBlock = args[0];
  2934. var blkInfo = args[1];
  2935. that.blockList[thisBlock].value = that.turtles.turtleList.length;
  2936. that.turtles.addTurtle(that.blockList[thisBlock], blkInfo);
  2937. };
  2938. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, blkInfo[1]], collapsed);
  2939. break;
  2940. case 'drum':
  2941. blkData[4][0] = null;
  2942. blkData[4][2] = null;
  2943. postProcess = function (args) {
  2944. var thisBlock = args[0];
  2945. var blkInfo = args[1];
  2946. that.blockList[thisBlock].value = that.turtles.turtleList.length;
  2947. that.turtles.addDrum(that.blockList[thisBlock], blkInfo);
  2948. };
  2949. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, blkInfo[1]], collapsed);
  2950. if (_THIS_IS_MUSIC_BLOCKS_) {
  2951. // Load the synth for this drum
  2952. this.logo.synth.loadSynth('kick');
  2953. }
  2954. break;
  2955. case 'action':
  2956. case 'hat':
  2957. blkData[4][0] = null;
  2958. blkData[4][3] = null;
  2959. this._makeNewBlockWithConnections('action', blockOffset, blkData[4], null, null, collapsed);
  2960. break;
  2961. // Named boxes and dos need private data.
  2962. case 'namedbox':
  2963. postProcess = function (args) {
  2964. var thisBlock = args[0];
  2965. var value = args[1];
  2966. that.blockList[thisBlock].privateData = value;
  2967. that.blockList[thisBlock].value = null;
  2968. };
  2969. this._makeNewBlockWithConnections('namedbox', blockOffset, blkData[4], postProcess, [thisBlock, value]);
  2970. break;
  2971. case 'namedarg':
  2972. postProcess = function (args) {
  2973. var thisBlock = args[0];
  2974. var value = args[1];
  2975. that.blockList[thisBlock].privateData = value;
  2976. that.blockList[thisBlock].value = null;
  2977. };
  2978. this._makeNewBlockWithConnections('namedarg', blockOffset, blkData[4], postProcess, [thisBlock, value]);
  2979. break;
  2980. case 'namedcalc':
  2981. postProcess = function (args) {
  2982. var thisBlock = args[0];
  2983. var value = args[1];
  2984. that.blockList[thisBlock].privateData = value;
  2985. that.blockList[thisBlock].value = null;
  2986. };
  2987. this._makeNewBlockWithConnections('namedcalc', blockOffset, blkData[4], postProcess, [thisBlock, value]);
  2988. break;
  2989. case 'nameddo':
  2990. postProcess = function (args) {
  2991. var thisBlock = args[0];
  2992. var value = args[1];
  2993. that.blockList[thisBlock].privateData = value;
  2994. that.blockList[thisBlock].value = null;
  2995. };
  2996. this._makeNewBlockWithConnections('nameddo', blockOffset, blkData[4], postProcess, [thisBlock, value]);
  2997. break;
  2998. // Arg clamps may need extra slots added.
  2999. case 'doArg':
  3000. postProcess = function (args) {
  3001. var thisBlock = args[0];
  3002. var extraSlots = args[1].length - 4;
  3003. if (extraSlots > 0) {
  3004. var slotList = that.blockList[thisBlock].argClampSlots;
  3005. for (var i = 0; i < extraSlots; i++) {
  3006. slotList.push(1);
  3007. that._newLocalArgBlock(slotList.length);
  3008. that.blockList[thisBlock].connections.push(null);
  3009. }
  3010. that.blockList[thisBlock].updateArgSlots(slotList);
  3011. for (var i = 0; i < args[1].length; i++) {
  3012. if (args[1][i] != null) {
  3013. that.blockList[thisBlock].connections[i] = args[1][i] + firstBlock;
  3014. } else {
  3015. that.blockList[thisBlock].connections[i] = args[1][i];
  3016. }
  3017. }
  3018. }
  3019. that._checkArgClampBlocks.push(thisBlock);
  3020. };
  3021. this._makeNewBlockWithConnections('doArg', blockOffset, blkData[4], postProcess, [thisBlock, blkData[4]]);
  3022. break;
  3023. case 'nameddoArg':
  3024. postProcess = function (args) {
  3025. var thisBlock = args[0];
  3026. var value = args[1];
  3027. that.blockList[thisBlock].privateData = value;
  3028. that.blockList[thisBlock].value = null;
  3029. var extraSlots = args[2].length - 3;
  3030. if (extraSlots > 0) {
  3031. var slotList = that.blockList[thisBlock].argClampSlots;
  3032. for (var i = 0; i < extraSlots; i++) {
  3033. slotList.push(1);
  3034. that._newLocalArgBlock(slotList.length);
  3035. that.blockList[thisBlock].connections.push(null);
  3036. }
  3037. that.blockList[thisBlock].updateArgSlots(slotList);
  3038. for (var i = 0; i < args[2].length; i++) {
  3039. if (args[2][i] != null) {
  3040. that.blockList[thisBlock].connections[i] = args[2][i] + firstBlock;
  3041. } else {
  3042. that.blockList[thisBlock].connections[i] = args[2][i];
  3043. }
  3044. }
  3045. }
  3046. that._checkArgClampBlocks.push(thisBlock);
  3047. };
  3048. this._makeNewBlockWithConnections('nameddoArg', blockOffset, blkData[4], postProcess, [thisBlock, value, blkData[4]]);
  3049. break;
  3050. case 'calcArg':
  3051. postProcess = function (args) {
  3052. var thisBlock = args[0];
  3053. var extraSlots = args[1].length - 3;
  3054. if (extraSlots > 0) {
  3055. var slotList = that.blockList[thisBlock].argClampSlots;
  3056. for (var i = 0; i < extraSlots; i++) {
  3057. slotList.push(1);
  3058. that._newLocalArgBlock(slotList.length);
  3059. that.blockList[thisBlock].connections.push(null);
  3060. }
  3061. that.blockList[thisBlock].updateArgSlots(slotList);
  3062. for (var i = 0; i < args[1].length; i++) {
  3063. if (args[1][i] != null) {
  3064. that.blockList[thisBlock].connections[i] = args[1][i] + firstBlock;
  3065. } else {
  3066. that.blockList[thisBlock].connections[i] = args[1][i];
  3067. }
  3068. }
  3069. }
  3070. that._checkArgClampBlocks.push(thisBlock);
  3071. };
  3072. this._makeNewBlockWithConnections('calcArg', blockOffset, blkData[4], postProcess, [thisBlock, blkData[4]]);
  3073. break;
  3074. case 'namedcalcArg':
  3075. postProcess = function (args) {
  3076. var thisBlock = args[0];
  3077. var value = args[1];
  3078. that.blockList[thisBlock].privateData = value;
  3079. that.blockList[thisBlock].value = null;
  3080. var extraSlots = args[2].length - 2;
  3081. if (extraSlots > 0) {
  3082. var slotList = that.blockList[thisBlock].argClampSlots;
  3083. for (var i = 0; i < extraSlots; i++) {
  3084. slotList.push(1);
  3085. that._newLocalArgBlock(slotList.length);
  3086. that.blockList[thisBlock].connections.push(null);
  3087. }
  3088. that.blockList[thisBlock].updateArgSlots(slotList);
  3089. for (var i = 0; i < args[2].length; i++) {
  3090. if (args[2][i] != null) {
  3091. that.blockList[thisBlock].connections[i] = args[2][i] + firstBlock;
  3092. } else {
  3093. that.blockList[thisBlock].connections[i] = args[2][i];
  3094. }
  3095. }
  3096. }
  3097. that._checkArgClampBlocks.push(thisBlock);
  3098. };
  3099. this._makeNewBlockWithConnections('namedcalcArg', blockOffset, blkData[4], postProcess, [thisBlock, value, blkData[4]]);
  3100. break;
  3101. // Value blocks need a default value set.
  3102. case 'number':
  3103. postProcess = function (args) {
  3104. var thisBlock = args[0];
  3105. var value = args[1];
  3106. that.blockList[thisBlock].value = Number(value);
  3107. that.updateBlockText(thisBlock);
  3108. };
  3109. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3110. break;
  3111. case 'text':
  3112. postProcess = function (args) {
  3113. var thisBlock = args[0];
  3114. var value = args[1];
  3115. that.blockList[thisBlock].value = value;
  3116. that.updateBlockText(thisBlock);
  3117. };
  3118. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3119. break;
  3120. case 'solfege':
  3121. postProcess = function (args) {
  3122. var thisBlock = args[0];
  3123. var value = args[1];
  3124. that.blockList[thisBlock].value = value;
  3125. that.updateBlockText(thisBlock);
  3126. };
  3127. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3128. break;
  3129. case 'eastindiansolfege':
  3130. postProcess = function (args) {
  3131. var thisBlock = args[0];
  3132. var value = args[1];
  3133. that.blockList[thisBlock].value = value;
  3134. that.updateBlockText(thisBlock);
  3135. };
  3136. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3137. break;
  3138. case 'notename':
  3139. postProcess = function (args) {
  3140. var thisBlock = args[0];
  3141. var value = args[1];
  3142. that.blockList[thisBlock].value = value;
  3143. that.updateBlockText(thisBlock);
  3144. };
  3145. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3146. break;
  3147. case 'modename':
  3148. postProcess = function (args) {
  3149. var thisBlock = args[0];
  3150. var value = args[1];
  3151. that.blockList[thisBlock].value = value;
  3152. that.updateBlockText(thisBlock);
  3153. };
  3154. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3155. break;
  3156. case 'drumname':
  3157. postProcess = function (args) {
  3158. var thisBlock = args[0];
  3159. var value = args[1];
  3160. that.blockList[thisBlock].value = value;
  3161. that.updateBlockText(thisBlock);
  3162. };
  3163. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3164. if (_THIS_IS_MUSIC_BLOCKS_) {
  3165. // Load the synth for this drum
  3166. this.logo.synth.loadSynth(getDrumSynthName(value));
  3167. }
  3168. break;
  3169. case 'voicename':
  3170. postProcess = function (args) {
  3171. var thisBlock = args[0];
  3172. var value = args[1];
  3173. that.blockList[thisBlock].value = value;
  3174. that.updateBlockText(thisBlock);
  3175. };
  3176. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3177. if (_THIS_IS_MUSIC_BLOCKS_) {
  3178. // Load the synth for this voice
  3179. this.logo.synth.loadSynth(getVoiceSynthName(value));
  3180. }
  3181. break;
  3182. case 'media':
  3183. // Load a thumbnail into a media blocks.
  3184. postProcess = function (args) {
  3185. var thisBlock = args[0];
  3186. var value = args[1];
  3187. that.blockList[thisBlock].value = value;
  3188. if (value != null) {
  3189. // Load artwork onto media block.
  3190. that.blockList[thisBlock].loadThumbnail(null);
  3191. }
  3192. };
  3193. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3194. break;
  3195. case 'camera':
  3196. postProcess = function (args) {
  3197. var thisBlock = args[0];
  3198. var value = args[1];
  3199. that.blockList[thisBlock].value = CAMERAVALUE;
  3200. };
  3201. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3202. break;
  3203. case 'video':
  3204. postProcess = function (args) {
  3205. var thisBlock = args[0];
  3206. var value = args[1];
  3207. that.blockList[thisBlock].value = VIDEOVALUE;
  3208. };
  3209. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3210. break;
  3211. // Define some constants for legacy blocks for
  3212. // backward compatibility with Python projects.
  3213. case 'red':
  3214. case 'black':
  3215. postProcess = function (thisBlock) {
  3216. that.blockList[thisBlock].value = 0;
  3217. that.updateBlockText(thisBlock);
  3218. };
  3219. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3220. break;
  3221. case 'white':
  3222. postProcess = function (thisBlock) {
  3223. that.blockList[thisBlock].value = 100;
  3224. that.updateBlockText(thisBlock);
  3225. };
  3226. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3227. break;
  3228. case 'orange':
  3229. postProcess = function (thisBlock) {
  3230. that.blockList[thisBlock].value = 10;
  3231. that.updateBlockText(thisBlock);
  3232. };
  3233. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3234. break;
  3235. case 'yellow':
  3236. postProcess = function (thisBlock) {
  3237. that.blockList[thisBlock].value = 20;
  3238. that.updateBlockText(thisBlock);
  3239. };
  3240. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3241. break;
  3242. case 'green':
  3243. postProcess = function (thisBlock) {
  3244. that.blockList[thisBlock].value = 40;
  3245. that.updateBlockText(thisBlock);
  3246. };
  3247. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3248. break;
  3249. case 'blue':
  3250. postProcess = function (thisBlock) {
  3251. that.blockList[thisBlock].value = 70;
  3252. that.updateBlockText(thisBlock);
  3253. };
  3254. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3255. break;
  3256. case 'leftpos':
  3257. postProcess = function (thisBlock) {
  3258. that.blockList[thisBlock].value = -(canvas.width / 2);
  3259. that.updateBlockText(thisBlock);
  3260. };
  3261. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3262. break;
  3263. case 'rightpos':
  3264. postProcess = function (thisBlock) {
  3265. that.blockList[thisBlock].value = (canvas.width / 2);
  3266. that.updateBlockText(thisBlock);
  3267. };
  3268. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3269. break;
  3270. case 'toppos':
  3271. postProcess = function (thisBlock) {
  3272. that.blockList[thisBlock].value = (canvas.height / 2);
  3273. that.updateBlockText(thisBlock);
  3274. };
  3275. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3276. break;
  3277. case 'botpos':
  3278. case 'bottompos':
  3279. postProcess = function (thisBlock) {
  3280. that.blockList[thisBlock].value = -(canvas.height / 2);
  3281. that.updateBlockText(thisBlock);
  3282. };
  3283. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3284. break;
  3285. case 'width':
  3286. postProcess = function (thisBlock) {
  3287. that.blockList[thisBlock].value = canvas.width;
  3288. that.updateBlockText(thisBlock);
  3289. };
  3290. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3291. break;
  3292. case 'height':
  3293. postProcess = function (thisBlock) {
  3294. that.blockList[thisBlock].value = canvas.height;
  3295. that.updateBlockText(thisBlock);
  3296. };
  3297. this._makeNewBlockWithConnections('number', blockOffset, blkData[4], postProcess, thisBlock);
  3298. break;
  3299. case 'loadFile':
  3300. postProcess = function (args) {
  3301. that.blockList[args[0]].value = args[1];
  3302. that.updateBlockText(args[0]);
  3303. };
  3304. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], postProcess, [thisBlock, value]);
  3305. break;
  3306. default:
  3307. // Check that name is in the proto list
  3308. if (!name in this.protoBlockDict || this.protoBlockDict[name] == null) {
  3309. // Lots of assumptions here.
  3310. // TODO: figure out if it is a flow or an arg block.
  3311. // Substitute a NOP block for an unknown block.
  3312. n = blkData[4].length;
  3313. console.log(n + ': substituting nop block for ' + name);
  3314. switch (n) {
  3315. case 1:
  3316. name = 'nopValueBlock';
  3317. break;
  3318. case 2:
  3319. name = 'nopZeroArgBlock';
  3320. break;
  3321. case 3:
  3322. name = 'nopOneArgBlock';
  3323. break;
  3324. case 4:
  3325. name = 'nopTwoArgBlock';
  3326. break;
  3327. case 5:
  3328. default:
  3329. name = 'nopThreeArgBlock';
  3330. break;
  3331. }
  3332. }
  3333. this._makeNewBlockWithConnections(name, blockOffset, blkData[4], null);
  3334. break;
  3335. }
  3336. if (thisBlock === this.blockList.length - 1) {
  3337. if (this.blockList[thisBlock].connections[0] == null) {
  3338. this.blockList[thisBlock].container.x = blkData[2];
  3339. this.blockList[thisBlock].container.y = blkData[3];
  3340. this._adjustTheseDocks.push(thisBlock);
  3341. if (blkData[4][0] == null) {
  3342. this._adjustTheseStacks.push(thisBlock);
  3343. }
  3344. if (blkData[2] < 0 || blkData[3] < 0 || blkData[2] > canvas.width || blkData[3] > canvas.height) {
  3345. this._homeButtonContainers[0].visible = true;
  3346. this._homeButtonContainers[1].visible = false;
  3347. }
  3348. }
  3349. }
  3350. }
  3351. };
  3352. this.cleanupAfterLoad = function (name) {
  3353. // If all the blocks are loaded, we can make the final adjustments.
  3354. this._loadCounter -= 1;
  3355. if (this._loadCounter > 0) {
  3356. return;
  3357. }
  3358. this._findDrumURLs();
  3359. this.updateBlockPositions();
  3360. this._cleanupStacks();
  3361. for (var i = 0; i < this.blocksToCollapse.length; i++) {
  3362. this.blockList[this.blocksToCollapse[i]].collapseToggle();
  3363. }
  3364. this.blocksToCollapse = [];
  3365. for (var blk = 0; blk < this.blockList.length; blk++) {
  3366. if (this.blockList[blk].collapseContainer != null) {
  3367. this.blockList[blk].collapseContainer.x = this.blockList[blk].container.x + COLLAPSEBUTTONXOFF * (this.blockList[blk].protoblock.scale / 2);
  3368. this.blockList[blk].collapseContainer.y = this.blockList[blk].container.y + COLLAPSEBUTTONYOFF * (this.blockList[blk].protoblock.scale / 2);
  3369. }
  3370. }
  3371. this.refreshCanvas();
  3372. // Do a final check on the action and boxes palettes.
  3373. var updatePalettes = false;
  3374. for (var blk = 0; blk < this.blockList.length; blk++) {
  3375. if (!this.blockList[blk].trash && this.blockList[blk].name === 'action') {
  3376. var myBlock = this.blockList[blk];
  3377. var arg = null;
  3378. var c = myBlock.connections[1];
  3379. if (c != null && this.blockList[c].value !== _('action')) {
  3380. if (this.newNameddoBlock(this.blockList[c].value, this.actionHasReturn(blk), this.actionHasArgs(blk))) {
  3381. updatePalettes = true;
  3382. }
  3383. }
  3384. }
  3385. }
  3386. if (updatePalettes) {
  3387. this.palettes.hide();
  3388. this.palettes.updatePalettes('action');
  3389. // this.palettes.dict['action'].hide();
  3390. this.palettes.show();
  3391. }
  3392. var updatePalettes = false;
  3393. for (var blk = 0; blk < this.blockList.length; blk++) {
  3394. if (!this.blockList[blk].trash && this.blockList[blk].name === 'storein') {
  3395. var myBlock = this.blockList[blk];
  3396. var arg = null;
  3397. var c = myBlock.connections[1];
  3398. if (c != null && this.blockList[c].value !== _('box')) {
  3399. var name = this.blockList[c].value;
  3400. // Is there an old block with this name still around?
  3401. if (this.protoBlockDict['myStorein_' + name] == undefined) {
  3402. console.log('adding new storein block ' + name);
  3403. this.newNamedboxBlock(this.blockList[c].value);
  3404. this.newStoreinBlock(this.blockList[c].value);
  3405. updatePalettes = true;
  3406. }
  3407. }
  3408. }
  3409. }
  3410. if (updatePalettes) {
  3411. // Do this update on a slight delay so as not to collide with
  3412. // the actions update.
  3413. var that = this;
  3414. setTimeout(function () {
  3415. that.palettes.hide();
  3416. that.palettes.updatePalettes('boxes');
  3417. // that.palettes.dict['boxes'].hide();
  3418. that.palettes.show();
  3419. }, 1500);
  3420. }
  3421. console.log("Finished block loading");
  3422. var myCustomEvent = new Event('finishedLoading');
  3423. document.dispatchEvent(myCustomEvent);
  3424. };
  3425. this._cleanupStacks = function () {
  3426. if (this._checkArgClampBlocks.length > 0) {
  3427. // We make multiple passes because we need to account for nesting.
  3428. // FIXME: needs to be interwoven with TwoArgBlocks check.
  3429. for (var i = 0; i < this._checkArgClampBlocks.length; i++) {
  3430. for (var b = 0; b < this._checkArgClampBlocks.length; b++) {
  3431. this._adjustArgClampBlock([this._checkArgClampBlocks[b]]);
  3432. }
  3433. }
  3434. }
  3435. if (this._checkTwoArgBlocks.length > 0) {
  3436. // We make multiple passes because we need to account for nesting.
  3437. for (var i = 0; i < this._checkTwoArgBlocks.length; i++) {
  3438. for (var b = 0; b < this._checkTwoArgBlocks.length; b++) {
  3439. this._adjustExpandableTwoArgBlock([this._checkTwoArgBlocks[b]]);
  3440. }
  3441. }
  3442. }
  3443. for (var blk = 0; blk < this._adjustTheseDocks.length; blk++) {
  3444. // console.log('Adjust Docks: ' + this.blockList[this._adjustTheseDocks[blk]].name);
  3445. this.adjustDocks(this._adjustTheseDocks[blk], true);
  3446. // blockBlocks._expandTwoArgs();
  3447. this._expandClamps();
  3448. }
  3449. for (var blk = 0; blk < this._adjustTheseStacks.length; blk++) {
  3450. // console.log('Adjust Stack: ' + this.blockList[this._adjustTheseStacks[blk]].name);
  3451. this.raiseStackToTop(this._adjustTheseStacks[blk]);
  3452. }
  3453. };
  3454. this.actionHasReturn = function (blk) {
  3455. // Look for a return block in an action stack.
  3456. if (this.blockList[blk].name !== 'action') {
  3457. return false;
  3458. }
  3459. this.findDragGroup(blk);
  3460. for (var b = 0; b < this.dragGroup.length; b++) {
  3461. if (this.blockList[this.dragGroup[b]].name === 'return') {
  3462. return true;
  3463. }
  3464. }
  3465. return false;
  3466. };
  3467. this.actionHasArgs = function (blk) {
  3468. // Look for an arg blocks in an action stack.
  3469. if (this.blockList[blk].name !== 'action') {
  3470. return false;
  3471. }
  3472. this.findDragGroup(blk);
  3473. for (var b = 0; b < this.dragGroup.length; b++) {
  3474. if (this.blockList[this.dragGroup[b]].name === 'arg' || this.blockList[this.dragGroup[b]].name === 'namedarg') {
  3475. return true;
  3476. }
  3477. }
  3478. return false;
  3479. };
  3480. this.raiseStackToTop = function (blk) {
  3481. // Move the stack associated with blk to the top.
  3482. var topBlk = this.findTopBlock(blk);
  3483. this.findDragGroup(topBlk);
  3484. var z = this.stage.getNumChildren() - 1;
  3485. for (var b = 0; b < this.dragGroup.length; b++) {
  3486. this.stage.setChildIndex(this.blockList[this.dragGroup[b]].container, z);
  3487. z -= 1;
  3488. }
  3489. this.refreshCanvas;
  3490. };
  3491. this.deleteActionBlock = function (myBlock) {
  3492. var actionArg = this.blockList[myBlock.connections[1]];
  3493. if (actionArg) {
  3494. var actionName = actionArg.value;
  3495. for (var blk = 0; blk < this.blockList.length; blk++) {
  3496. var myBlock = this.blockList[blk];
  3497. var blkParent = this.blockList[myBlock.connections[0]];
  3498. if (blkParent == null) {
  3499. continue;
  3500. }
  3501. if (['namedcalc', 'calc', 'nameddo', 'do', 'action'].indexOf(blkParent.name) !== -1) {
  3502. continue;
  3503. }
  3504. var blockValue = myBlock.value;
  3505. if (blockValue === _('action')) {
  3506. continue;
  3507. }
  3508. if (blockValue === actionName) {
  3509. blkParent.hide();
  3510. myBlock.hide();
  3511. myBlock.trash = true;
  3512. blkParent.trash = true;
  3513. }
  3514. }
  3515. // Avoid palette refreash race condition.
  3516. this.deleteActionTimeout += 500;
  3517. var timeout = this.deleteActionTimeout;
  3518. var that = this;
  3519. setTimeout(function () {
  3520. that.deleteActionTimeout -= 500;
  3521. that.palettes.removeActionPrototype(actionName);
  3522. }, timeout);
  3523. }
  3524. };
  3525. this.sendStackToTrash = function (myBlock) {
  3526. // First, hide the palettes as they will need updating.
  3527. for (var name in this.palettes.dict) {
  3528. this.palettes.dict[name].hideMenu(true);
  3529. }
  3530. this.refreshCanvas();
  3531. var thisBlock = this.blockList.indexOf(myBlock);
  3532. // Add this block to the list of blocks in the trash so we can
  3533. // undo this action.
  3534. this.trashStacks.push(thisBlock);
  3535. // Disconnect block.
  3536. var parentBlock = myBlock.connections[0];
  3537. if (parentBlock != null) {
  3538. for (var c in this.blockList[parentBlock].connections) {
  3539. if (this.blockList[parentBlock].connections[c] === thisBlock) {
  3540. this.blockList[parentBlock].connections[c] = null;
  3541. break;
  3542. }
  3543. }
  3544. myBlock.connections[0] = null;
  3545. // Add default block if user deletes all blocks from inside the note block
  3546. this.addDefaultBlock(parentBlock, thisBlock);
  3547. }
  3548. if (myBlock.name === 'start' || myBlock.name === 'drum') {
  3549. turtle = myBlock.value;
  3550. var turtleNotInTrash = 0;
  3551. for (var i = 0; i < this.turtles.turtleList.length; i++) {
  3552. if (!this.turtles.turtleList[i].trash) {
  3553. turtleNotInTrash += 1;
  3554. }
  3555. }
  3556. if (turtle != null && turtleNotInTrash > 1) {
  3557. console.log('putting turtle ' + turtle + ' in the trash');
  3558. this.turtles.turtleList[turtle].trash = true;
  3559. this.turtles.turtleList[turtle].container.visible = false;
  3560. } else {
  3561. this.errorMsg("You must always have at least one start block");
  3562. console.log('null turtle');
  3563. return;
  3564. }
  3565. } else if (myBlock.name === 'action') {
  3566. if (!myBlock.trash) {
  3567. this.deleteActionBlock(myBlock);
  3568. }
  3569. }
  3570. // put drag group in trash
  3571. this.findDragGroup(thisBlock);
  3572. for (var b = 0; b < this.dragGroup.length; b++) {
  3573. var blk = this.dragGroup[b];
  3574. // console.log('putting ' + this.blockList[blk].name + ' in the trash');
  3575. this.blockList[blk].trash = true;
  3576. this.blockList[blk].hide();
  3577. this.refreshCanvas();
  3578. }
  3579. // Adjust the stack from which we just deleted blocks.
  3580. if (parentBlock != null) {
  3581. var topBlk = this.findTopBlock(parentBlock);
  3582. this.findDragGroup(topBlk);
  3583. // We need to track two-arg blocks in case they need expanding.
  3584. this._checkTwoArgBlocks = [];
  3585. // And arg clamp blocks in case they need expanding.
  3586. this._checkArgClampBlocks = [];
  3587. for (var b = 0; b < this.dragGroup.length; b++) {
  3588. var blk = this.dragGroup[b];
  3589. var myBlock = this.blockList[blk];
  3590. if (myBlock.isTwoArgBlock()) {
  3591. this._checkTwoArgBlocks.push(blk);
  3592. } else if (myBlock.isArgBlock() && myBlock.isExpandableBlock() || myBlock.isArgClamp()) {
  3593. this._checkTwoArgBlocks.push(blk);
  3594. } else if (['clamp', 'argclamp', 'argclamparg', 'doubleclamp', 'argflowclamp'].indexOf(myBlock.protoblock.style) !== -1) {
  3595. this._checkArgClampBlocks.push(blk);
  3596. }
  3597. }
  3598. this._cleanupStacks();
  3599. this.refreshCanvas();
  3600. }
  3601. };
  3602. return this;
  3603. };