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

2015 lines
78 KiB

  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. //
  12. // Length of a long touch
  13. const LONGPRESSTIME = 1500;
  14. const COLLAPSABLES = ['drum', 'start', 'action', 'matrix', 'pitchdrummatrix', 'rhythmruler', 'status', 'pitchstaircase', 'tempo', 'pitchslider', 'modewidget'];
  15. const NOHIT = ['hidden', 'hiddennoflow'];
  16. const SPECIALINPUTS = ['text', 'number', 'solfege', 'eastindiansolfege', 'notename', 'voicename', 'modename', 'drumname'];
  17. // Define block instance objects and any methods that are intra-block.
  18. function Block(protoblock, blocks, overrideName) {
  19. if (protoblock === null) {
  20. console.log('null protoblock sent to Block');
  21. return;
  22. }
  23. this.protoblock = protoblock;
  24. this.name = protoblock.name;
  25. this.overrideName = overrideName;
  26. this.blocks = blocks;
  27. this.collapsed = false; // Is this block in a collapsed stack?
  28. this.trash = false; // Is this block in the trash?
  29. this.loadComplete = false; // Has the block finished loading?
  30. this.label = null; // Editable textview in DOM.
  31. this.text = null; // A dynamically generated text label on block itself.
  32. this.value = null; // Value for number, text, and media blocks.
  33. this.privateData = null; // A block may have some private data,
  34. // e.g., nameboxes use this field to store
  35. // the box name associated with the block.
  36. this.image = protoblock.image; // The file path of the image.
  37. this.imageBitmap = null;
  38. // All blocks have at a container and least one bitmap.
  39. this.container = null;
  40. this.bounds = null;
  41. this.bitmap = null;
  42. this.highlightBitmap = null;
  43. // The svg from which the bitmaps are generated
  44. this.artwork = null;
  45. this.collapseArtwork = null;
  46. // Start and Action blocks has a collapse button (in a separate
  47. // container).
  48. this.collapseContainer = null;
  49. this.collapseBitmap = null;
  50. this.expandBitmap = null;
  51. this.collapseBlockBitmap = null;
  52. this.highlightCollapseBlockBitmap = null;
  53. this.collapseText = null;
  54. this.size = 1; // Proto size is copied here.
  55. this.docks = []; // Proto dock is copied here.
  56. this.connections = [];
  57. // Keep track of clamp count for blocks with clamps.
  58. this.clampCount = [1, 1];
  59. this.argClampSlots = [1];
  60. // Some blocks have some post process after they are first loaded.
  61. this.postProcess = null;
  62. this.postProcessArg = null;
  63. // Lock on label change
  64. this._label_lock = false;
  65. // Internal function for creating cache.
  66. // Includes workaround for a race condition.
  67. this._createCache = function () {
  68. var myBlock = this;
  69. myBlock.bounds = myBlock.container.getBounds();
  70. if (myBlock.bounds == null) {
  71. setTimeout(function () {
  72. myBlock._createCache();
  73. }, 200);
  74. } else {
  75. myBlock.container.cache(myBlock.bounds.x, myBlock.bounds.y, myBlock.bounds.width, myBlock.bounds.height);
  76. }
  77. };
  78. // Internal function for creating cache.
  79. // Includes workaround for a race condition.
  80. this.updateCache = function () {
  81. var myBlock = this;
  82. if (myBlock.bounds == null) {
  83. setTimeout(function () {
  84. myBlock.updateCache();
  85. }, 300);
  86. } else {
  87. myBlock.container.updateCache();
  88. myBlock.blocks.refreshCanvas();
  89. }
  90. };
  91. this.offScreen = function (boundary) {
  92. return !this.trash && boundary.offScreen(this.container.x, this.container.y);
  93. };
  94. this.copySize = function () {
  95. this.size = this.protoblock.size;
  96. };
  97. this.getInfo = function () {
  98. return this.name + ' block';
  99. };
  100. this.highlight = function () {
  101. if (this.collapsed && COLLAPSABLES.indexOf(this.name) !== -1) {
  102. // We may have a race condition.
  103. if (this.highlightCollapseBlockBitmap) {
  104. this.highlightCollapseBlockBitmap.visible = true;
  105. this.collapseBlockBitmap.visible = false;
  106. this.collapseText.visible = true;
  107. this.bitmap.visible = false;
  108. this.highlightBitmap.visible = false;
  109. }
  110. } else {
  111. this.bitmap.visible = false;
  112. this.highlightBitmap.visible = true;
  113. if (COLLAPSABLES.indexOf(this.name) !== -1) {
  114. // There could be a race condition when making a
  115. // new action block.
  116. if (this.highlightCollapseBlockBitmap) {
  117. if (this.collapseText !== null) {
  118. this.collapseText.visible = false;
  119. }
  120. if (this.collapseBlockBitmap.visible !== null) {
  121. this.collapseBlockBitmap.visible = false;
  122. }
  123. if (this.highlightCollapseBlockBitmap.visible !== null) {
  124. this.highlightCollapseBlockBitmap.visible = false;
  125. }
  126. }
  127. }
  128. }
  129. this.updateCache();
  130. };
  131. this.unhighlight = function () {
  132. if (this.collapsed && COLLAPSABLES.indexOf(this.name) !== -1) {
  133. if (this.highlightCollapseBlockBitmap) {
  134. this.highlightCollapseBlockBitmap.visible = false;
  135. this.collapseBlockBitmap.visible = true;
  136. this.collapseText.visible = true;
  137. this.bitmap.visible = false;
  138. this.highlightBitmap.visible = false;
  139. }
  140. } else {
  141. this.bitmap.visible = true;
  142. this.highlightBitmap.visible = false;
  143. if (COLLAPSABLES.indexOf(this.name) !== -1) {
  144. if (this.highlightCollapseBlockBitmap) {
  145. this.highlightCollapseBlockBitmap.visible = false;
  146. this.collapseBlockBitmap.visible = false;
  147. this.collapseText.visible = false;
  148. }
  149. }
  150. }
  151. this.updateCache();
  152. };
  153. this.updateArgSlots = function (slotList) {
  154. // Resize and update number of slots in argClamp
  155. this.argClampSlots = slotList;
  156. this._newArtwork();
  157. this.regenerateArtwork(false);
  158. };
  159. this.updateSlots = function (clamp, plusMinus) {
  160. // Resize an expandable block.
  161. this.clampCount[clamp] += plusMinus;
  162. this._newArtwork(plusMinus);
  163. this.regenerateArtwork(false);
  164. };
  165. this.resize = function (scale) {
  166. // If the block scale changes, we need to regenerate the
  167. // artwork and recalculate the hitarea.
  168. var myBlock = this;
  169. this.postProcess = function (args) {
  170. if (myBlock.imageBitmap !== null) {
  171. myBlock._positionMedia(myBlock.imageBitmap, myBlock.imageBitmap.image.width, myBlock.imageBitmap.image.height, scale);
  172. z = myBlock.container.getNumChildren() - 1;
  173. myBlock.container.setChildIndex(myBlock.imageBitmap, z);
  174. }
  175. if (myBlock.name === 'start' || myBlock.name === 'drum') {
  176. // Rescale the decoration on the start blocks.
  177. for (var turtle = 0; turtle < myBlock.blocks.turtles.turtleList.length; turtle++) {
  178. if (myBlock.blocks.turtles.turtleList[turtle].startBlock === myBlock) {
  179. myBlock.blocks.turtles.turtleList[turtle].resizeDecoration(scale, myBlock.bitmap.image.width);
  180. myBlock._ensureDecorationOnTop();
  181. break;
  182. }
  183. }
  184. }
  185. myBlock.updateCache();
  186. myBlock._calculateBlockHitArea();
  187. // If it is in the trash, make sure it remains hidden.
  188. if (myBlock.trash) {
  189. myBlock.hide();
  190. }
  191. };
  192. this.postProcessArg = null;
  193. this.protoblock.scale = scale;
  194. this._newArtwork(0);
  195. this.regenerateArtwork(true, []);
  196. if (this.text !== null) {
  197. this._positionText(scale);
  198. }
  199. if (this.collapseContainer !== null) {
  200. this.collapseContainer.uncache();
  201. var postProcess = function (myBlock) {
  202. myBlock.collapseBitmap.scaleX = myBlock.collapseBitmap.scaleY = myBlock.collapseBitmap.scale = scale / 2;
  203. myBlock.expandBitmap.scaleX = myBlock.expandBitmap.scaleY = myBlock.expandBitmap.scale = scale / 2;
  204. var bounds = myBlock.collapseContainer.getBounds();
  205. if (bounds) myBlock.collapseContainer.cache(bounds.x, bounds.y, bounds.width, bounds.height);
  206. myBlock._positionCollapseContainer(myBlock.protoblock.scale);
  207. myBlock._calculateCollapseHitArea();
  208. };
  209. this._generateCollapseArtwork(postProcess);
  210. var fontSize = 10 * scale;
  211. this.collapseText.font = fontSize + 'px Sans';
  212. this._positionCollapseLabel(scale);
  213. }
  214. };
  215. this._newArtwork = function (plusMinus) {
  216. if (COLLAPSABLES.indexOf(this.name) > -1) {
  217. var proto = new ProtoBlock('collapse');
  218. proto.scale = this.protoblock.scale;
  219. proto.extraWidth = 10;
  220. proto.basicBlockCollapsed();
  221. var obj = proto.generator();
  222. this.collapseArtwork = obj[0];
  223. var obj = this.protoblock.generator(this.clampCount[0]);
  224. } else if (this.name === 'ifthenelse') {
  225. var obj = this.protoblock.generator(this.clampCount[0], this.clampCount[1]);
  226. } else if (this.protoblock.style === 'clamp') {
  227. var obj = this.protoblock.generator(this.clampCount[0]);
  228. } else {
  229. switch (this.name) {
  230. case 'equal':
  231. case 'greater':
  232. case 'less':
  233. var obj = this.protoblock.generator(this.clampCount[0]);
  234. break;
  235. case 'calcArg':
  236. case 'doArg':
  237. case 'namedcalcArg':
  238. case 'nameddoArg':
  239. var obj = this.protoblock.generator(this.argClampSlots);
  240. this.size = 2;
  241. for (var i = 0; i < this.argClampSlots.length; i++) {
  242. this.size += this.argClampSlots[i];
  243. }
  244. this.docks = [];
  245. this.docks.push([obj[1][0][0], obj[1][0][1], this.protoblock.dockTypes[0]]);
  246. break;
  247. default:
  248. if (this.isArgBlock()) {
  249. var obj = this.protoblock.generator(this.clampCount[0]);
  250. } else if (this.isTwoArgBlock()) {
  251. var obj = this.protoblock.generator(this.clampCount[0]);
  252. } else {
  253. var obj = this.protoblock.generator();
  254. }
  255. this.size += plusMinus;
  256. break;
  257. }
  258. }
  259. switch (this.name) {
  260. case 'nameddoArg':
  261. for (var i = 1; i < obj[1].length - 1; i++) {
  262. this.docks.push([obj[1][i][0], obj[1][i][1], 'anyin']);
  263. }
  264. this.docks.push([obj[1][2][0], obj[1][2][1], 'in']);
  265. break;
  266. case 'namedcalcArg':
  267. for (var i = 1; i < obj[1].length; i++) {
  268. this.docks.push([obj[1][i][0], obj[1][i][1], 'anyin']);
  269. }
  270. break;
  271. case 'doArg':
  272. this.docks.push([obj[1][1][0], obj[1][1][1], this.protoblock.dockTypes[1]]);
  273. for (var i = 2; i < obj[1].length - 1; i++) {
  274. this.docks.push([obj[1][i][0], obj[1][i][1], 'anyin']);
  275. }
  276. this.docks.push([obj[1][3][0], obj[1][3][1], 'in']);
  277. break;
  278. case 'calcArg':
  279. this.docks.push([obj[1][1][0], obj[1][1][1], this.protoblock.dockTypes[1]]);
  280. for (var i = 2; i < obj[1].length; i++) {
  281. this.docks.push([obj[1][i][0], obj[1][i][1], 'anyin']);
  282. }
  283. break;
  284. default:
  285. break;
  286. }
  287. // Save new artwork and dock positions.
  288. this.artwork = obj[0];
  289. for (var i = 0; i < this.docks.length; i++) {
  290. this.docks[i][0] = obj[1][i][0];
  291. this.docks[i][1] = obj[1][i][1];
  292. }
  293. };
  294. this.imageLoad = function () {
  295. // Load any artwork associated with the block and create any
  296. // extra parts. Image components are loaded asynchronously so
  297. // most the work happens in callbacks.
  298. // We need a text label for some blocks. For number and text
  299. // blocks, this is the primary label; for parameter blocks,
  300. // this is used to display the current block value.
  301. var fontSize = 10 * this.protoblock.scale;
  302. this.text = new createjs.Text('', fontSize + 'px Sans', '#000000');
  303. this.generateArtwork(true, []);
  304. };
  305. this._addImage = function () {
  306. var image = new Image();
  307. var myBlock = this;
  308. image.onload = function () {
  309. var bitmap = new createjs.Bitmap(image);
  310. bitmap.name = 'media';
  311. myBlock.container.addChild(bitmap);
  312. myBlock._positionMedia(bitmap, image.width, image.height, myBlock.protoblock.scale);
  313. myBlock.imageBitmap = bitmap;
  314. myBlock.updateCache();
  315. };
  316. image.src = this.image;
  317. };
  318. this.regenerateArtwork = function (collapse) {
  319. // Sometimes (in the case of namedboxes and nameddos) we need
  320. // to regenerate the artwork associated with a block.
  321. // First we need to remove the old artwork.
  322. if (this.bitmap != null) {
  323. this.container.removeChild(this.bitmap);
  324. }
  325. if (this.highlightBitmap != null) {
  326. this.container.removeChild(this.highlightBitmap);
  327. }
  328. if (collapse && this.collapseBitmap !== null) {
  329. this.collapseContainer.removeChild(this.collapseBitmap);
  330. this.collapseContainer.removeChild(this.expandBitmap);
  331. this.container.removeChild(this.collapseBlockBitmap);
  332. this.container.removeChild(this.highlightCollapseBlockBitmap);
  333. }
  334. // Then we generate new artwork.
  335. this.generateArtwork(false);
  336. };
  337. this.generateArtwork = function (firstTime) {
  338. // Get the block labels from the protoblock.
  339. var myBlock = this;
  340. var thisBlock = this.blocks.blockList.indexOf(this);
  341. var block_label = '';
  342. // Create the highlight bitmap for the block.
  343. function __processHighlightBitmap(name, bitmap, myBlock) {
  344. if (myBlock.highlightBitmap != null) {
  345. myBlock.container.removeChild(myBlock.highlightBitmap);
  346. }
  347. myBlock.highlightBitmap = bitmap;
  348. myBlock.container.addChild(myBlock.highlightBitmap);
  349. myBlock.highlightBitmap.x = 0;
  350. myBlock.highlightBitmap.y = 0;
  351. myBlock.highlightBitmap.name = 'bmp_highlight_' + thisBlock;
  352. myBlock.highlightBitmap.cursor = 'pointer';
  353. // Hide highlight bitmap to start.
  354. myBlock.highlightBitmap.visible = false;
  355. // At me point, it should be safe to calculate the
  356. // bounds of the container and cache its contents.
  357. if (!firstTime) {
  358. myBlock.container.uncache();
  359. }
  360. myBlock._createCache();
  361. myBlock.blocks.refreshCanvas();
  362. if (firstTime) {
  363. myBlock._loadEventHandlers();
  364. if (myBlock.image !== null) {
  365. myBlock._addImage();
  366. }
  367. myBlock._finishImageLoad();
  368. } else {
  369. if (myBlock.name === 'start' || myBlock.name === 'drum') {
  370. myBlock._ensureDecorationOnTop();
  371. }
  372. // Adjust the docks.
  373. myBlock.blocks.adjustDocks(thisBlock, true);
  374. // Adjust the text position.
  375. myBlock._positionText(myBlock.protoblock.scale);
  376. if (COLLAPSABLES.indexOf(myBlock.name) !== -1) {
  377. myBlock.bitmap.visible = !myBlock.collapsed;
  378. myBlock.highlightBitmap.visible = false;
  379. myBlock.updateCache();
  380. }
  381. if (myBlock.postProcess != null) {
  382. myBlock.postProcess(myBlock.postProcessArg);
  383. myBlock.postProcess = null;
  384. }
  385. }
  386. };
  387. // Create the bitmap for the block.
  388. function __processBitmap(name, bitmap, myBlock) {
  389. if (myBlock.bitmap != null) {
  390. myBlock.container.removeChild(myBlock.bitmap);
  391. }
  392. myBlock.bitmap = bitmap;
  393. myBlock.container.addChild(myBlock.bitmap);
  394. myBlock.bitmap.x = 0;
  395. myBlock.bitmap.y = 0;
  396. myBlock.bitmap.name = 'bmp_' + thisBlock;
  397. myBlock.bitmap.cursor = 'pointer';
  398. myBlock.blocks.refreshCanvas();
  399. if (myBlock.protoblock.disabled) {
  400. var artwork = myBlock.artwork.replace(/fill_color/g, DISABLEDFILLCOLOR).replace(/stroke_color/g, DISABLEDSTROKECOLOR).replace('block_label', block_label);
  401. } else {
  402. var artwork = myBlock.artwork.replace(/fill_color/g, PALETTEHIGHLIGHTCOLORS[myBlock.protoblock.palette.name]).replace(/stroke_color/g, HIGHLIGHTSTROKECOLORS[myBlock.protoblock.palette.name]).replace('block_label', block_label);
  403. }
  404. for (var i = 1; i < myBlock.protoblock.staticLabels.length; i++) {
  405. artwork = artwork.replace('arg_label_' + i, myBlock.protoblock.staticLabels[i]);
  406. }
  407. _makeBitmap(artwork, myBlock.name, __processHighlightBitmap, myBlock);
  408. };
  409. if (this.overrideName) {
  410. if (['nameddo', 'nameddoArg', 'namedcalc', 'namedcalcArg'].indexOf(this.name) !== -1) {
  411. block_label = this.overrideName;
  412. if (block_label.length > 8) {
  413. block_label = block_label.substr(0, 7) + '...';
  414. }
  415. } else {
  416. block_label = this.overrideName;
  417. }
  418. } else if (this.protoblock.staticLabels.length > 0 && !this.protoblock.image) {
  419. // Label should be defined inside _().
  420. block_label = this.protoblock.staticLabels[0];
  421. }
  422. while (this.protoblock.staticLabels.length < this.protoblock.args + 1) {
  423. this.protoblock.staticLabels.push('');
  424. }
  425. if (firstTime) {
  426. // Create artwork and dock.
  427. var obj = this.protoblock.generator();
  428. this.artwork = obj[0];
  429. for (var i = 0; i < obj[1].length; i++) {
  430. this.docks.push([obj[1][i][0], obj[1][i][1], this.protoblock.dockTypes[i]]);
  431. }
  432. }
  433. if (this.protoblock.disabled) {
  434. var artwork = this.artwork.replace(/fill_color/g, DISABLEDFILLCOLOR).replace(/stroke_color/g, DISABLEDSTROKECOLOR).replace('block_label', block_label);
  435. } else {
  436. var artwork = this.artwork.replace(/fill_color/g, PALETTEFILLCOLORS[this.protoblock.palette.name]).replace(/stroke_color/g, PALETTESTROKECOLORS[this.protoblock.palette.name]).replace('block_label', block_label);
  437. }
  438. for (var i = 1; i < this.protoblock.staticLabels.length; i++) {
  439. artwork = artwork.replace('arg_label_' + i, this.protoblock.staticLabels[i]);
  440. }
  441. _makeBitmap(artwork, this.name, __processBitmap, this);
  442. };
  443. this._finishImageLoad = function () {
  444. var thisBlock = this.blocks.blockList.indexOf(this);
  445. // Value blocks get a modifiable text label.
  446. if (SPECIALINPUTS.indexOf(this.name) !== -1) {
  447. if (this.value == null) {
  448. switch(this.name) {
  449. case 'text':
  450. this.value = '---';
  451. break;
  452. case 'solfege':
  453. case 'eastindiansolfege':
  454. this.value = 'sol';
  455. break;
  456. case 'notename':
  457. this.value = 'G';
  458. break;
  459. case 'rest':
  460. this.value = _('rest');
  461. break;
  462. case 'number':
  463. this.value = NUMBERBLOCKDEFAULT;
  464. break;
  465. case 'modename':
  466. this.value = getModeName(DEFAULTMODE);
  467. break;
  468. case 'voicename':
  469. this.value = DEFAULTVOICE;
  470. break;
  471. case 'drumname':
  472. this.value = getDrumName(DEFAULTDRUM);
  473. break;
  474. }
  475. }
  476. if (this.name === 'solfege') {
  477. var obj = splitSolfege(this.value);
  478. var label = i18nSolfege(obj[0]);
  479. var attr = obj[1];
  480. if (attr !== '♮') {
  481. label += attr;
  482. }
  483. } else if (this.name === 'eastindiansolfege') {
  484. var obj = splitSolfege(this.value);
  485. var label = WESTERN2EISOLFEGENAMES[obj[0]];
  486. var attr = obj[1];
  487. if (attr !== '♮') {
  488. label += attr;
  489. }
  490. } else {
  491. var label = this.value.toString();
  492. }
  493. if (label.length > 8) {
  494. label = label.substr(0, 7) + '...';
  495. }
  496. this.text.text = label;
  497. this.container.addChild(this.text);
  498. this._positionText(this.protoblock.scale);
  499. } else if (this.protoblock.parameter) {
  500. // Parameter blocks get a text label to show their current value.
  501. this.container.addChild(this.text);
  502. this._positionText(this.protoblock.scale);
  503. }
  504. if (COLLAPSABLES.indexOf(this.name) === -1) {
  505. this.loadComplete = true;
  506. if (this.postProcess !== null) {
  507. this.postProcess(this.postProcessArg);
  508. this.postProcess = null;
  509. }
  510. this.blocks.refreshCanvas();
  511. this.blocks.cleanupAfterLoad(this.name);
  512. } else {
  513. // Start blocks and Action blocks can collapse, so add an
  514. // event handler.
  515. var proto = new ProtoBlock('collapse');
  516. proto.scale = this.protoblock.scale;
  517. proto.extraWidth = 10;
  518. proto.basicBlockCollapsed();
  519. var obj = proto.generator();
  520. this.collapseArtwork = obj[0];
  521. var postProcess = function (myBlock) {
  522. myBlock._loadCollapsibleEventHandlers();
  523. myBlock.loadComplete = true;
  524. if (myBlock.postProcess !== null) {
  525. myBlock.postProcess(myBlock.postProcessArg);
  526. myBlock.postProcess = null;
  527. }
  528. };
  529. this._generateCollapseArtwork(postProcess);
  530. }
  531. };
  532. this._generateCollapseArtwork = function (postProcess) {
  533. var myBlock = this;
  534. var thisBlock = this.blocks.blockList.indexOf(this);
  535. function __processHighlightCollapseBitmap(name, bitmap, myBlock) {
  536. myBlock.highlightCollapseBlockBitmap = bitmap;
  537. myBlock.highlightCollapseBlockBitmap.name = 'highlight_collapse_' + thisBlock;
  538. myBlock.container.addChild(myBlock.highlightCollapseBlockBitmap);
  539. myBlock.highlightCollapseBlockBitmap.visible = false;
  540. if (myBlock.collapseText === null) {
  541. var fontSize = 10 * myBlock.protoblock.scale;
  542. switch (myBlock.name) {
  543. case 'action':
  544. myBlock.collapseText = new createjs.Text(_('action'), fontSize + 'px Sans', '#000000');
  545. break;
  546. case 'start':
  547. myBlock.collapseText = new createjs.Text(_('start'), fontSize + 'px Sans', '#000000');
  548. break;
  549. case 'matrix':
  550. myBlock.collapseText = new createjs.Text(_('matrix'), fontSize + 'px Sans', '#000000');
  551. break;
  552. case 'status':
  553. myBlock.collapseText = new createjs.Text(_('status'), fontSize + 'px Sans', '#000000');
  554. break;
  555. case 'pitchdrummatrix':
  556. myBlock.collapseText = new createjs.Text(_('drum'), fontSize + 'px Sans', '#000000');
  557. break;
  558. case 'rhythmruler':
  559. myBlock.collapseText = new createjs.Text(_('ruler'), fontSize + 'px Sans', '#000000');
  560. break;
  561. case 'pitchstaircase':
  562. myBlock.collapseText = new createjs.Text(_('stair'), fontSize + 'px Sans', '#000000');
  563. break;
  564. case 'tempo':
  565. myBlock.collapseText = new createjs.Text(_('tempo'), fontSize + 'px Sans', '#000000');
  566. case 'modewidget':
  567. myBlock.collapseText = new createjs.Text(_('mode'), fontSize + 'px Sans', '#000000');
  568. break;
  569. case 'pitchslider':
  570. myBlock.collapseText = new createjs.Text(_('slider'), fontSize + 'px Sans', '#000000');
  571. break;
  572. case 'drum':
  573. myBlock.collapseText = new createjs.Text(_('drum'), fontSize + 'px Sans', '#000000');
  574. break;
  575. }
  576. myBlock.collapseText.textAlign = 'left';
  577. myBlock.collapseText.textBaseline = 'alphabetic';
  578. myBlock.container.addChild(myBlock.collapseText);
  579. }
  580. myBlock._positionCollapseLabel(myBlock.protoblock.scale);
  581. myBlock.collapseText.visible = myBlock.collapsed;
  582. myBlock._ensureDecorationOnTop();
  583. myBlock.updateCache();
  584. myBlock.collapseContainer = new createjs.Container();
  585. myBlock.collapseContainer.snapToPixelEnabled = true;
  586. var image = new Image();
  587. image.onload = function () {
  588. myBlock.collapseBitmap = new createjs.Bitmap(image);
  589. myBlock.collapseBitmap.scaleX = myBlock.collapseBitmap.scaleY = myBlock.collapseBitmap.scale = myBlock.protoblock.scale / 2;
  590. myBlock.collapseContainer.addChild(myBlock.collapseBitmap);
  591. myBlock.collapseBitmap.visible = !myBlock.collapsed;
  592. finishCollapseButton(myBlock);
  593. };
  594. image.src = 'images/collapse.svg';
  595. finishCollapseButton = function (myBlock) {
  596. var image = new Image();
  597. image.onload = function () {
  598. myBlock.expandBitmap = new createjs.Bitmap(image);
  599. myBlock.expandBitmap.scaleX = myBlock.expandBitmap.scaleY = myBlock.expandBitmap.scale = myBlock.protoblock.scale / 2;
  600. myBlock.collapseContainer.addChild(myBlock.expandBitmap);
  601. myBlock.expandBitmap.visible = myBlock.collapsed;
  602. var bounds = myBlock.collapseContainer.getBounds();
  603. if (bounds) myBlock.collapseContainer.cache(bounds.x, bounds.y, bounds.width, bounds.height);
  604. myBlock.blocks.stage.addChild(myBlock.collapseContainer);
  605. if (postProcess !== null) {
  606. postProcess(myBlock);
  607. }
  608. myBlock.blocks.refreshCanvas();
  609. myBlock.blocks.cleanupAfterLoad(myBlock.name);
  610. };
  611. image.src = 'images/expand.svg';
  612. }
  613. };
  614. function __processCollapseBitmap(name, bitmap, myBlock) {
  615. myBlock.collapseBlockBitmap = bitmap;
  616. myBlock.collapseBlockBitmap.name = 'collapse_' + thisBlock;
  617. myBlock.container.addChild(myBlock.collapseBlockBitmap);
  618. myBlock.collapseBlockBitmap.visible = myBlock.collapsed;
  619. myBlock.blocks.refreshCanvas();
  620. var artwork = myBlock.collapseArtwork;
  621. _makeBitmap(artwork.replace(/fill_color/g, PALETTEHIGHLIGHTCOLORS[myBlock.protoblock.palette.name]).replace(/stroke_color/g, HIGHLIGHTSTROKECOLORS[myBlock.protoblock.palette.name]).replace('block_label', ''), '', __processHighlightCollapseBitmap, myBlock);
  622. };
  623. var artwork = this.collapseArtwork;
  624. _makeBitmap(artwork.replace(/fill_color/g, PALETTEFILLCOLORS[this.protoblock.palette.name]).replace(/stroke_color/g, PALETTESTROKECOLORS[this.protoblock.palette.name]).replace('block_label', ''), '', __processCollapseBitmap, this);
  625. };
  626. this.hide = function () {
  627. this.container.visible = false;
  628. if (this.collapseContainer !== null) {
  629. this.collapseContainer.visible = false;
  630. this.collapseText.visible = false;
  631. }
  632. };
  633. this.show = function () {
  634. if (!this.trash) {
  635. // If it is an action block or it is not collapsed then show it.
  636. if (!(COLLAPSABLES.indexOf(this.name) === -1 && this.collapsed)) {
  637. this.container.visible = true;
  638. if (this.collapseContainer !== null) {
  639. this.collapseContainer.visible = true;
  640. this.collapseText.visible = true;
  641. }
  642. }
  643. }
  644. };
  645. // Utility functions
  646. this.isValueBlock = function () {
  647. return this.protoblock.style === 'value';
  648. };
  649. this.isNoHitBlock = function () {
  650. return NOHIT.indexOf(this.name) !== -1;
  651. };
  652. this.isArgBlock = function () {
  653. return this.protoblock.style === 'value' || this.protoblock.style === 'arg';
  654. };
  655. this.isTwoArgBlock = function () {
  656. return this.protoblock.style === 'twoarg';
  657. };
  658. this.isTwoArgBooleanBlock = function () {
  659. return ['equal', 'greater', 'less'].indexOf(this.name) !== -1;
  660. };
  661. this.isClampBlock = function () {
  662. return this.protoblock.style === 'clamp' || this.isDoubleClampBlock() || this.isArgFlowClampBlock();
  663. };
  664. this.isArgFlowClampBlock = function () {
  665. return this.protoblock.style === 'argflowclamp';
  666. };
  667. this.isDoubleClampBlock = function () {
  668. return this.protoblock.style === 'doubleclamp';
  669. };
  670. this.isNoRunBlock = function () {
  671. return this.name === 'action';
  672. };
  673. this.isArgClamp = function () {
  674. return this.protoblock.style === 'argclamp' || this.protoblock.style === 'argclamparg';
  675. };
  676. this.isExpandableBlock = function () {
  677. return this.protoblock.expandable;
  678. };
  679. this.getBlockId = function () {
  680. // Generate a UID based on the block index into the blockList.
  681. var number = blockBlocks.blockList.indexOf(this);
  682. return '_' + number.toString();
  683. };
  684. this.removeChildBitmap = function (name) {
  685. for (var child = 0; child < this.container.getNumChildren(); child++) {
  686. if (this.container.children[child].name === name) {
  687. this.container.removeChild(this.container.children[child]);
  688. break;
  689. }
  690. }
  691. };
  692. this.loadThumbnail = function (imagePath) {
  693. // Load an image thumbnail onto block.
  694. var thisBlock = this.blocks.blockList.indexOf(this);
  695. var myBlock = this;
  696. if (this.blocks.blockList[thisBlock].value === null && imagePath === null) {
  697. return;
  698. }
  699. var image = new Image();
  700. image.onload = function () {
  701. // Before adding new artwork, remove any old artwork.
  702. myBlock.removeChildBitmap('media');
  703. var bitmap = new createjs.Bitmap(image);
  704. bitmap.name = 'media';
  705. var myContainer = new createjs.Container();
  706. myContainer.addChild(bitmap);
  707. // Resize the image to a reasonable maximum.
  708. var MAXWIDTH = 600;
  709. var MAXHEIGHT = 450;
  710. if (image.width > image.height) {
  711. if (image.width > MAXWIDTH) {
  712. bitmap.scaleX = bitmap.scaleY = bitmap.scale = MAXWIDTH / image.width;
  713. }
  714. } else {
  715. if (image.height > MAXHEIGHT) {
  716. bitmap.scaleX = bitmap.scaleY = bitmap.scale = MAXHEIGHT / image.height;
  717. }
  718. }
  719. var bounds = myContainer.getBounds();
  720. myContainer.cache(bounds.x, bounds.y, bounds.width, bounds.height);
  721. myBlock.value = myContainer.getCacheDataURL();
  722. myBlock.imageBitmap = bitmap;
  723. // Next, scale the bitmap for the thumbnail.
  724. myBlock._positionMedia(bitmap, bitmap.image.width, bitmap.image.height, myBlock.protoblock.scale);
  725. myBlock.container.addChild(bitmap);
  726. myBlock.updateCache();
  727. };
  728. if (imagePath === null) {
  729. image.src = this.value;
  730. } else {
  731. image.src = imagePath;
  732. }
  733. };
  734. this._doOpenMedia = function (thisBlock) {
  735. var fileChooser = docById('myOpenAll');
  736. var myBlock = this;
  737. readerAction = function (event) {
  738. window.scroll(0, 0);
  739. var reader = new FileReader();
  740. reader.onloadend = (function () {
  741. if (reader.result) {
  742. if (myBlock.name === 'media') {
  743. myBlock.value = reader.result;
  744. myBlock.loadThumbnail(null);
  745. return;
  746. }
  747. myBlock.value = [fileChooser.files[0].name, reader.result];
  748. myBlock.blocks.updateBlockText(thisBlock);
  749. }
  750. });
  751. if (myBlock.name === 'media') {
  752. reader.readAsDataURL(fileChooser.files[0]);
  753. }
  754. else {
  755. reader.readAsText(fileChooser.files[0]);
  756. }
  757. fileChooser.removeEventListener('change', readerAction);
  758. };
  759. fileChooser.addEventListener('change', readerAction, false);
  760. fileChooser.focus();
  761. fileChooser.click();
  762. window.scroll(0, 0)
  763. };
  764. this.collapseToggle = function () {
  765. // Find the blocks to collapse/expand
  766. var myBlock = this;
  767. var thisBlock = this.blocks.blockList.indexOf(this);
  768. this.blocks.findDragGroup(thisBlock);
  769. function __toggle() {
  770. var collapse = myBlock.collapsed;
  771. if (myBlock.collapseBitmap === null) {
  772. console.log('collapse bitmap not ready');
  773. return;
  774. }
  775. myBlock.collapsed = !collapse;
  776. // These are the buttons to collapse/expand the stack.
  777. myBlock.collapseBitmap.visible = collapse;
  778. myBlock.expandBitmap.visible = !collapse;
  779. // These are the collpase-state bitmaps.
  780. myBlock.collapseBlockBitmap.visible = !collapse;
  781. myBlock.highlightCollapseBlockBitmap.visible = false;
  782. myBlock.collapseText.visible = !collapse;
  783. if (collapse) {
  784. myBlock.bitmap.visible = true;
  785. } else {
  786. myBlock.bitmap.visible = false;
  787. myBlock.updateCache();
  788. }
  789. myBlock.highlightBitmap.visible = false;
  790. if (myBlock.name === 'action') {
  791. // Label the collapsed block with the action label
  792. if (myBlock.connections[1] !== null) {
  793. var text = myBlock.blocks.blockList[myBlock.connections[1]].value;
  794. if (text.length > 8) {
  795. text = text.substr(0, 7) + '...';
  796. }
  797. myBlock.collapseText.text = text;
  798. } else {
  799. myBlock.collapseText.text = '';
  800. }
  801. }
  802. // Make sure the text is on top.
  803. var z = myBlock.container.getNumChildren() - 1;
  804. myBlock.container.setChildIndex(myBlock.collapseText, z);
  805. // Set collapsed state of blocks in drag group.
  806. if (myBlock.blocks.dragGroup.length > 0) {
  807. for (var b = 1; b < myBlock.blocks.dragGroup.length; b++) {
  808. var blk = myBlock.blocks.dragGroup[b];
  809. myBlock.blocks.blockList[blk].collapsed = !collapse;
  810. myBlock.blocks.blockList[blk].container.visible = collapse;
  811. }
  812. }
  813. myBlock.collapseContainer.updateCache();
  814. myBlock.updateCache();
  815. }
  816. __toggle();
  817. };
  818. this._positionText = function (blockScale) {
  819. this.text.textBaseline = 'alphabetic';
  820. this.text.textAlign = 'right';
  821. var fontSize = 10 * blockScale;
  822. this.text.font = fontSize + 'px Sans';
  823. this.text.x = TEXTX * blockScale / 2.;
  824. this.text.y = TEXTY * blockScale / 2.;
  825. // Some special cases
  826. if (SPECIALINPUTS.indexOf(this.name) !== -1) {
  827. this.text.textAlign = 'center';
  828. this.text.x = VALUETEXTX * blockScale / 2.;
  829. } else if (this.protoblock.args === 0) {
  830. var bounds = this.container.getBounds();
  831. this.text.x = bounds.width - 25;
  832. } else {
  833. this.text.textAlign = 'left';
  834. if (this.docks[0][2] === 'booleanout') {
  835. this.text.y = this.docks[0][1];
  836. }
  837. }
  838. // Ensure text is on top.
  839. z = this.container.getNumChildren() - 1;
  840. this.container.setChildIndex(this.text, z);
  841. this.updateCache();
  842. };
  843. this._positionMedia = function (bitmap, width, height, blockScale) {
  844. if (width > height) {
  845. bitmap.scaleX = bitmap.scaleY = bitmap.scale = MEDIASAFEAREA[2] / width * blockScale / 2;
  846. } else {
  847. bitmap.scaleX = bitmap.scaleY = bitmap.scale = MEDIASAFEAREA[3] / height * blockScale / 2;
  848. }
  849. bitmap.x = (MEDIASAFEAREA[0] - 10) * blockScale / 2;
  850. bitmap.y = MEDIASAFEAREA[1] * blockScale / 2;
  851. };
  852. this._calculateCollapseHitArea = function () {
  853. var bounds = this.collapseContainer.getBounds();
  854. var hitArea = new createjs.Shape();
  855. var w2 = bounds.width;
  856. var h2 = bounds.height;
  857. hitArea.graphics.beginFill('#FFF').drawEllipse(-w2 / 2, -h2 / 2, w2, h2);
  858. hitArea.x = w2 / 2;
  859. hitArea.y = h2 / 2;
  860. this.collapseContainer.hitArea = hitArea;
  861. };
  862. this._positionCollapseLabel = function (blockScale) {
  863. this.collapseText.x = COLLAPSETEXTX * blockScale / 2;
  864. this.collapseText.y = COLLAPSETEXTY * blockScale / 2;
  865. // Ensure text is on top.
  866. z = this.container.getNumChildren() - 1;
  867. this.container.setChildIndex(this.collapseText, z);
  868. };
  869. this._positionCollapseContainer = function (blockScale) {
  870. this.collapseContainer.x = this.container.x + (COLLAPSEBUTTONXOFF * blockScale / 2);
  871. this.collapseContainer.y = this.container.y + (COLLAPSEBUTTONYOFF * blockScale / 2);
  872. };
  873. // These are the event handlers for collapsible blocks.
  874. this._loadCollapsibleEventHandlers = function () {
  875. var myBlock = this;
  876. var thisBlock = this.blocks.blockList.indexOf(this);
  877. this._calculateCollapseHitArea();
  878. this.collapseContainer.on('mouseover', function (event) {
  879. myBlock.blocks.highlight(thisBlock, true);
  880. myBlock.blocks.activeBlock = thisBlock;
  881. myBlock.blocks.refreshCanvas();
  882. });
  883. var moved = false;
  884. var locked = false;
  885. var mousedown = false;
  886. var offset = {x:0, y:0};
  887. function handleClick () {
  888. if (locked) {
  889. return;
  890. }
  891. locked = true;
  892. setTimeout(function () {
  893. locked = false;
  894. }, 500);
  895. hideDOMLabel();
  896. if (!moved) {
  897. myBlock.collapseToggle();
  898. }
  899. }
  900. this.collapseContainer.on('click', function (event) {
  901. handleClick();
  902. });
  903. this.collapseContainer.on('mousedown', function (event) {
  904. hideDOMLabel();
  905. // Always show the trash when there is a block selected.
  906. trashcan.show();
  907. moved = false;
  908. mousedown = true;
  909. var d = new Date();
  910. blocks.mouseDownTime = d.getTime();
  911. offset = {
  912. x: myBlock.collapseContainer.x - Math.round(event.stageX / blocks.blockScale),
  913. y: myBlock.collapseContainer.y - Math.round(event.stageY / blocks.blockScale)
  914. };
  915. });
  916. this.collapseContainer.on('pressup', function (event) {
  917. if (!mousedown) {
  918. return;
  919. }
  920. mousedown = false;
  921. if (moved) {
  922. myBlock._collapseOut(blocks, thisBlock, moved, event);
  923. moved = false;
  924. } else {
  925. var d = new Date();
  926. if ((d.getTime() - blocks.mouseDownTime) > 1000) {
  927. var d = new Date();
  928. blocks.mouseDownTime = d.getTime();
  929. handleClick();
  930. }
  931. }
  932. });
  933. this.collapseContainer.on('mouseout', function (event) {
  934. if (!mousedown) {
  935. return;
  936. }
  937. mousedown = false;
  938. if (moved) {
  939. myBlock._collapseOut(blocks, thisBlock, moved, event);
  940. moved = false;
  941. } else {
  942. // Maybe restrict to Android?
  943. var d = new Date();
  944. if ((d.getTime() - blocks.mouseDownTime) < 200) {
  945. var d = new Date();
  946. blocks.mouseDownTime = d.getTime();
  947. handleClick();
  948. }
  949. }
  950. });
  951. this.collapseContainer.on('pressmove', function (event) {
  952. if (!mousedown) {
  953. return;
  954. }
  955. moved = true;
  956. var oldX = myBlock.collapseContainer.x;
  957. var oldY = myBlock.collapseContainer.y;
  958. myBlock.collapseContainer.x = Math.round(event.stageX / blocks.blockScale) + offset.x;
  959. myBlock.collapseContainer.y = Math.round(event.stageY / blocks.blockScale) + offset.y;
  960. var dx = myBlock.collapseContainer.x - oldX;
  961. var dy = myBlock.collapseContainer.y - oldY;
  962. myBlock.container.x += dx;
  963. myBlock.container.y += dy;
  964. // If we are over the trash, warn the user.
  965. if (trashcan.overTrashcan(event.stageX / blocks.blockScale, event.stageY / blocks.blockScale)) {
  966. trashcan.startHighlightAnimation();
  967. } else {
  968. trashcan.stopHighlightAnimation();
  969. }
  970. myBlock.blocks.findDragGroup(thisBlock)
  971. if (myBlock.blocks.dragGroup.length > 0) {
  972. for (var b = 0; b < myBlock.blocks.dragGroup.length; b++) {
  973. var blk = myBlock.blocks.dragGroup[b];
  974. if (b !== 0) {
  975. myBlock.blocks.moveBlockRelative(blk, dx, dy);
  976. }
  977. }
  978. }
  979. myBlock.blocks.refreshCanvas();
  980. });
  981. };
  982. this._collapseOut = function (blocks, thisBlock, moved, event) {
  983. // Always hide the trash when there is no block selected.
  984. trashcan.hide();
  985. blocks.unhighlight(thisBlock);
  986. if (moved) {
  987. // Check if block is in the trash.
  988. if (trashcan.overTrashcan(event.stageX / blocks.blockScale, event.stageY / blocks.blockScale)) {
  989. if (trashcan.isVisible)
  990. blocks.sendStackToTrash(this);
  991. } else {
  992. // Otherwise, process move.
  993. blocks.blockMoved(thisBlock);
  994. }
  995. }
  996. if (blocks.activeBlock !== myBlock) {
  997. return;
  998. }
  999. blocks.unhighlight(null);
  1000. blocks.activeBlock = null;
  1001. blocks.refreshCanvas();
  1002. };
  1003. this._calculateBlockHitArea = function () {
  1004. var hitArea = new createjs.Shape();
  1005. var bounds = this.container.getBounds()
  1006. if (bounds === null) {
  1007. this._createCache();
  1008. bounds = this.bounds;
  1009. }
  1010. // Since hitarea is concave, we only detect hits on top
  1011. // section of block. Otherwise we would not be able to grab
  1012. // blocks placed inside of clamps.
  1013. if (this.isClampBlock() || this.isArgClamp()) {
  1014. hitArea.graphics.beginFill('#FFF').drawRect(0, 0, bounds.width, STANDARDBLOCKHEIGHT);
  1015. } else if (this.isNoHitBlock()) {
  1016. // No hit area
  1017. hitArea.graphics.beginFill('#FFF').drawRect(0, 0, 0, 0);
  1018. } else {
  1019. // Shrinking the height makes it easier to grab blocks below
  1020. // in the stack.
  1021. hitArea.graphics.beginFill('#FFF').drawRect(0, 0, bounds.width, bounds.height * 0.75);
  1022. }
  1023. this.container.hitArea = hitArea;
  1024. };
  1025. // These are the event handlers for block containers.
  1026. this._loadEventHandlers = function () {
  1027. var myBlock = this;
  1028. var thisBlock = this.blocks.blockList.indexOf(this);
  1029. var blocks = this.blocks;
  1030. this._calculateBlockHitArea();
  1031. this.container.on('mouseover', function (event) {
  1032. blocks.highlight(thisBlock, true);
  1033. blocks.activeBlock = thisBlock;
  1034. blocks.refreshCanvas();
  1035. });
  1036. var haveClick = false;
  1037. var moved = false;
  1038. var locked = false;
  1039. var getInput = window.hasMouse;
  1040. this.container.on('click', function (event) {
  1041. blocks.activeBlock = thisBlock;
  1042. haveClick = true;
  1043. if (locked) {
  1044. return;
  1045. }
  1046. locked = true;
  1047. setTimeout(function () {
  1048. locked = false;
  1049. }, 500);
  1050. hideDOMLabel();
  1051. if ((!window.hasMouse && getInput) || (window.hasMouse && !moved)) {
  1052. if (blocks.selectingStack) {
  1053. var topBlock = blocks.findTopBlock(thisBlock);
  1054. blocks.selectedStack = topBlock;
  1055. blocks.selectingStack = false;
  1056. } else if (myBlock.name === 'media') {
  1057. myBlock._doOpenMedia(thisBlock);
  1058. } else if (myBlock.name === 'loadFile') {
  1059. myBlock._doOpenMedia(thisBlock);
  1060. } else if (SPECIALINPUTS.indexOf(myBlock.name) !== -1) {
  1061. if (!myBlock.trash) {
  1062. myBlock._changeLabel();
  1063. }
  1064. } else {
  1065. if (!blocks.inLongPress) {
  1066. var topBlock = blocks.findTopBlock(thisBlock);
  1067. console.log('running from ' + blocks.blockList[topBlock].name);
  1068. blocks.logo.runLogoCommands(topBlock);
  1069. }
  1070. }
  1071. }
  1072. });
  1073. this.container.on('mousedown', function (event) {
  1074. // Track time for detecting long pause...
  1075. // but only for top block in stack.
  1076. if (myBlock.connections[0] == null) {
  1077. var d = new Date();
  1078. blocks.mouseDownTime = d.getTime();
  1079. blocks.longPressTimeout = setTimeout(function () {
  1080. blocks.triggerLongPress(myBlock);
  1081. }, LONGPRESSTIME);
  1082. }
  1083. // Always show the trash when there is a block selected,
  1084. trashcan.show();
  1085. // Raise entire stack to the top.
  1086. blocks.raiseStackToTop(thisBlock);
  1087. // And possibly the collapse button.
  1088. if (myBlock.collapseContainer != null) {
  1089. blocks.stage.setChildIndex(myBlock.collapseContainer, blocks.stage.getNumChildren() - 1);
  1090. }
  1091. moved = false;
  1092. var offset = {
  1093. x: myBlock.container.x - Math.round(event.stageX / blocks.blockScale),
  1094. y: myBlock.container.y - Math.round(event.stageY / blocks.blockScale)
  1095. };
  1096. myBlock.container.on('mouseout', function (event) {
  1097. if (haveClick) {
  1098. return;
  1099. }
  1100. if (!blocks.inLongPress) {
  1101. myBlock._mouseoutCallback(event, moved, haveClick, true);
  1102. }
  1103. moved = false;
  1104. });
  1105. myBlock.container.on('pressup', function (event) {
  1106. if (haveClick) {
  1107. return;
  1108. }
  1109. if (!blocks.inLongPress) {
  1110. myBlock._mouseoutCallback(event, moved, haveClick, true);
  1111. }
  1112. moved = false;
  1113. });
  1114. var original = {x: event.stageX / blocks.blockScale, y: event.stageY / blocks.blockScale};
  1115. myBlock.container.on('pressmove', function (event) {
  1116. // FIXME: More voodoo
  1117. event.nativeEvent.preventDefault();
  1118. if (blocks.longPressTimeout != null) {
  1119. clearTimeout(blocks.longPressTimeout);
  1120. blocks.longPressTimeout = null;
  1121. }
  1122. if (!moved && myBlock.label != null) {
  1123. myBlock.label.style.display = 'none';
  1124. }
  1125. if (window.hasMouse) {
  1126. moved = true;
  1127. } else {
  1128. // Make it eaiser to select text on mobile.
  1129. setTimeout(function () {
  1130. moved = Math.abs((event.stageX / blocks.blockScale) - original.x) + Math.abs((event.stageY / blocks.blockScale) - original.y) > 20 && !window.hasMouse;
  1131. getInput = !moved;
  1132. }, 200);
  1133. }
  1134. var oldX = myBlock.container.x;
  1135. var oldY = myBlock.container.y;
  1136. var dx = Math.round(Math.round(event.stageX / blocks.blockScale) + offset.x - oldX);
  1137. var dy = Math.round(Math.round(event.stageY / blocks.blockScale) + offset.y - oldY);
  1138. var finalPos = oldY + dy;
  1139. if (blocks.stage.y === 0 && finalPos < (45 * blocks.blockScale)) {
  1140. dy += (45 * blocks.blockScale) - finalPos;
  1141. }
  1142. blocks.moveBlockRelative(thisBlock, dx, dy);
  1143. // If we are over the trash, warn the user.
  1144. if (trashcan.overTrashcan(event.stageX / blocks.blockScale, event.stageY / blocks.blockScale)) {
  1145. trashcan.startHighlightAnimation();
  1146. } else {
  1147. trashcan.stopHighlightAnimation();
  1148. }
  1149. if (myBlock.isValueBlock() && myBlock.name !== 'media') {
  1150. // Ensure text is on top
  1151. var z = myBlock.container.getNumChildren() - 1;
  1152. myBlock.container.setChildIndex(myBlock.text, z);
  1153. } else if (myBlock.collapseContainer != null) {
  1154. myBlock._positionCollapseContainer(myBlock.protoblock.scale);
  1155. }
  1156. // ...and move any connected blocks.
  1157. blocks.findDragGroup(thisBlock)
  1158. if (blocks.dragGroup.length > 0) {
  1159. for (var b = 0; b < blocks.dragGroup.length; b++) {
  1160. var blk = blocks.dragGroup[b];
  1161. if (b !== 0) {
  1162. blocks.moveBlockRelative(blk, dx, dy);
  1163. }
  1164. }
  1165. }
  1166. blocks.refreshCanvas();
  1167. });
  1168. });
  1169. this.container.on('mouseout', function (event) {
  1170. if (!blocks.inLongPress) {
  1171. myBlock._mouseoutCallback(event, moved, haveClick, true);
  1172. }
  1173. moved = false;
  1174. });
  1175. this.container.on('pressup', function (event) {
  1176. if (!blocks.inLongPress) {
  1177. myBlock._mouseoutCallback(event, moved, haveClick, false);
  1178. }
  1179. moved = false;
  1180. });
  1181. };
  1182. this._mouseoutCallback = function (event, moved, haveClick, hideDOM) {
  1183. var thisBlock = this.blocks.blockList.indexOf(this);
  1184. // Always hide the trash when there is no block selected.
  1185. trashcan.hide();
  1186. if (this.blocks.longPressTimeout != null) {
  1187. clearTimeout(this.blocks.longPressTimeout);
  1188. this.blocks.longPressTimeout = null;
  1189. }
  1190. if (moved) {
  1191. // Check if block is in the trash.
  1192. if (trashcan.overTrashcan(event.stageX / blocks.blockScale, event.stageY / blocks.blockScale)) {
  1193. if (trashcan.isVisible) {
  1194. blocks.sendStackToTrash(this);
  1195. }
  1196. } else {
  1197. // Otherwise, process move.
  1198. // Also, keep track of the time of the last move.
  1199. var d = new Date();
  1200. blocks.mouseDownTime = d.getTime();
  1201. this.blocks.blockMoved(thisBlock);
  1202. // Just in case the blocks are not properly docked after
  1203. // the move (workaround for issue #38 -- Blocks fly
  1204. // apart). Still need to get to the root cause.
  1205. this.blocks.adjustDocks(this.blocks.blockList.indexOf(this), true);
  1206. }
  1207. } else if (SPECIALINPUTS.indexOf(this.name) !== -1 || ['media', 'loadFile'].indexOf(this.name) !== -1) {
  1208. if (!haveClick) {
  1209. // Simulate click on Android.
  1210. var d = new Date();
  1211. if ((d.getTime() - blocks.mouseDownTime) < 500) {
  1212. if (!this.trash)
  1213. {
  1214. var d = new Date();
  1215. blocks.mouseDownTime = d.getTime();
  1216. if (this.name === 'media' || this.name === 'loadFile') {
  1217. this._doOpenMedia(thisBlock);
  1218. } else {
  1219. this._changeLabel();
  1220. }
  1221. }
  1222. }
  1223. }
  1224. }
  1225. if (hideDOM) {
  1226. // Did the mouse move out off the block? If so, hide the
  1227. // label DOM element.
  1228. if (this.bounds != null && (event.stageX / blocks.blockScale < this.container.x || event.stageX / blocks.blockScale > this.container.x + this.bounds.width || event.stageY / blocks.blockScale < this.container.y || event.stageY / blocks.blockScale > this.container.y + this.bounds.height)) {
  1229. this._labelChanged();
  1230. hideDOMLabel();
  1231. this.blocks.unhighlight(null);
  1232. this.blocks.refreshCanvas();
  1233. } else if (this.blocks.activeBlock !== thisBlock) {
  1234. // Are we in a different block altogether?
  1235. hideDOMLabel();
  1236. this.blocks.unhighlight(null);
  1237. this.blocks.refreshCanvas();
  1238. } else {
  1239. // this.blocks.unhighlight(null);
  1240. // this.blocks.refreshCanvas();
  1241. }
  1242. this.blocks.activeBlock = null;
  1243. }
  1244. };
  1245. this._ensureDecorationOnTop = function () {
  1246. // Find the turtle decoration and move it to the top.
  1247. for (var child = 0; child < this.container.getNumChildren(); child++) {
  1248. if (this.container.children[child].name === 'decoration') {
  1249. // Drum block in collapsed state is less wide.
  1250. if (this.name === 'drum') {
  1251. var bounds = this.container.getBounds();
  1252. if (this.collapsed) {
  1253. var dx = 25 * this.protoblock.scale / 2;
  1254. } else {
  1255. var dx = 0;
  1256. }
  1257. for (var turtle = 0; turtle < this.blocks.turtles.turtleList.length; turtle++) {
  1258. if (this.blocks.turtles.turtleList[turtle].startBlock === this) {
  1259. this.blocks.turtles.turtleList[turtle].decorationBitmap.x = bounds.width - dx - 50 * this.protoblock.scale / 2;
  1260. break;
  1261. }
  1262. }
  1263. }
  1264. this.container.setChildIndex(this.container.children[child], this.container.getNumChildren() - 1);
  1265. break;
  1266. }
  1267. }
  1268. };
  1269. this._changeLabel = function () {
  1270. var myBlock = this;
  1271. var blocks = this.blocks;
  1272. var x = this.container.x;
  1273. var y = this.container.y;
  1274. var canvasLeft = blocks.canvas.offsetLeft + 28 * blocks.blockScale;
  1275. var canvasTop = blocks.canvas.offsetTop + 6 * blocks.blockScale;
  1276. var movedStage = false;
  1277. if (!window.hasMouse && blocks.stage.y + y > 75) {
  1278. movedStage = true;
  1279. var fromY = blocks.stage.y;
  1280. blocks.stage.y = -y + 75;
  1281. }
  1282. // A place in the DOM to put modifiable labels (textareas).
  1283. var labelValue = (this.label)?this.label.value:this.value;
  1284. var labelElem = docById('labelDiv');
  1285. if (this.name === 'text') {
  1286. var type = 'text';
  1287. labelElem.innerHTML = '<input id="textLabel" style="position: absolute; -webkit-user-select: text;-moz-user-select: text;-ms-user-select: text;" class="text" type="text" value="' + labelValue + '" />';
  1288. labelElem.classList.add('hasKeyboard');
  1289. this.label = docById('textLabel');
  1290. } else if (this.name === 'solfege') {
  1291. var type = 'solfege';
  1292. var obj = splitSolfege(this.value);
  1293. var selectednote = obj[0];
  1294. var selectedattr = obj[1];
  1295. // solfnotes_ is used in the interface for internationalization.
  1296. //.TRANS: the note names must be separated by single spaces
  1297. var solfnotes_ = _('ti la sol fa mi re do').split(' ');
  1298. var labelHTML = '<select name="solfege" id="solfegeLabel" style="position: absolute; background-color: #88e20a; width: 100px;">'
  1299. for (var i = 0; i < SOLFNOTES.length; i++) {
  1300. if (selectednote === solfnotes_[i]) {
  1301. labelHTML += '<option value="' + SOLFNOTES[i] + '" selected>' + solfnotes_[i] + '</option>';
  1302. } else if (selectednote === SOLFNOTES[i]) {
  1303. labelHTML += '<option value="' + SOLFNOTES[i] + '" selected>' + solfnotes_[i] + '</option>';
  1304. } else {
  1305. labelHTML += '<option value="' + SOLFNOTES[i] + '">' + solfnotes_[i] + '</option>';
  1306. }
  1307. }
  1308. labelHTML += '</select>';
  1309. if (selectedattr === '') {
  1310. selectedattr = '♮';
  1311. }
  1312. labelHTML += '<select name="noteattr" id="noteattrLabel" style="position: absolute; background-color: #88e20a; width: 60px;">';
  1313. for (var i = 0; i < SOLFATTRS.length; i++) {
  1314. if (selectedattr === SOLFATTRS[i]) {
  1315. labelHTML += '<option value="' + selectedattr + '" selected>' + selectedattr + '</option>';
  1316. } else {
  1317. labelHTML += '<option value="' + SOLFATTRS[i] + '">' + SOLFATTRS[i] + '</option>';
  1318. }
  1319. }
  1320. labelHTML += '</select>';
  1321. labelElem.innerHTML = labelHTML;
  1322. this.label = docById('solfegeLabel');
  1323. this.labelattr = docById('noteattrLabel');
  1324. } else if (this.name === 'eastindiansolfege') {
  1325. var type = 'solfege';
  1326. var obj = splitSolfege(this.value);
  1327. var selectednote = WESTERN2EISOLFEGENAMES[obj[0]];
  1328. var selectedattr = obj[1];
  1329. var eisolfnotes_ = ['ni', 'dha', 'pa', 'ma', 'ga', 're', 'sa'];
  1330. var labelHTML = '<select name="solfege" id="solfegeLabel" style="position: absolute; background-color: #88e20a; width: 100px;">'
  1331. for (var i = 0; i < SOLFNOTES.length; i++) {
  1332. if (selectednote === eisolfnotes_[i]) {
  1333. labelHTML += '<option value="' + SOLFNOTES[i] + '" selected>' + eisolfnotes_[i] + '</option>';
  1334. } else if (selectednote === WESTERN2EISOLFEGENAMES[SOLFNOTES[i]]) {
  1335. labelHTML += '<option value="' + SOLFNOTES[i] + '" selected>' + eisolfnotes_[i] + '</option>';
  1336. } else {
  1337. labelHTML += '<option value="' + SOLFNOTES[i] + '">' + eisolfnotes_[i] + '</option>';
  1338. }
  1339. }
  1340. labelHTML += '</select>';
  1341. if (selectedattr === '') {
  1342. selectedattr = '♮';
  1343. }
  1344. labelHTML += '<select name="noteattr" id="noteattrLabel" style="position: absolute; background-color: #88e20a; width: 60px;">';
  1345. for (var i = 0; i < SOLFATTRS.length; i++) {
  1346. if (selectedattr === SOLFATTRS[i]) {
  1347. labelHTML += '<option value="' + selectedattr + '" selected>' + selectedattr + '</option>';
  1348. } else {
  1349. labelHTML += '<option value="' + SOLFATTRS[i] + '">' + SOLFATTRS[i] + '</option>';
  1350. }
  1351. }
  1352. labelHTML += '</select>';
  1353. labelElem.innerHTML = labelHTML;
  1354. this.label = docById('solfegeLabel');
  1355. this.labelattr = docById('noteattrLabel');
  1356. } else if (this.name === 'notename') {
  1357. var type = 'notename';
  1358. const NOTENOTES = ['B', 'A', 'G', 'F', 'E', 'D', 'C'];
  1359. const NOTEATTRS = ['♯♯', '♯', '♮', '♭', '♭♭'];
  1360. if (this.value != null) {
  1361. var selectednote = this.value[0];
  1362. if (this.value.length === 1) {
  1363. var selectedattr = '♮';
  1364. } else if (this.value.length === 2) {
  1365. var selectedattr = this.value[1];
  1366. } else {
  1367. var selectedattr = this.value[1] + this.value[1];
  1368. }
  1369. } else {
  1370. var selectednote = 'G';
  1371. var selectedattr = '♮'
  1372. }
  1373. var labelHTML = '<select name="notename" id="notenameLabel" style="position: absolute; background-color: #88e20a; width: 60px;">'
  1374. for (var i = 0; i < NOTENOTES.length; i++) {
  1375. if (selectednote === NOTENOTES[i]) {
  1376. labelHTML += '<option value="' + selectednote + '" selected>' + selectednote + '</option>';
  1377. } else {
  1378. labelHTML += '<option value="' + NOTENOTES[i] + '">' + NOTENOTES[i] + '</option>';
  1379. }
  1380. }
  1381. labelHTML += '</select>';
  1382. if (selectedattr === '') {
  1383. selectedattr = '♮';
  1384. }
  1385. labelHTML += '<select name="noteattr" id="noteattrLabel" style="position: absolute; background-color: #88e20a; width: 60px;">';
  1386. for (var i = 0; i < NOTEATTRS.length; i++) {
  1387. if (selectedattr === NOTEATTRS[i]) {
  1388. labelHTML += '<option value="' + selectedattr + '" selected>' + selectedattr + '</option>';
  1389. } else {
  1390. labelHTML += '<option value="' + NOTEATTRS[i] + '">' + NOTEATTRS[i] + '</option>';
  1391. }
  1392. }
  1393. labelHTML += '</select>';
  1394. labelElem.innerHTML = labelHTML;
  1395. this.label = docById('notenameLabel');
  1396. this.labelattr = docById('noteattrLabel');
  1397. } else if (this.name === 'modename') {
  1398. var type = 'modename';
  1399. if (this.value != null) {
  1400. var selectedmode = this.value[0];
  1401. } else {
  1402. var selectedmode = getModeName(DEFAULTMODE);
  1403. }
  1404. var labelHTML = '<select name="modename" id="modenameLabel" style="position: absolute; background-color: #88e20a; width: 60px;">'
  1405. for (var i = 0; i < MODENAMES.length; i++) {
  1406. if (MODENAMES[i][0].length === 0) {
  1407. // work around some weird i18n bug
  1408. labelHTML += '<option value="' + MODENAMES[i][1] + '">' + MODENAMES[i][1] + '</option>';
  1409. } else if (selectednote === MODENAMES[i][0]) {
  1410. labelHTML += '<option value="' + selectedmode + '" selected>' + selectedmode + '</option>';
  1411. } else if (selectednote === MODENAMES[i][1]) {
  1412. labelHTML += '<option value="' + selectedmode + '" selected>' + selectedmode + '</option>';
  1413. } else {
  1414. labelHTML += '<option value="' + MODENAMES[i][0] + '">' + MODENAMES[i][0] + '</option>';
  1415. }
  1416. }
  1417. labelHTML += '</select>';
  1418. labelElem.innerHTML = labelHTML;
  1419. this.label = docById('modenameLabel');
  1420. } else if (this.name === 'drumname') {
  1421. var type = 'drumname';
  1422. if (this.value != null) {
  1423. var selecteddrum = getDrumName(this.value);
  1424. } else {
  1425. var selecteddrum = getDrumName(DEFAULTDRUM);
  1426. }
  1427. var labelHTML = '<select name="drumname" id="drumnameLabel" style="position: absolute; background-color: #00b0a4; width: 60px;">'
  1428. for (var i = 0; i < DRUMNAMES.length; i++) {
  1429. if (DRUMNAMES[i][0].length === 0) {
  1430. // work around some weird i18n bug
  1431. labelHTML += '<option value="' + DRUMNAMES[i][1] + '">' + DRUMNAMES[i][1] + '</option>';
  1432. } else if (selecteddrum === DRUMNAMES[i][0]) {
  1433. labelHTML += '<option value="' + selecteddrum + '" selected>' + selecteddrum + '</option>';
  1434. } else if (selecteddrum === DRUMNAMES[i][1]) {
  1435. labelHTML += '<option value="' + selecteddrum + '" selected>' + selecteddrum + '</option>';
  1436. } else {
  1437. labelHTML += '<option value="' + DRUMNAMES[i][0] + '">' + DRUMNAMES[i][0] + '</option>';
  1438. }
  1439. }
  1440. labelHTML += '</select>';
  1441. labelElem.innerHTML = labelHTML;
  1442. this.label = docById('drumnameLabel');
  1443. } else if (this.name === 'voicename') {
  1444. var type = 'voicename';
  1445. if (this.value != null) {
  1446. var selectedvoice = getVoiceName(this.value);
  1447. } else {
  1448. var selectedvoice = getVoiceName(DEFAULTVOICE);
  1449. }
  1450. var labelHTML = '<select name="voicename" id="voicenameLabel" style="position: absolute; background-color: #00b0a4; width: 60px;">'
  1451. for (var i = 0; i < VOICENAMES.length; i++) {
  1452. if (VOICENAMES[i][0].length === 0) {
  1453. // work around some weird i18n bug
  1454. labelHTML += '<option value="' + VOICENAMES[i][1] + '">' + VOICENAMES[i][1] + '</option>';
  1455. } else if (selectedvoice === VOICENAMES[i][0]) {
  1456. labelHTML += '<option value="' + selectedvoice + '" selected>' + selectedvoice + '</option>';
  1457. } else if (selectedvoice === VOICENAMES[i][1]) {
  1458. labelHTML += '<option value="' + selectedvoice + '" selected>' + selectedvoice + '</option>';
  1459. } else {
  1460. labelHTML += '<option value="' + VOICENAMES[i][0] + '">' + VOICENAMES[i][0] + '</option>';
  1461. }
  1462. }
  1463. labelHTML += '</select>';
  1464. labelElem.innerHTML = labelHTML;
  1465. this.label = docById('voicenameLabel');
  1466. } else {
  1467. var type = 'number';
  1468. labelElem.innerHTML = '<input id="numberLabel" style="position: absolute; -webkit-user-select: text;-moz-user-select: text;-ms-user-select: text;" class="number" type="number" value="' + labelValue + '" />';
  1469. labelElem.classList.add('hasKeyboard');
  1470. this.label = docById('numberLabel');
  1471. }
  1472. var focused = false;
  1473. var __blur = function (event) {
  1474. // Not sure why the change in the input is not available
  1475. // immediately in FireFox. We need a workaround if hardware
  1476. // acceleration is enabled.
  1477. if (!focused) {
  1478. return;
  1479. }
  1480. myBlock._labelChanged();
  1481. event.preventDefault();
  1482. labelElem.classList.remove('hasKeyboard');
  1483. window.scroll(0, 0);
  1484. myBlock.label.removeEventListener('keypress', __keypress);
  1485. if (movedStage) {
  1486. blocks.stage.y = fromY;
  1487. blocks.updateStage();
  1488. }
  1489. };
  1490. if (this.name === 'text' || this.name === 'number') {
  1491. this.label.addEventListener('blur', __blur);
  1492. }
  1493. var __keypress = function (event) {
  1494. if ([13, 10, 9].indexOf(event.keyCode) !== -1) {
  1495. __blur(event);
  1496. }
  1497. };
  1498. this.label.addEventListener('keypress', __keypress);
  1499. this.label.addEventListener('change', function () {
  1500. myBlock._labelChanged();
  1501. });
  1502. if (this.labelattr != null) {
  1503. this.labelattr.addEventListener('change', function () {
  1504. myBlock._labelChanged();
  1505. });
  1506. }
  1507. this.label.style.left = Math.round((x + blocks.stage.x) * blocks.blockScale + canvasLeft) + 'px';
  1508. this.label.style.top = Math.round((y + blocks.stage.y) * blocks.blockScale + canvasTop) + 'px';
  1509. // There may be a second select used for # and b.
  1510. if (this.labelattr != null) {
  1511. this.label.style.width = Math.round(60 * blocks.blockScale) * this.protoblock.scale / 2 + 'px';
  1512. this.labelattr.style.left = Math.round((x + blocks.stage.x + 60) * blocks.blockScale + canvasLeft) + 'px';
  1513. this.labelattr.style.top = Math.round((y + blocks.stage.y) * blocks.blockScale + canvasTop) + 'px';
  1514. this.labelattr.style.width = Math.round(60 * blocks.blockScale) * this.protoblock.scale / 2 + 'px';
  1515. this.labelattr.style.fontSize = Math.round(20 * blocks.blockScale * this.protoblock.scale / 2) + 'px';
  1516. } else {
  1517. this.label.style.width = Math.round(100 * blocks.blockScale) * this.protoblock.scale / 2 + 'px';
  1518. }
  1519. this.label.style.fontSize = Math.round(20 * blocks.blockScale * this.protoblock.scale / 2) + 'px';
  1520. this.label.style.display = '';
  1521. this.label.focus();
  1522. // Firefox fix
  1523. setTimeout(function () {
  1524. myBlock.label.style.display = '';
  1525. myBlock.label.focus();
  1526. focused = true;
  1527. }, 100);
  1528. };
  1529. this._labelChanged = function () {
  1530. // Update the block values as they change in the DOM label.
  1531. if (this == null || this.label == null) {
  1532. // console.log('cannot find block associated with label change');
  1533. this._label_lock = false;
  1534. return;
  1535. }
  1536. this._label_lock = true;
  1537. this.label.style.display = 'none';
  1538. if (this.labelattr != null) {
  1539. this.labelattr.style.display = 'none';
  1540. }
  1541. var oldValue = this.value;
  1542. if (this.label.value === '') {
  1543. this.label.value = '_';
  1544. }
  1545. var newValue = this.label.value;
  1546. if (this.labelattr != null) {
  1547. var attrValue = this.labelattr.value;
  1548. switch (attrValue) {
  1549. case '♯♯':
  1550. case '♯':
  1551. case '♭♭':
  1552. case '♭':
  1553. newValue = newValue + attrValue;
  1554. break;
  1555. default:
  1556. break;
  1557. }
  1558. }
  1559. if (oldValue === newValue) {
  1560. // Nothing to do in this case.
  1561. this._label_lock = false;
  1562. return;
  1563. }
  1564. var c = this.connections[0];
  1565. if (this.name === 'text' && c != null) {
  1566. var cblock = this.blocks.blockList[c];
  1567. switch (cblock.name) {
  1568. case 'action':
  1569. var that = this;
  1570. setTimeout(function () {
  1571. that.blocks.palettes.removeActionPrototype(oldValue);
  1572. }, 1000);
  1573. // Ensure new name is unique.
  1574. var uniqueValue = this.blocks.findUniqueActionName(newValue);
  1575. if (uniqueValue !== newValue) {
  1576. newValue = uniqueValue;
  1577. this.value = newValue;
  1578. var label = this.value.toString();
  1579. if (label.length > 8) {
  1580. label = label.substr(0, 7) + '...';
  1581. }
  1582. this.text.text = label;
  1583. this.label.value = newValue;
  1584. this.updateCache();
  1585. }
  1586. break;
  1587. default:
  1588. break;
  1589. }
  1590. }
  1591. // Update the block value and block text.
  1592. if (this.name === 'number') {
  1593. this.value = Number(newValue);
  1594. if (isNaN(this.value)) {
  1595. var thisBlock = this.blocks.blockList.indexOf(this);
  1596. this.blocks.errorMsg(newValue + ': Not a number', thisBlock);
  1597. this.blocks.refreshCanvas();
  1598. this.value = oldValue;
  1599. }
  1600. } else {
  1601. this.value = newValue;
  1602. }
  1603. if (this.name === 'solfege') {
  1604. var obj = splitSolfege(this.value);
  1605. var label = i18nSolfege(obj[0]);
  1606. var attr = obj[1];
  1607. if (attr !== '♮') {
  1608. label += attr;
  1609. }
  1610. } else if (this.name === 'eastindiansolfege') {
  1611. var obj = splitSolfege(this.value);
  1612. var label = WESTERN2EISOLFEGENAMES[obj[0]];
  1613. var attr = obj[1];
  1614. if (attr !== '♮') {
  1615. label += attr;
  1616. }
  1617. } else {
  1618. var label = this.value.toString();
  1619. }
  1620. if (label.length > 8) {
  1621. label = label.substr(0, 7) + '...';
  1622. }
  1623. this.text.text = label;
  1624. // and hide the DOM textview...
  1625. this.label.style.display = 'none';
  1626. // Make sure text is on top.
  1627. var z = this.container.getNumChildren() - 1;
  1628. this.container.setChildIndex(this.text, z);
  1629. this.updateCache();
  1630. var c = this.connections[0];
  1631. if (this.name === 'text' && c != null) {
  1632. var cblock = this.blocks.blockList[c];
  1633. switch (cblock.name) {
  1634. case 'action':
  1635. // If the label was the name of an action, update the
  1636. // associated run this.blocks and the palette buttons
  1637. // Rename both do <- name and nameddo blocks.
  1638. this.blocks.renameDos(oldValue, newValue);
  1639. if (oldValue === _('action')) {
  1640. this.blocks.newNameddoBlock(newValue, this.blocks.actionHasReturn(c), this.blocks.actionHasArgs(c));
  1641. this.blocks.setActionProtoVisiblity(false);
  1642. }
  1643. this.blocks.newNameddoBlock(newValue, this.blocks.actionHasReturn(c), this.blocks.actionHasArgs(c));
  1644. var blockPalette = blocks.palettes.dict['action'];
  1645. for (var blk = 0; blk < blockPalette.protoList.length; blk++) {
  1646. var block = blockPalette.protoList[blk];
  1647. if (oldValue === _('action')) {
  1648. if (block.name === 'nameddo' && block.defaults.length === 0) {
  1649. block.hidden = true;
  1650. }
  1651. }
  1652. else {
  1653. if (block.name === 'nameddo' && block.defaults[0] === oldValue) {
  1654. blockPalette.remove(block,oldValue);
  1655. }
  1656. }
  1657. }
  1658. if (oldValue === _('action')) {
  1659. this.blocks.newNameddoBlock(newValue, this.blocks.actionHasReturn(c), this.blocks.actionHasArgs(c));
  1660. this.blocks.setActionProtoVisiblity(false);
  1661. }
  1662. this.blocks.renameNameddos(oldValue, newValue);
  1663. this.blocks.palettes.hide();
  1664. this.blocks.palettes.updatePalettes('action');
  1665. this.blocks.palettes.show();
  1666. break;
  1667. case 'storein':
  1668. // If the label was the name of a storein, update the
  1669. // associated box this.blocks and the palette buttons.
  1670. if (this.value !== 'box') {
  1671. this.blocks.newStoreinBlock(this.value);
  1672. this.blocks.newNamedboxBlock(this.value);
  1673. }
  1674. // Rename both box <- name and namedbox blocks.
  1675. this.blocks.renameBoxes(oldValue, newValue);
  1676. this.blocks.renameNamedboxes(oldValue, newValue);
  1677. this.blocks.palettes.hide();
  1678. this.blocks.palettes.updatePalettes('boxes');
  1679. this.blocks.palettes.show();
  1680. break;
  1681. case 'setdrum':
  1682. case 'playdrum':
  1683. if (_THIS_IS_MUSIC_BLOCKS_) {
  1684. if (newValue.slice(0, 4) === 'http') {
  1685. this.blocks.logo.synth.loadSynth(newValue);
  1686. }
  1687. }
  1688. break;
  1689. default:
  1690. break;
  1691. }
  1692. }
  1693. // We are done changing the label, so unlock.
  1694. this._label_lock = false;
  1695. if (_THIS_IS_MUSIC_BLOCKS_) {
  1696. // Load the synth for the selected drum.
  1697. if (this.name === 'drumname') {
  1698. this.blocks.logo.synth.loadSynth(getDrumSynthName(this.value));
  1699. } else if (this.name === 'voicename') {
  1700. this.blocks.logo.synth.loadSynth(getVoiceSynthName(this.value));
  1701. }
  1702. }
  1703. };
  1704. };
  1705. function $() {
  1706. var elements = new Array();
  1707. for (var i = 0; i < arguments.length; i++) {
  1708. var element = arguments[i];
  1709. if (typeof element === 'string')
  1710. element = docById(element);
  1711. if (arguments.length === 1)
  1712. return element;
  1713. elements.push(element);
  1714. }
  1715. return elements;
  1716. }
  1717. window.hasMouse = false;
  1718. // Mousemove is not emulated for touch
  1719. document.addEventListener('mousemove', function (e) {
  1720. window.hasMouse = true;
  1721. });
  1722. function _makeBitmap(data, name, callback, args) {
  1723. // Async creation of bitmap from SVG data.
  1724. // Works with Chrome, Safari, Firefox (untested on IE).
  1725. var img = new Image();
  1726. img.onload = function () {
  1727. var bitmap = new createjs.Bitmap(img);
  1728. callback(name, bitmap, args);
  1729. };
  1730. img.src = 'data:image/svg+xml;base64,' + window.btoa(unescape(encodeURIComponent(data)));
  1731. };