block.js 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2011 Google Inc.
  6. * https://developers.google.com/blockly/
  7. *
  8. * Licensed under the Apache License, Version 2.0 (the "License");
  9. * you may not use this file except in compliance with the License.
  10. * You may obtain a copy of the License at
  11. *
  12. * http://www.apache.org/licenses/LICENSE-2.0
  13. *
  14. * Unless required by applicable law or agreed to in writing, software
  15. * distributed under the License is distributed on an "AS IS" BASIS,
  16. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17. * See the License for the specific language governing permissions and
  18. * limitations under the License.
  19. */
  20. /**
  21. * @fileoverview The class representing one block.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Block');
  26. goog.require('Blockly.Blocks');
  27. goog.require('Blockly.Comment');
  28. goog.require('Blockly.Connection');
  29. goog.require('Blockly.Input');
  30. goog.require('Blockly.Mutator');
  31. goog.require('Blockly.MutatorPlus');
  32. goog.require('Blockly.MutatorMinus');
  33. goog.require('Blockly.Warning');
  34. goog.require('Blockly.Workspace');
  35. goog.require('Blockly.Xml');
  36. goog.require('goog.array');
  37. goog.require('goog.asserts');
  38. goog.require('goog.math.Coordinate');
  39. goog.require('goog.string');
  40. /**
  41. * Class for one block.
  42. * Not normally called directly, workspace.newBlock() is preferred.
  43. * @param {!Blockly.Workspace} workspace The block's workspace.
  44. * @param {?string} prototypeName Name of the language object containing
  45. * type-specific functions for this block.
  46. * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise
  47. * create a new id.
  48. * @constructor
  49. */
  50. Blockly.Block = function(workspace, prototypeName, opt_id) {
  51. /** @type {string} */
  52. this.id = (opt_id && !workspace.getBlockById(opt_id)) ?
  53. opt_id : Blockly.genUid();
  54. workspace.blockDB_[this.id] = this;
  55. /** @type {Blockly.Connection} */
  56. this.outputConnection = null;
  57. /** @type {Blockly.Connection} */
  58. this.nextConnection = null;
  59. /** @type {Blockly.Connection} */
  60. this.previousConnection = null;
  61. /** @type {!Array.<!Blockly.Input>} */
  62. this.inputList = [];
  63. /** @type {boolean|undefined} */
  64. this.inputsInline = undefined;
  65. /** @type {boolean} */
  66. this.disabled = false;
  67. /** @type {string|!Function} */
  68. this.tooltip = '';
  69. /** @type {boolean} */
  70. this.contextMenu = true;
  71. /**
  72. * @type {Blockly.Block}
  73. * @private
  74. */
  75. this.parentBlock_ = null;
  76. /**
  77. * @type {!Array.<!Blockly.Block>}
  78. * @private
  79. */
  80. this.childBlocks_ = [];
  81. /**
  82. * @type {boolean}
  83. * @private
  84. */
  85. this.deletable_ = true;
  86. /**
  87. * @type {boolean}
  88. * @private
  89. */
  90. this.movable_ = true;
  91. /**
  92. * @type {boolean}
  93. * @private
  94. */
  95. this.editable_ = true;
  96. /**
  97. * @type {boolean}
  98. * @private
  99. */
  100. this.isShadow_ = false;
  101. /**
  102. * @type {boolean}
  103. * @private
  104. */
  105. this.collapsed_ = false;
  106. /** @type {string|Blockly.Comment} */
  107. this.comment = null;
  108. /**
  109. * @type {!goog.math.Coordinate}
  110. * @private
  111. */
  112. this.xy_ = new goog.math.Coordinate(0, 0);
  113. /** @type {!Blockly.Workspace} */
  114. this.workspace = workspace;
  115. /** @type {boolean} */
  116. this.isInFlyout = workspace.isFlyout;
  117. /** @type {boolean} */
  118. this.RTL = workspace.RTL;
  119. // Copy the type-specific functions and data from the prototype.
  120. if (prototypeName) {
  121. /** @type {string} */
  122. this.type = prototypeName;
  123. var prototype = Blockly.Blocks[prototypeName];
  124. goog.asserts.assertObject(prototype,
  125. 'Error: "%s" is an unknown language block.', prototypeName);
  126. goog.mixin(this, prototype);
  127. }
  128. workspace.addTopBlock(this);
  129. // Call an initialization function, if it exists.
  130. if (goog.isFunction(this.init)) {
  131. this.init();
  132. }
  133. // Record initial inline state.
  134. /** @type {boolean|undefined} */
  135. this.inputsInlineDefault = this.inputsInline;
  136. if (Blockly.Events.isEnabled()) {
  137. Blockly.Events.fire(new Blockly.Events.Create(this));
  138. }
  139. // Bind an onchange function, if it exists.
  140. if (goog.isFunction(this.onchange)) {
  141. this.onchangeWrapper_ = this.onchange.bind(this);
  142. this.workspace.addChangeListener(this.onchangeWrapper_);
  143. }
  144. };
  145. /**
  146. * Obtain a newly created block.
  147. * @param {!Blockly.Workspace} workspace The block's workspace.
  148. * @param {?string} prototypeName Name of the language object containing
  149. * type-specific functions for this block.
  150. * @return {!Blockly.Block} The created block.
  151. * @deprecated December 2015
  152. */
  153. Blockly.Block.obtain = function(workspace, prototypeName) {
  154. console.warn('Deprecated call to Blockly.Block.obtain, ' +
  155. 'use workspace.newBlock instead.');
  156. return workspace.newBlock(prototypeName);
  157. };
  158. /**
  159. * Optional text data that round-trips beween blocks and XML.
  160. * Has no effect. May be used by 3rd parties for meta information.
  161. * @type {?string}
  162. */
  163. Blockly.Block.prototype.data = null;
  164. /**
  165. * Colour of the block in '#RRGGBB' format.
  166. * @type {string}
  167. * @private
  168. */
  169. Blockly.Block.prototype.colour_ = '#000000';
  170. /**
  171. * Dispose of this block.
  172. * @param {boolean} healStack If true, then try to heal any gap by connecting
  173. * the next statement with the previous statement. Otherwise, dispose of
  174. * all children of this block.
  175. */
  176. Blockly.Block.prototype.dispose = function(healStack) {
  177. // Terminate onchange event calls.
  178. if (this.onchangeWrapper_) {
  179. this.workspace.removeChangeListener(this.onchangeWrapper_);
  180. }
  181. this.unplug(healStack);
  182. if (Blockly.Events.isEnabled()) {
  183. Blockly.Events.fire(new Blockly.Events.Delete(this));
  184. }
  185. Blockly.Events.disable();
  186. try {
  187. // This block is now at the top of the workspace.
  188. // Remove this block from the workspace's list of top-most blocks.
  189. if (this.workspace) {
  190. this.workspace.removeTopBlock(this);
  191. // Remove from block database.
  192. delete this.workspace.blockDB_[this.id];
  193. this.workspace = null;
  194. }
  195. // Just deleting this block from the DOM would result in a memory leak as
  196. // well as corruption of the connection database. Therefore we must
  197. // methodically step through the blocks and carefully disassemble them.
  198. this.unbacklight(); // BlocksCAD
  199. // First, dispose of all my children.
  200. for (var i = this.childBlocks_.length - 1; i >= 0; i--) {
  201. this.childBlocks_[i].dispose(false);
  202. }
  203. // Then dispose of myself.
  204. // Dispose of all inputs and their fields.
  205. for (var i = 0, input; input = this.inputList[i]; i++) {
  206. input.dispose();
  207. }
  208. this.inputList.length = 0;
  209. // Dispose of any remaining connections (next/previous/output).
  210. var connections = this.getConnections_(true);
  211. for (var i = 0; i < connections.length; i++) {
  212. var connection = connections[i];
  213. if (connection.isConnected()) {
  214. connection.disconnect();
  215. }
  216. connections[i].dispose();
  217. }
  218. } finally {
  219. Blockly.Events.enable();
  220. }
  221. };
  222. /**
  223. * Unplug this block from its superior block. If this block is a statement,
  224. * optionally reconnect the block underneath with the block on top.
  225. * @param {boolean} opt_healStack Disconnect child statement and reconnect
  226. * stack. Defaults to false.
  227. */
  228. Blockly.Block.prototype.unplug = function(opt_healStack) {
  229. if (this.outputConnection) {
  230. if (this.outputConnection.isConnected()) {
  231. // Disconnect from any superior block.
  232. this.outputConnection.disconnect();
  233. }
  234. } else if (this.previousConnection) {
  235. var previousTarget = null;
  236. if (this.previousConnection.isConnected()) {
  237. // Remember the connection that any next statements need to connect to.
  238. previousTarget = this.previousConnection.targetConnection;
  239. // Detach this block from the parent's tree.
  240. this.previousConnection.disconnect();
  241. }
  242. var nextBlock = this.getNextBlock();
  243. if (opt_healStack && nextBlock) {
  244. // Disconnect the next statement.
  245. var nextTarget = this.nextConnection.targetConnection;
  246. nextTarget.disconnect();
  247. if (previousTarget && previousTarget.checkType_(nextTarget)) {
  248. // Attach the next statement to the previous statement.
  249. previousTarget.connect(nextTarget);
  250. }
  251. }
  252. }
  253. };
  254. /**
  255. * Returns all connections originating from this block.
  256. * @return {!Array.<!Blockly.Connection>} Array of connections.
  257. * @private
  258. */
  259. Blockly.Block.prototype.getConnections_ = function() {
  260. var myConnections = [];
  261. if (this.outputConnection) {
  262. myConnections.push(this.outputConnection);
  263. }
  264. if (this.previousConnection) {
  265. myConnections.push(this.previousConnection);
  266. }
  267. if (this.nextConnection) {
  268. myConnections.push(this.nextConnection);
  269. }
  270. for (var i = 0, input; input = this.inputList[i]; i++) {
  271. if (input.connection) {
  272. myConnections.push(input.connection);
  273. }
  274. }
  275. return myConnections;
  276. };
  277. /**
  278. * Walks down a stack of blocks and finds the last next connection on the stack.
  279. * @return {Blockly.Connection} The last next connection on the stack, or null.
  280. * @private
  281. */
  282. Blockly.Block.prototype.lastConnectionInStack_ = function() {
  283. var nextConnection = this.nextConnection;
  284. while (nextConnection) {
  285. var nextBlock = nextConnection.targetBlock();
  286. if (!nextBlock) {
  287. // Found a next connection with nothing on the other side.
  288. return nextConnection;
  289. }
  290. nextConnection = nextBlock.nextConnection;
  291. }
  292. // Ran out of next connections.
  293. return null;
  294. };
  295. /**
  296. * Bump unconnected blocks out of alignment. Two blocks which aren't actually
  297. * connected should not coincidentally line up on screen.
  298. * @private
  299. */
  300. Blockly.Block.prototype.bumpNeighbours_ = function() {
  301. if (!this.workspace) {
  302. return; // Deleted block.
  303. }
  304. if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
  305. return; // Don't bump blocks during a drag.
  306. }
  307. var rootBlock = this.getRootBlock();
  308. if (rootBlock.isInFlyout) {
  309. return; // Don't move blocks around in a flyout.
  310. }
  311. // Loop though every connection on this block.
  312. var myConnections = this.getConnections_(false);
  313. for (var i = 0, connection; connection = myConnections[i]; i++) {
  314. // Spider down from this block bumping all sub-blocks.
  315. if (connection.isConnected() && connection.isSuperior()) {
  316. connection.targetBlock().bumpNeighbours_();
  317. }
  318. var neighbours = connection.neighbours_(Blockly.SNAP_RADIUS);
  319. for (var j = 0, otherConnection; otherConnection = neighbours[j]; j++) {
  320. // If both connections are connected, that's probably fine. But if
  321. // either one of them is unconnected, then there could be confusion.
  322. if (!connection.isConnected() || !otherConnection.isConnected()) {
  323. // Only bump blocks if they are from different tree structures.
  324. if (otherConnection.getSourceBlock().getRootBlock() != rootBlock) {
  325. // Always bump the inferior block.
  326. if (connection.isSuperior()) {
  327. otherConnection.bumpAwayFrom_(connection);
  328. } else {
  329. connection.bumpAwayFrom_(otherConnection);
  330. }
  331. }
  332. }
  333. }
  334. }
  335. };
  336. /**
  337. * Return the parent block or null if this block is at the top level.
  338. * @return {Blockly.Block} The block that holds the current block.
  339. */
  340. Blockly.Block.prototype.getParent = function() {
  341. // Look at the DOM to see if we are nested in another block.
  342. return this.parentBlock_;
  343. };
  344. /**
  345. * Return the input that connects to the specified block.
  346. * @param {!Blockly.Block} block A block connected to an input on this block.
  347. * @return {Blockly.Input} The input that connects to the specified block.
  348. */
  349. Blockly.Block.prototype.getInputWithBlock = function(block) {
  350. for (var i = 0, input; input = this.inputList[i]; i++) {
  351. if (input.connection && input.connection.targetBlock() == block) {
  352. return input;
  353. }
  354. }
  355. return null;
  356. };
  357. /**
  358. * Return the parent block that surrounds the current block, or null if this
  359. * block has no surrounding block. A parent block might just be the previous
  360. * statement, whereas the surrounding block is an if statement, while loop, etc.
  361. * @return {Blockly.Block} The block that surrounds the current block.
  362. */
  363. Blockly.Block.prototype.getSurroundParent = function() {
  364. var block = this;
  365. do {
  366. var prevBlock = block;
  367. block = block.getParent();
  368. if (!block) {
  369. // Ran off the top.
  370. return null;
  371. }
  372. } while (block.getNextBlock() == prevBlock);
  373. // This block is an enclosing parent, not just a statement in a stack.
  374. return block;
  375. };
  376. /**
  377. * Return the next statement block directly connected to this block.
  378. * @return {Blockly.Block} The next statement block or null.
  379. */
  380. Blockly.Block.prototype.getNextBlock = function() {
  381. return this.nextConnection && this.nextConnection.targetBlock();
  382. };
  383. /**
  384. * Return the top-most block in this block's tree.
  385. * This will return itself if this block is at the top level.
  386. * @return {!Blockly.Block} The root block.
  387. */
  388. Blockly.Block.prototype.getRootBlock = function() {
  389. var rootBlock;
  390. var block = this;
  391. do {
  392. rootBlock = block;
  393. block = rootBlock.parentBlock_;
  394. } while (block);
  395. return rootBlock;
  396. };
  397. /**
  398. * Find all the blocks that are directly nested inside this one.
  399. * Includes value and block inputs, as well as any following statement.
  400. * Excludes any connection on an output tab or any preceding statement.
  401. * @return {!Array.<!Blockly.Block>} Array of blocks.
  402. */
  403. Blockly.Block.prototype.getChildren = function() {
  404. return this.childBlocks_;
  405. };
  406. /**
  407. * Set parent of this block to be a new block or null.
  408. * @param {Blockly.Block} newParent New parent block.
  409. */
  410. Blockly.Block.prototype.setParent = function(newParent) {
  411. if (newParent == this.parentBlock_) {
  412. return;
  413. }
  414. if (this.parentBlock_) {
  415. // Remove this block from the old parent's child list.
  416. var children = this.parentBlock_.childBlocks_;
  417. for (var child, x = 0; child = children[x]; x++) {
  418. if (child == this) {
  419. children.splice(x, 1);
  420. break;
  421. }
  422. }
  423. // Disconnect from superior blocks.
  424. if (this.previousConnection && this.previousConnection.isConnected()) {
  425. throw 'Still connected to previous block.';
  426. }
  427. if (this.outputConnection && this.outputConnection.isConnected()) {
  428. throw 'Still connected to parent block.';
  429. }
  430. this.parentBlock_ = null;
  431. // This block hasn't actually moved on-screen, so there's no need to update
  432. // its connection locations.
  433. } else {
  434. // Remove this block from the workspace's list of top-most blocks.
  435. this.workspace.removeTopBlock(this);
  436. }
  437. this.parentBlock_ = newParent;
  438. if (newParent) {
  439. // Add this block to the new parent's child list.
  440. newParent.childBlocks_.push(this);
  441. } else {
  442. this.workspace.addTopBlock(this);
  443. }
  444. };
  445. /**
  446. * Find all the blocks that are directly or indirectly nested inside this one.
  447. * Includes this block in the list.
  448. * Includes value and block inputs, as well as any following statements.
  449. * Excludes any connection on an output tab or any preceding statements.
  450. * @return {!Array.<!Blockly.Block>} Flattened array of blocks.
  451. */
  452. Blockly.Block.prototype.getDescendants = function() {
  453. var blocks = [this];
  454. for (var child, x = 0; child = this.childBlocks_[x]; x++) {
  455. blocks.push.apply(blocks, child.getDescendants());
  456. }
  457. return blocks;
  458. };
  459. /**
  460. * Get whether this block is deletable or not.
  461. * @return {boolean} True if deletable.
  462. */
  463. Blockly.Block.prototype.isDeletable = function() {
  464. return this.deletable_ && !this.isShadow_ &&
  465. !(this.workspace && this.workspace.options.readOnly);
  466. };
  467. /**
  468. * Set whether this block is deletable or not.
  469. * @param {boolean} deletable True if deletable.
  470. */
  471. Blockly.Block.prototype.setDeletable = function(deletable) {
  472. this.deletable_ = deletable;
  473. };
  474. /**
  475. * Get whether this block is movable or not.
  476. * @return {boolean} True if movable.
  477. */
  478. Blockly.Block.prototype.isMovable = function() {
  479. return this.movable_ && !this.isShadow_ &&
  480. !(this.workspace && this.workspace.options.readOnly);
  481. };
  482. /**
  483. * Set whether this block is movable or not.
  484. * @param {boolean} movable True if movable.
  485. */
  486. Blockly.Block.prototype.setMovable = function(movable) {
  487. this.movable_ = movable;
  488. };
  489. /**
  490. * Get whether this block is a shadow block or not.
  491. * @return {boolean} True if a shadow.
  492. */
  493. Blockly.Block.prototype.isShadow = function() {
  494. return this.isShadow_;
  495. };
  496. /**
  497. * Set whether this block is a shadow block or not.
  498. * @param {boolean} shadow True if a shadow.
  499. */
  500. Blockly.Block.prototype.setShadow = function(shadow) {
  501. this.isShadow_ = shadow;
  502. };
  503. /**
  504. * Get whether this block is editable or not.
  505. * @return {boolean} True if editable.
  506. */
  507. Blockly.Block.prototype.isEditable = function() {
  508. return this.editable_ && !(this.workspace && this.workspace.options.readOnly);
  509. };
  510. /**
  511. * Set whether this block is editable or not.
  512. * @param {boolean} editable True if editable.
  513. */
  514. Blockly.Block.prototype.setEditable = function(editable) {
  515. this.editable_ = editable;
  516. for (var i = 0, input; input = this.inputList[i]; i++) {
  517. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  518. field.updateEditable();
  519. }
  520. }
  521. };
  522. /**
  523. * Set whether the connections are hidden (not tracked in a database) or not.
  524. * Recursively walk down all child blocks (except collapsed blocks).
  525. * @param {boolean} hidden True if connections are hidden.
  526. */
  527. Blockly.Block.prototype.setConnectionsHidden = function(hidden) {
  528. if (!hidden && this.isCollapsed()) {
  529. if (this.outputConnection) {
  530. this.outputConnection.setHidden(hidden);
  531. }
  532. if (this.previousConnection) {
  533. this.previousConnection.setHidden(hidden);
  534. }
  535. if (this.nextConnection) {
  536. this.nextConnection.setHidden(hidden);
  537. var child = this.nextConnection.targetBlock();
  538. if (child) {
  539. child.setConnectionsHidden(hidden);
  540. }
  541. }
  542. } else {
  543. var myConnections = this.getConnections_(true);
  544. for (var i = 0, connection; connection = myConnections[i]; i++) {
  545. connection.setHidden(hidden);
  546. if (connection.isSuperior()) {
  547. var child = connection.targetBlock();
  548. if (child) {
  549. child.setConnectionsHidden(hidden);
  550. }
  551. }
  552. }
  553. }
  554. };
  555. /**
  556. * Set the URL of this block's help page.
  557. * @param {string|Function} url URL string for block help, or function that
  558. * returns a URL. Null for no help.
  559. */
  560. Blockly.Block.prototype.setHelpUrl = function(url) {
  561. this.helpUrl = url;
  562. };
  563. /**
  564. * Change the tooltip text for a block.
  565. * @param {string|!Function} newTip Text for tooltip or a parent element to
  566. * link to for its tooltip. May be a function that returns a string.
  567. */
  568. Blockly.Block.prototype.setTooltip = function(newTip) {
  569. this.tooltip = newTip;
  570. };
  571. /**
  572. * Get the colour of a block.
  573. * @return {string} #RRGGBB string.
  574. */
  575. Blockly.Block.prototype.getColour = function() {
  576. return this.colour_;
  577. };
  578. /**
  579. * Change the colour of a block.
  580. * @param {number|string} colour HSV hue value, or #RRGGBB string.
  581. */
  582. Blockly.Block.prototype.setColour = function(colour) {
  583. var hue = parseFloat(colour);
  584. if (!isNaN(hue)) {
  585. this.colour_ = Blockly.hueToRgb(hue);
  586. } else if (goog.isString(colour) && colour.match(/^#[0-9a-fA-F]{6}$/)) {
  587. this.colour_ = colour;
  588. } else {
  589. throw 'Invalid colour: ' + colour;
  590. }
  591. };
  592. // /**
  593. // * Get the colour of a block.
  594. // * @return {number} colourRGB: an Array [r,g,b]
  595. // * Added for Blockscad.
  596. // */
  597. // Blockly.Block.prototype.getColourHex = function() {
  598. // return this.colourHex;
  599. // };
  600. // *
  601. // * Change the colour of a block.
  602. // * @param {number} colourRGB: an array containing [R,G,B] values
  603. // * Added for Blockscad
  604. // Blockly.Block.prototype.setColourHex = function(colourHex) {
  605. // this.colourHex = colourHex;
  606. // // if (this.rendered) {
  607. // // this.updateColour();
  608. // // }
  609. // };
  610. /**
  611. * Returns the named field from a block.
  612. * @param {string} name The name of the field.
  613. * @return {Blockly.Field} Named field, or null if field does not exist.
  614. */
  615. Blockly.Block.prototype.getField = function(name) {
  616. for (var i = 0, input; input = this.inputList[i]; i++) {
  617. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  618. if (field.name === name) {
  619. return field;
  620. }
  621. }
  622. }
  623. return null;
  624. };
  625. // BlocksCAD
  626. Blockly.Block.prototype.getAllFieldValues = function() {
  627. var fields = '';
  628. for (var i = 0, input; input = this.inputList[i]; i++) {
  629. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  630. if (field.name != undefined) {
  631. fields += (field.getValue());
  632. fields += ':';
  633. }
  634. }
  635. }
  636. return fields;
  637. }
  638. /**
  639. * Return all variables referenced by this block.
  640. * @return {!Array.<string>} List of variable names.
  641. */
  642. Blockly.Block.prototype.getVars = function() {
  643. var vars = [];
  644. for (var i = 0, input; input = this.inputList[i]; i++) {
  645. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  646. if (field instanceof Blockly.FieldVariable) {
  647. vars.push(field.getValue());
  648. }
  649. }
  650. }
  651. return vars;
  652. };
  653. /**
  654. * Notification that a variable is renaming.
  655. * If the name matches one of this block's variables, rename it.
  656. * @param {string} oldName Previous name of variable.
  657. * @param {string} newName Renamed variable.
  658. */
  659. Blockly.Block.prototype.renameVar = function(oldName, newName) {
  660. for (var i = 0, input; input = this.inputList[i]; i++) {
  661. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  662. if (field instanceof Blockly.FieldVariable &&
  663. Blockly.Names.equals(oldName, field.getValue())) {
  664. field.setValue(newName);
  665. }
  666. }
  667. }
  668. };
  669. /**
  670. * Returns the language-neutral value from the field of a block.
  671. * @param {string} name The name of the field.
  672. * @return {?string} Value from the field or null if field does not exist.
  673. */
  674. Blockly.Block.prototype.getFieldValue = function(name) {
  675. var field = this.getField(name);
  676. if (field) {
  677. return field.getValue();
  678. }
  679. return null;
  680. };
  681. /**
  682. * Returns the language-neutral value from the field of a block.
  683. * @param {string} name The name of the field.
  684. * @return {?string} Value from the field or null if field does not exist.
  685. * @deprecated December 2013
  686. */
  687. Blockly.Block.prototype.getTitleValue = function(name) {
  688. console.warn('Deprecated call to getTitleValue, use getFieldValue instead.');
  689. return this.getFieldValue(name);
  690. };
  691. /**
  692. * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE').
  693. * @param {string} newValue Value to be the new field.
  694. * @param {string} name The name of the field.
  695. */
  696. Blockly.Block.prototype.setFieldValue = function(newValue, name) {
  697. var field = this.getField(name);
  698. goog.asserts.assertObject(field, 'Field "%s" not found.', name);
  699. field.setValue(newValue);
  700. };
  701. /**
  702. * Change the field value for a block (e.g. 'CHOOSE' or 'REMOVE').
  703. * @param {string} newValue Value to be the new field.
  704. * @param {string} name The name of the field.
  705. * @deprecated December 2013
  706. */
  707. Blockly.Block.prototype.setTitleValue = function(newValue, name) {
  708. console.warn('Deprecated call to setTitleValue, use setFieldValue instead.');
  709. this.setFieldValue(newValue, name);
  710. };
  711. /**
  712. * Set whether this block can chain onto the bottom of another block.
  713. * @param {boolean} newBoolean True if there can be a previous statement.
  714. * @param {string|Array.<string>|null|undefined} opt_check Statement type or
  715. * list of statement types. Null/undefined if any type could be connected.
  716. */
  717. Blockly.Block.prototype.setPreviousStatement = function(newBoolean, opt_check) {
  718. if (newBoolean) {
  719. if (opt_check === undefined) {
  720. opt_check = null;
  721. }
  722. if (!this.previousConnection) {
  723. goog.asserts.assert(!this.outputConnection,
  724. 'Remove output connection prior to adding previous connection.');
  725. this.previousConnection =
  726. this.makeConnection_(Blockly.PREVIOUS_STATEMENT);
  727. }
  728. this.previousConnection.setCheck(opt_check);
  729. } else {
  730. if (this.previousConnection) {
  731. goog.asserts.assert(!this.previousConnection.isConnected(),
  732. 'Must disconnect previous statement before removing connection.');
  733. this.previousConnection.dispose();
  734. this.previousConnection = null;
  735. }
  736. }
  737. };
  738. /**
  739. * Set whether another block can chain onto the bottom of this block.
  740. * @param {boolean} newBoolean True if there can be a next statement.
  741. * @param {string|Array.<string>|null|undefined} opt_check Statement type or
  742. * list of statement types. Null/undefined if any type could be connected.
  743. */
  744. Blockly.Block.prototype.setNextStatement = function(newBoolean, opt_check) {
  745. if (newBoolean) {
  746. if (opt_check === undefined) {
  747. opt_check = null;
  748. }
  749. if (!this.nextConnection) {
  750. this.nextConnection = this.makeConnection_(Blockly.NEXT_STATEMENT);
  751. }
  752. this.nextConnection.setCheck(opt_check);
  753. } else {
  754. if (this.nextConnection) {
  755. goog.asserts.assert(!this.nextConnection.isConnected(),
  756. 'Must disconnect next statement before removing connection.');
  757. this.nextConnection.dispose();
  758. this.nextConnection = null;
  759. }
  760. }
  761. };
  762. /**
  763. * Set whether this block returns a value.
  764. * @param {boolean} newBoolean True if there is an output.
  765. * @param {string|Array.<string>|null|undefined} opt_check Returned type or list
  766. * of returned types. Null or undefined if any type could be returned
  767. * (e.g. variable get).
  768. */
  769. Blockly.Block.prototype.setOutput = function(newBoolean, opt_check) {
  770. if (newBoolean) {
  771. if (opt_check === undefined) {
  772. opt_check = null;
  773. }
  774. if (!this.outputConnection) {
  775. goog.asserts.assert(!this.previousConnection,
  776. 'Remove previous connection prior to adding output connection.');
  777. this.outputConnection = this.makeConnection_(Blockly.OUTPUT_VALUE);
  778. }
  779. this.outputConnection.setCheck(opt_check);
  780. } else {
  781. if (this.outputConnection) {
  782. goog.asserts.assert(!this.outputConnection.isConnected(),
  783. 'Must disconnect output value before removing connection.');
  784. this.outputConnection.dispose();
  785. this.outputConnection = null;
  786. }
  787. }
  788. };
  789. /**
  790. * Set whether value inputs are arranged horizontally or vertically.
  791. * @param {boolean} newBoolean True if inputs are horizontal.
  792. */
  793. Blockly.Block.prototype.setInputsInline = function(newBoolean) {
  794. if (this.inputsInline != newBoolean) {
  795. Blockly.Events.fire(new Blockly.Events.Change(
  796. this, 'inline', null, this.inputsInline, newBoolean));
  797. this.inputsInline = newBoolean;
  798. }
  799. };
  800. /**
  801. * Get whether value inputs are arranged horizontally or vertically.
  802. * @return {boolean} True if inputs are horizontal.
  803. */
  804. Blockly.Block.prototype.getInputsInline = function() {
  805. if (this.inputsInline != undefined) {
  806. // Set explicitly.
  807. return this.inputsInline;
  808. }
  809. // Not defined explicitly. Figure out what would look best.
  810. for (var i = 1; i < this.inputList.length; i++) {
  811. if (this.inputList[i - 1].type == Blockly.DUMMY_INPUT &&
  812. this.inputList[i].type == Blockly.DUMMY_INPUT) {
  813. // Two dummy inputs in a row. Don't inline them.
  814. return false;
  815. }
  816. }
  817. for (var i = 1; i < this.inputList.length; i++) {
  818. if (this.inputList[i - 1].type == Blockly.INPUT_VALUE &&
  819. this.inputList[i].type == Blockly.DUMMY_INPUT) {
  820. // Dummy input after a value input. Inline them.
  821. return true;
  822. }
  823. }
  824. return false;
  825. };
  826. /**
  827. * Set whether the block is disabled or not.
  828. * @param {boolean} disabled True if disabled.
  829. */
  830. Blockly.Block.prototype.setDisabled = function(disabled) {
  831. if (this.disabled != disabled) {
  832. Blockly.Events.fire(new Blockly.Events.Change(
  833. this, 'disabled', null, this.disabled, disabled));
  834. this.disabled = disabled;
  835. }
  836. };
  837. /**
  838. * Get whether the block is disabled or not due to parents.
  839. * The block's own disabled property is not considered.
  840. * @return {boolean} True if disabled.
  841. */
  842. Blockly.Block.prototype.getInheritedDisabled = function() {
  843. var block = this;
  844. while (true) {
  845. block = block.getSurroundParent();
  846. if (!block) {
  847. // Ran off the top.
  848. return false;
  849. } else if (block.disabled) {
  850. return true;
  851. }
  852. }
  853. };
  854. /**
  855. * Get whether the block is collapsed or not.
  856. * @return {boolean} True if collapsed.
  857. */
  858. Blockly.Block.prototype.isCollapsed = function() {
  859. return this.collapsed_;
  860. };
  861. /**
  862. * Set whether the block is collapsed or not.
  863. * @param {boolean} collapsed True if collapsed.
  864. */
  865. Blockly.Block.prototype.setCollapsed = function(collapsed) {
  866. if (this.collapsed_ != collapsed) {
  867. Blockly.Events.fire(new Blockly.Events.Change(
  868. this, 'collapsed', null, this.collapsed_, collapsed));
  869. this.collapsed_ = collapsed;
  870. }
  871. };
  872. /**
  873. * Create a human-readable text representation of this block and any children.
  874. * @param {number=} opt_maxLength Truncate the string to this length.
  875. * @return {string} Text of block.
  876. */
  877. Blockly.Block.prototype.toString = function(opt_maxLength) {
  878. var text = [];
  879. if (this.collapsed_) {
  880. text.push(this.getInput('_TEMP_COLLAPSED_INPUT').fieldRow[0].text_);
  881. } else {
  882. for (var i = 0, input; input = this.inputList[i]; i++) {
  883. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  884. text.push(field.getText());
  885. }
  886. if (input.connection) {
  887. var child = input.connection.targetBlock();
  888. if (child) {
  889. text.push(child.toString());
  890. } else {
  891. text.push('?');
  892. }
  893. }
  894. }
  895. }
  896. text = goog.string.trim(text.join(' ')) || '???';
  897. if (opt_maxLength) {
  898. // TODO: Improve truncation so that text from this block is given priority.
  899. // E.g. "1+2+3+4+5+6+7+8+9=0" should be "...6+7+8+9=0", not "1+2+3+4+5...".
  900. // E.g. "1+2+3+4+5=6+7+8+9+0" should be "...4+5=6+7...".
  901. text = goog.string.truncate(text, opt_maxLength);
  902. }
  903. return text;
  904. };
  905. /**
  906. * Shortcut for appending a value input row.
  907. * @param {string} name Language-neutral identifier which may used to find this
  908. * input again. Should be unique to this block.
  909. * @return {!Blockly.Input} The input object created.
  910. */
  911. Blockly.Block.prototype.appendValueInput = function(name) {
  912. return this.appendInput_(Blockly.INPUT_VALUE, name);
  913. };
  914. /**
  915. * Shortcut for appending a statement input row.
  916. * @param {string} name Language-neutral identifier which may used to find this
  917. * input again. Should be unique to this block.
  918. * @return {!Blockly.Input} The input object created.
  919. */
  920. Blockly.Block.prototype.appendStatementInput = function(name) {
  921. return this.appendInput_(Blockly.NEXT_STATEMENT, name);
  922. };
  923. /**
  924. * Shortcut for appending a dummy input row.
  925. * @param {string=} opt_name Language-neutral identifier which may used to find
  926. * this input again. Should be unique to this block.
  927. * @return {!Blockly.Input} The input object created.
  928. */
  929. Blockly.Block.prototype.appendDummyInput = function(opt_name) {
  930. return this.appendInput_(Blockly.DUMMY_INPUT, opt_name || '');
  931. };
  932. /**
  933. * Initialize this block using a cross-platform, internationalization-friendly
  934. * JSON description.
  935. * @param {!Object} json Structured data describing the block.
  936. */
  937. Blockly.Block.prototype.jsonInit = function(json) {
  938. // Validate inputs.
  939. goog.asserts.assert(json['output'] == undefined ||
  940. json['previousStatement'] == undefined,
  941. 'Must not have both an output and a previousStatement.');
  942. // Set basic properties of block.
  943. if (json['colour'] !== undefined) {
  944. this.setColour(json['colour']);
  945. }
  946. // Interpolate the message blocks.
  947. var i = 0;
  948. while (json['message' + i] !== undefined) {
  949. this.interpolate_(json['message' + i], json['args' + i] || [],
  950. json['lastDummyAlign' + i]);
  951. i++;
  952. }
  953. if (json['inputsInline'] !== undefined) {
  954. this.setInputsInline(json['inputsInline']);
  955. }
  956. // Set output and previous/next connections.
  957. if (json['output'] !== undefined) {
  958. this.setOutput(true, json['output']);
  959. }
  960. if (json['previousStatement'] !== undefined) {
  961. this.setPreviousStatement(true, json['previousStatement']);
  962. }
  963. if (json['nextStatement'] !== undefined) {
  964. this.setNextStatement(true, json['nextStatement']);
  965. }
  966. if (json['tooltip'] !== undefined) {
  967. this.setTooltip(json['tooltip']);
  968. }
  969. if (json['helpUrl'] !== undefined) {
  970. this.setHelpUrl(json['helpUrl']);
  971. }
  972. };
  973. /**
  974. * Interpolate a message description onto the block.
  975. * @param {string} message Text contains interpolation tokens (%1, %2, ...)
  976. * that match with fields or inputs defined in the args array.
  977. * @param {!Array} args Array of arguments to be interpolated.
  978. * @param {=string} lastDummyAlign If a dummy input is added at the end,
  979. * how should it be aligned?
  980. * @private
  981. */
  982. Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
  983. var tokens = Blockly.utils.tokenizeInterpolation(message);
  984. // Interpolate the arguments. Build a list of elements.
  985. var indexDup = [];
  986. var indexCount = 0;
  987. var elements = [];
  988. for (var i = 0; i < tokens.length; i++) {
  989. var token = tokens[i];
  990. if (typeof token == 'number') {
  991. goog.asserts.assert(token > 0 && token <= args.length,
  992. 'Message index "%s" out of range.', token);
  993. goog.asserts.assert(!indexDup[token],
  994. 'Message index "%s" duplicated.', token);
  995. indexDup[token] = true;
  996. indexCount++;
  997. elements.push(args[token - 1]);
  998. } else {
  999. token = token.trim();
  1000. if (token) {
  1001. elements.push(token);
  1002. }
  1003. }
  1004. }
  1005. goog.asserts.assert(indexCount == args.length,
  1006. 'Message does not reference all %s arg(s).', args.length);
  1007. // Add last dummy input if needed.
  1008. if (elements.length && (typeof elements[elements.length - 1] == 'string' ||
  1009. elements[elements.length - 1]['type'].indexOf('field_') == 0)) {
  1010. var dummyInput = {type: 'input_dummy'};
  1011. if (lastDummyAlign) {
  1012. dummyInput['align'] = lastDummyAlign;
  1013. }
  1014. elements.push(dummyInput);
  1015. }
  1016. // Lookup of alignment constants.
  1017. var alignmentLookup = {
  1018. 'LEFT': Blockly.ALIGN_LEFT,
  1019. 'RIGHT': Blockly.ALIGN_RIGHT,
  1020. 'CENTRE': Blockly.ALIGN_CENTRE
  1021. };
  1022. // Populate block with inputs and fields.
  1023. var fieldStack = [];
  1024. for (var i = 0; i < elements.length; i++) {
  1025. var element = elements[i];
  1026. if (typeof element == 'string') {
  1027. fieldStack.push([element, undefined]);
  1028. } else {
  1029. var field = null;
  1030. var input = null;
  1031. do {
  1032. var altRepeat = false;
  1033. if (typeof element == 'string') {
  1034. field = new Blockly.FieldLabel(element);
  1035. } else {
  1036. switch (element['type']) {
  1037. case 'input_value':
  1038. input = this.appendValueInput(element['name']);
  1039. break;
  1040. case 'input_statement':
  1041. input = this.appendStatementInput(element['name']);
  1042. break;
  1043. case 'input_dummy':
  1044. input = this.appendDummyInput(element['name']);
  1045. break;
  1046. case 'field_label':
  1047. field = new Blockly.FieldLabel(element['text'], element['class']);
  1048. break;
  1049. case 'field_input':
  1050. field = new Blockly.FieldTextInput(element['text']);
  1051. if (typeof element['spellcheck'] == 'boolean') {
  1052. field.setSpellcheck(element['spellcheck']);
  1053. }
  1054. break;
  1055. case 'field_angle':
  1056. field = new Blockly.FieldAngle(element['angle']);
  1057. break;
  1058. case 'field_checkbox':
  1059. field = new Blockly.FieldCheckbox(
  1060. element['checked'] ? 'TRUE' : 'FALSE');
  1061. break;
  1062. case 'field_colour':
  1063. field = new Blockly.FieldColour(element['colour']);
  1064. break;
  1065. case 'field_variable':
  1066. field = new Blockly.FieldVariable(element['variable']);
  1067. break;
  1068. case 'field_dropdown':
  1069. field = new Blockly.FieldDropdown(element['options']);
  1070. break;
  1071. case 'field_image':
  1072. field = new Blockly.FieldImage(element['src'],
  1073. element['width'], element['height'], element['alt']);
  1074. break;
  1075. case 'field_number':
  1076. field = new Blockly.FieldNumber(element['value'],
  1077. element['min'], element['max'], element['precision']);
  1078. break;
  1079. case 'field_date':
  1080. if (Blockly.FieldDate) {
  1081. field = new Blockly.FieldDate(element['date']);
  1082. break;
  1083. }
  1084. // Fall through if FieldDate is not compiled in.
  1085. default:
  1086. // Unknown field.
  1087. if (element['alt']) {
  1088. element = element['alt'];
  1089. altRepeat = true;
  1090. }
  1091. }
  1092. }
  1093. } while (altRepeat);
  1094. if (field) {
  1095. fieldStack.push([field, element['name']]);
  1096. } else if (input) {
  1097. if (element['check']) {
  1098. input.setCheck(element['check']);
  1099. }
  1100. if (element['align']) {
  1101. input.setAlign(alignmentLookup[element['align']]);
  1102. }
  1103. for (var j = 0; j < fieldStack.length; j++) {
  1104. input.appendField(fieldStack[j][0], fieldStack[j][1]);
  1105. }
  1106. fieldStack.length = 0;
  1107. }
  1108. }
  1109. }
  1110. };
  1111. /**
  1112. * Add a value input, statement input or local variable to this block.
  1113. * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or
  1114. * Blockly.DUMMY_INPUT.
  1115. * @param {string} name Language-neutral identifier which may used to find this
  1116. * input again. Should be unique to this block.
  1117. * @return {!Blockly.Input} The input object created.
  1118. * @private
  1119. */
  1120. Blockly.Block.prototype.appendInput_ = function(type, name) {
  1121. var connection = null;
  1122. if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT) {
  1123. connection = this.makeConnection_(type);
  1124. }
  1125. var input = new Blockly.Input(type, name, this, connection);
  1126. // Append input to list.
  1127. this.inputList.push(input);
  1128. return input;
  1129. };
  1130. /**
  1131. * Move a named input to a different location on this block.
  1132. * @param {string} name The name of the input to move.
  1133. * @param {?string} refName Name of input that should be after the moved input,
  1134. * or null to be the input at the end.
  1135. */
  1136. Blockly.Block.prototype.moveInputBefore = function(name, refName) {
  1137. if (name == refName) {
  1138. return;
  1139. }
  1140. // Find both inputs.
  1141. var inputIndex = -1;
  1142. var refIndex = refName ? -1 : this.inputList.length;
  1143. for (var i = 0, input; input = this.inputList[i]; i++) {
  1144. if (input.name == name) {
  1145. inputIndex = i;
  1146. if (refIndex != -1) {
  1147. break;
  1148. }
  1149. } else if (refName && input.name == refName) {
  1150. refIndex = i;
  1151. if (inputIndex != -1) {
  1152. break;
  1153. }
  1154. }
  1155. }
  1156. goog.asserts.assert(inputIndex != -1, 'Named input "%s" not found.', name);
  1157. goog.asserts.assert(refIndex != -1, 'Reference input "%s" not found.',
  1158. refName);
  1159. this.moveNumberedInputBefore(inputIndex, refIndex);
  1160. };
  1161. /**
  1162. * Move a numbered input to a different location on this block.
  1163. * @param {number} inputIndex Index of the input to move.
  1164. * @param {number} refIndex Index of input that should be after the moved input.
  1165. */
  1166. Blockly.Block.prototype.moveNumberedInputBefore = function(
  1167. inputIndex, refIndex) {
  1168. // Validate arguments.
  1169. goog.asserts.assert(inputIndex != refIndex, 'Can\'t move input to itself.');
  1170. goog.asserts.assert(inputIndex < this.inputList.length,
  1171. 'Input index ' + inputIndex + ' out of bounds.');
  1172. goog.asserts.assert(refIndex <= this.inputList.length,
  1173. 'Reference input ' + refIndex + ' out of bounds.');
  1174. // Remove input.
  1175. var input = this.inputList[inputIndex];
  1176. this.inputList.splice(inputIndex, 1);
  1177. if (inputIndex < refIndex) {
  1178. refIndex--;
  1179. }
  1180. // Reinsert input.
  1181. this.inputList.splice(refIndex, 0, input);
  1182. };
  1183. /**
  1184. * Remove an input from this block.
  1185. * @param {string} name The name of the input.
  1186. * @param {boolean=} opt_quiet True to prevent error if input is not present.
  1187. * @throws {goog.asserts.AssertionError} if the input is not present and
  1188. * opt_quiet is not true.
  1189. */
  1190. Blockly.Block.prototype.removeInput = function(name, opt_quiet) {
  1191. for (var i = 0, input; input = this.inputList[i]; i++) {
  1192. if (input.name == name) {
  1193. if (input.connection && input.connection.isConnected()) {
  1194. input.connection.setShadowDom(null);
  1195. var block = input.connection.targetBlock();
  1196. if (block.isShadow()) {
  1197. // Destroy any attached shadow block.
  1198. block.dispose();
  1199. } else {
  1200. // Disconnect any attached normal block.
  1201. block.unplug();
  1202. }
  1203. }
  1204. input.dispose();
  1205. this.inputList.splice(i, 1);
  1206. return;
  1207. }
  1208. }
  1209. if (!opt_quiet) {
  1210. goog.asserts.fail('Input "%s" not found.', name);
  1211. }
  1212. };
  1213. /**
  1214. * Fetches the named input object.
  1215. * @param {string} name The name of the input.
  1216. * @return {Blockly.Input} The input object, or null if input does not exist.
  1217. */
  1218. Blockly.Block.prototype.getInput = function(name) {
  1219. for (var i = 0, input; input = this.inputList[i]; i++) {
  1220. if (input.name == name) {
  1221. return input;
  1222. }
  1223. }
  1224. // This input does not exist.
  1225. return null;
  1226. };
  1227. /**
  1228. * Fetches the block attached to the named input.
  1229. * @param {string} name The name of the input.
  1230. * @return {Blockly.Block} The attached value block, or null if the input is
  1231. * either disconnected or if the input does not exist.
  1232. */
  1233. Blockly.Block.prototype.getInputTargetBlock = function(name) {
  1234. var input = this.getInput(name);
  1235. return input && input.connection && input.connection.targetBlock();
  1236. };
  1237. /**
  1238. * Returns the comment on this block (or '' if none).
  1239. * @return {string} Block's comment.
  1240. */
  1241. Blockly.Block.prototype.getCommentText = function() {
  1242. return this.comment || '';
  1243. };
  1244. /**
  1245. * Set this block's comment text.
  1246. * @param {?string} text The text, or null to delete.
  1247. */
  1248. Blockly.Block.prototype.setCommentText = function(text) {
  1249. if (this.comment != text) {
  1250. Blockly.Events.fire(new Blockly.Events.Change(
  1251. this, 'comment', null, this.comment, text || ''));
  1252. this.comment = text;
  1253. }
  1254. };
  1255. /**
  1256. * Set this block's warning text.
  1257. * @param {?string} text The text, or null to delete.
  1258. */
  1259. Blockly.Block.prototype.setWarningText = function(text) {
  1260. // NOP.
  1261. };
  1262. /**
  1263. * Give this block a mutator dialog.
  1264. * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
  1265. */
  1266. Blockly.Block.prototype.setMutator = function(mutator) {
  1267. // NOP.
  1268. };
  1269. /**
  1270. * Give this block a mutatorPlus button for BlocksCAD.
  1271. * @param {Blockly.MutatorPlus}
  1272. * mutatorPlus A mutatorPlus instance or null to remove.
  1273. */
  1274. Blockly.Block.prototype.setMutatorPlus = function(mutatorPlus) {
  1275. // NOP
  1276. };
  1277. /**
  1278. * Give this block a mutatorMinus button for BlocksCAD.
  1279. * @param {Blockly.MutatorMinus}
  1280. * mutatorMinus A mutatorMinus instance or null to remove.
  1281. */
  1282. Blockly.Block.prototype.setMutatorMinus = function(mutatorMinus) {
  1283. // NOP.
  1284. };
  1285. /**
  1286. * Return the coordinates of the top-left corner of this block relative to the
  1287. * drawing surface's origin (0,0).
  1288. * @return {!goog.math.Coordinate} Object with .x and .y properties.
  1289. */
  1290. Blockly.Block.prototype.getRelativeToSurfaceXY = function() {
  1291. return this.xy_;
  1292. };
  1293. /**
  1294. * Move a block by a relative offset.
  1295. * @param {number} dx Horizontal offset.
  1296. * @param {number} dy Vertical offset.
  1297. */
  1298. Blockly.Block.prototype.moveBy = function(dx, dy) {
  1299. goog.asserts.assert(!this.parentBlock_, 'Block has parent.');
  1300. var event = new Blockly.Events.Move(this);
  1301. this.xy_.translate(dx, dy);
  1302. event.recordNew();
  1303. Blockly.Events.fire(event);
  1304. };
  1305. /**
  1306. * Create a connection of the specified type.
  1307. * @param {number} type The type of the connection to create.
  1308. * @return {!Blockly.Connection} A new connection of the specified type.
  1309. * @private
  1310. */
  1311. Blockly.Block.prototype.makeConnection_ = function(type) {
  1312. return new Blockly.Connection(this, type);
  1313. };
  1314. /**
  1315. * For BlocksCAD
  1316. * return a list of all collapsed parents of a block
  1317. */
  1318. Blockly.Block.prototype.collapsedParents = function() {
  1319. var block = this;
  1320. var collapsedParents = [];
  1321. do {
  1322. if (block.isCollapsed())
  1323. collapsedParents.push(block);
  1324. block = block.parentBlock_;
  1325. } while (block);
  1326. if (collapsedParents.length == 0) return false;
  1327. return collapsedParents;
  1328. }
  1329. /**
  1330. * For BlocksCAD
  1331. * true if any parent of this block is disabled
  1332. */
  1333. Blockly.Block.prototype.hasDisabledParent = function() {
  1334. var block = this;
  1335. do {
  1336. if (block.disabled) return true;
  1337. block = block.parentBlock_;
  1338. } while (block);
  1339. return false;
  1340. }
  1341. /* BlocksCAD still uses interpolate message, so KEEP IT!!!
  1342. /**
  1343. * Interpolate a message string, creating fields and inputs.
  1344. * @param {string} msg The message string to parse. %1, %2, etc. are symbols
  1345. * for value inputs or for Fields, such as an instance of
  1346. * Blockly.FieldDropdown, which would be placed as a field in either the
  1347. * following value input or a dummy input. The newline character forces
  1348. * the creation of an unnamed dummy input if any fields need placement.
  1349. * Note that '%10' would be interpreted as a reference to the tenth
  1350. * argument. To show the first argument followed by a zero, use '%1 0'.
  1351. * (Spaces around tokens are stripped.) To display a percentage sign
  1352. * followed by a number (e.g., "%123"), put that text in a
  1353. * Blockly.FieldLabel (as described below).
  1354. * @param {!Array.<?string|number|Array.<string>|Blockly.Field>|number} var_args
  1355. * A series of tuples that each specify the value inputs to create. Each
  1356. * tuple has at least two elements. The first is its name; the second is
  1357. * its type, which can be any of:
  1358. * - A string (such as 'Number'), denoting the one type allowed in the
  1359. * corresponding socket.
  1360. * - An array of strings (such as ['Number', 'List']), denoting the
  1361. * different types allowed in the corresponding socket.
  1362. * - null, denoting that any type is allowed in the corresponding socket.
  1363. * - Blockly.Field, in which case that field instance, such as an
  1364. * instance of Blockly.FieldDropdown, appears (instead of a socket).
  1365. * If the type is any of the first three options (which are legal arguments
  1366. * to setCheck()), there should be a third element in the tuple, giving its
  1367. * alignment.
  1368. * The final parameter is not a tuple, but just an alignment for any
  1369. * trailing dummy inputs. This last parameter is mandatory; there may be
  1370. * any number of tuples (though the number of tuples must match the symbols
  1371. * in msg).
  1372. */
  1373. Blockly.Block.prototype.interpolateMsg = function(msg, var_args) {
  1374. /**
  1375. * Add a field to this input.
  1376. * @this !Blockly.Input
  1377. * @param {Blockly.Field|Array.<string|Blockly.Field>} field
  1378. * This is either a Field or a tuple of a name and a Field.
  1379. */
  1380. function addFieldToInput(field) {
  1381. if (field instanceof Blockly.Field) {
  1382. this.appendField(field);
  1383. } else {
  1384. goog.asserts.assert(goog.isArray(field));
  1385. this.appendField(field[1], field[0]);
  1386. }
  1387. }
  1388. // Validate the msg at the start and the dummy alignment at the end,
  1389. // and remove the latter.
  1390. // console.log("in interpolate: here is msg:",msg);
  1391. goog.asserts.assertString(msg);
  1392. var dummyAlign = arguments[arguments.length - 1];
  1393. goog.asserts.assert(
  1394. dummyAlign === Blockly.ALIGN_LEFT ||
  1395. dummyAlign === Blockly.ALIGN_CENTRE ||
  1396. dummyAlign === Blockly.ALIGN_RIGHT,
  1397. 'Illegal final argument "%d" is not an alignment.', dummyAlign);
  1398. arguments.length = arguments.length - 1;
  1399. var tokens = msg.split(this.interpolateMsg.SPLIT_REGEX_);
  1400. var fields = [];
  1401. for (var i = 0; i < tokens.length; i += 2) {
  1402. var text = goog.string.trim(tokens[i]);
  1403. var input = undefined;
  1404. if (text) {
  1405. fields.push(new Blockly.FieldLabel(text));
  1406. }
  1407. var symbol = tokens[i + 1];
  1408. if (symbol && symbol.charAt(0) == '%') {
  1409. // Numeric field.
  1410. var number = parseInt(symbol.substring(1), 10);
  1411. var tuple = arguments[number];
  1412. goog.asserts.assertArray(tuple,
  1413. 'Message symbol "%s" is out of range.', symbol);
  1414. goog.asserts.assertArray(tuple,
  1415. 'Argument "%s" is not a tuple.', symbol);
  1416. if (tuple[1] instanceof Blockly.Field) {
  1417. fields.push([tuple[0], tuple[1]]);
  1418. } else {
  1419. input = this.appendValueInput(tuple[0])
  1420. .setCheck(tuple[1])
  1421. .setAlign(tuple[2]);
  1422. }
  1423. arguments[number] = null; // Inputs may not be reused.
  1424. } else if (symbol == '\n' && fields.length) {
  1425. // Create a dummy input.
  1426. input = this.appendDummyInput();
  1427. }
  1428. // If we just added an input, hang any pending fields on it.
  1429. if (input && fields.length) {
  1430. fields.forEach(addFieldToInput, input);
  1431. fields = [];
  1432. }
  1433. }
  1434. // If any fields remain, create a trailing dummy input.
  1435. if (fields.length) {
  1436. var input = this.appendDummyInput()
  1437. .setAlign(dummyAlign);
  1438. fields.forEach(addFieldToInput, input);
  1439. }
  1440. // Verify that all inputs were used.
  1441. for (var i = 1; i < arguments.length - 1; i++) {
  1442. goog.asserts.assert(arguments[i] === null,
  1443. 'Input "%%s" not used in message: "%s"', i, msg);
  1444. }
  1445. // Make the inputs inline unless there is only one input and
  1446. // no text follows it.
  1447. this.setInputsInline(!msg.match(this.interpolateMsg.INLINE_REGEX_));
  1448. };
  1449. Blockly.Block.prototype.interpolateMsg.SPLIT_REGEX_ = /(%\d+|\n)/;
  1450. Blockly.Block.prototype.interpolateMsg.INLINE_REGEX_ = /%1\s*$/;