block_svg.js 58 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845
  1. /**
  2. * @license
  3. * Visual Blocks Editor
  4. *
  5. * Copyright 2012 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 Methods for graphically rendering a block as SVG.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.BlockSvg');
  26. goog.require('Blockly.Block');
  27. goog.require('Blockly.ContextMenu');
  28. goog.require('Blockly.RenderedConnection');
  29. goog.require('goog.Timer');
  30. goog.require('goog.asserts');
  31. goog.require('goog.dom');
  32. goog.require('goog.math.Coordinate');
  33. goog.require('goog.userAgent');
  34. /**
  35. * Class for a block's SVG representation.
  36. * Not normally called directly, workspace.newBlock() is preferred.
  37. * @param {!Blockly.Workspace} workspace The block's workspace.
  38. * @param {?string} prototypeName Name of the language object containing
  39. * type-specific functions for this block.
  40. * @param {=string} opt_id Optional ID. Use this ID if provided, otherwise
  41. * create a new id.
  42. * @extends {Blockly.Block}
  43. * @constructor
  44. */
  45. Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
  46. // Create core elements for the block.
  47. /**
  48. * @type {SVGElement}
  49. * @private
  50. */
  51. this.svgGroup_ = Blockly.createSvgElement('g', {}, null);
  52. /**
  53. * @type {SVGElement}
  54. * @private
  55. */
  56. this.svgPathDark_ = Blockly.createSvgElement('path',
  57. {'class': 'blocklyPathDark', 'transform': 'translate(1,1)'},
  58. this.svgGroup_);
  59. /**
  60. * @type {SVGElement}
  61. * @private
  62. */
  63. this.svgPath_ = Blockly.createSvgElement('path', {'class': 'blocklyPath'},
  64. this.svgGroup_);
  65. /**
  66. * @type {SVGElement}
  67. * @private
  68. */
  69. this.svgPathLight_ = Blockly.createSvgElement('path',
  70. {'class': 'blocklyPathLight'}, this.svgGroup_);
  71. this.svgPath_.tooltip = this;
  72. /** @type {boolean} */
  73. this.rendered = false;
  74. Blockly.Tooltip.bindMouseEvents(this.svgPath_);
  75. Blockly.BlockSvg.superClass_.constructor.call(this,
  76. workspace, prototypeName, opt_id);
  77. };
  78. goog.inherits(Blockly.BlockSvg, Blockly.Block);
  79. /**
  80. * Height of this block, not including any statement blocks above or below.
  81. */
  82. Blockly.BlockSvg.prototype.height = 0;
  83. /**
  84. * Width of this block, including any connected value blocks.
  85. */
  86. Blockly.BlockSvg.prototype.width = 0;
  87. /**
  88. * Original location of block being dragged.
  89. * @type {goog.math.Coordinate}
  90. * @private
  91. */
  92. Blockly.BlockSvg.prototype.dragStartXY_ = null;
  93. /**
  94. * Constant for identifying rows that are to be rendered inline.
  95. * Don't collide with Blockly.INPUT_VALUE and friends.
  96. * @const
  97. */
  98. Blockly.BlockSvg.INLINE = -1;
  99. /**
  100. * Create and initialize the SVG representation of the block.
  101. * May be called more than once.
  102. */
  103. Blockly.BlockSvg.prototype.initSvg = function() {
  104. goog.asserts.assert(this.workspace.rendered, 'Workspace is headless.');
  105. for (var i = 0, input; input = this.inputList[i]; i++) {
  106. input.init();
  107. }
  108. var icons = this.getIcons();
  109. for (var i = 0; i < icons.length; i++) {
  110. icons[i].createIcon();
  111. }
  112. // not sure if we need this after the update - JY
  113. // if (this.mutator) {
  114. // this.mutator.createIcon();
  115. // }
  116. // for BlocksCAD
  117. if (this.mutatorPlus) {
  118. this.mutatorPlus.createIcon();
  119. }
  120. if (this.mutatorMinus) {
  121. this.mutatorMinus.createIcon();
  122. }
  123. this.updateColour();
  124. this.updateMovable();
  125. if (!this.workspace.options.readOnly && !this.eventsInit_) {
  126. Blockly.bindEvent_(this.getSvgRoot(), 'mousedown', this,
  127. this.onMouseDown_);
  128. var thisBlock = this;
  129. Blockly.bindEvent_(this.getSvgRoot(), 'touchstart', null,
  130. function(e) {Blockly.longStart_(e, thisBlock);});
  131. }
  132. this.eventsInit_ = true;
  133. if (!this.getSvgRoot().parentNode) {
  134. this.workspace.getCanvas().appendChild(this.getSvgRoot());
  135. }
  136. };
  137. /**
  138. * Select this block. Highlight it visually.
  139. */
  140. Blockly.BlockSvg.prototype.select = function() {
  141. if (this.isShadow() && this.getParent()) {
  142. // Shadow blocks should not be selected.
  143. this.getParent().select();
  144. return;
  145. }
  146. if (Blockly.selected == this) {
  147. return;
  148. }
  149. var oldId = null;
  150. if (Blockly.selected) {
  151. oldId = Blockly.selected.id;
  152. // Unselect any previously selected block.
  153. Blockly.Events.disable();
  154. try {
  155. Blockly.selected.unselect();
  156. } finally {
  157. Blockly.Events.enable();
  158. }
  159. }
  160. var event = new Blockly.Events.Ui(null, 'selected', oldId, this.id);
  161. event.workspaceId = this.workspace.id;
  162. Blockly.Events.fire(event);
  163. Blockly.selected = this;
  164. this.addSelect();
  165. // for BlocksCAD - I want to turn off backlighting when the user selects the block
  166. // console.log("in select: turning off any backlighting");
  167. this.unbacklight();
  168. };
  169. /**
  170. * Unselect this block. Remove its highlighting.
  171. */
  172. Blockly.BlockSvg.prototype.unselect = function() {
  173. if (Blockly.selected != this) {
  174. return;
  175. }
  176. var event = new Blockly.Events.Ui(null, 'selected', this.id, null);
  177. event.workspaceId = this.workspace.id;
  178. Blockly.Events.fire(event);
  179. Blockly.selected = null;
  180. this.removeSelect();
  181. };
  182. /**
  183. * Backlight this block. Highlight it visually. Added for BlocksCAD.
  184. */
  185. Blockly.BlockSvg.prototype.backlight = function() {
  186. var found_it = 0;
  187. for(var i = Blockly.backlight.length; i--;) {
  188. if(Blockly.backlight[i] == this.id) {
  189. found_it = 1;
  190. break;
  191. }
  192. }
  193. if (!found_it && this) {
  194. // Add this block to the list of backlight blocks
  195. Blockly.backlight.push(this.id);
  196. }
  197. this.addBacklight();
  198. };
  199. /**
  200. * Remove backlighting from this block. Added for BlocksCAD.
  201. * Fire a change event so procedures can turn their warning messages off.
  202. */
  203. Blockly.BlockSvg.prototype.unbacklight = function() {
  204. var found_it = 0;
  205. for(var i = Blockly.backlight.length; i--;) {
  206. if(Blockly.backlight[i] == this.id) {
  207. Blockly.backlight.splice(i, 1);
  208. found_it = 1;
  209. }
  210. }
  211. if (found_it && this) {
  212. this.removeBacklight();
  213. // Take this id off the internal backlight list, id applicable
  214. // console.log("in unbacklight with:",this);
  215. // need to get the setter block to clear the block off of the list there, too.
  216. if (this.type == 'varibles_get') {
  217. var all_of_them = Blockly.Variables.getInstances(this.getFieldValue('VAR'), this.workspace);
  218. var setters = [];
  219. for (var i = 0; i < all_of_them.length; i++) {
  220. if (all_of_them[i].type == 'variables_set')
  221. setters.push(all_of_them[i]);
  222. }
  223. for (var i = 0; i < setters.length; i++) {
  224. for (var j = 0; j < setters[i].backlightBlocks.length; j++)
  225. if (setters[i].backlightBlocks[j] == this.id) {
  226. setters[i].backlightBlocks.splice(j,1);
  227. if (setters[i].backlightBlocks.length == 0)
  228. setters[i].setWarningText(null);
  229. }
  230. }
  231. }
  232. else if (this.type == 'procedures_callnoreturn' || this.type == 'procedures_callreturn') {
  233. var defBlock = Blockly.Procedures.getDefinition(this.getProcedureCall(),
  234. this.workspace);
  235. for (var i = 0; i < defBlock.backlightBlocks.length; i++) {
  236. if (defBlock.backlightBlocks[i] == this.id) {
  237. defBlock.backlightBlocks.splice(i,1);
  238. if (defBlock.backlightBlocks.length == 0)
  239. defBlock.setWarningText(null);
  240. }
  241. }
  242. }
  243. }
  244. }
  245. /**
  246. * Block's mutator icon (if any).
  247. * @type {Blockly.Mutator}
  248. */
  249. Blockly.BlockSvg.prototype.mutator = null;
  250. /**
  251. * Block's comment icon (if any).
  252. * @type {Blockly.Comment}
  253. */
  254. Blockly.BlockSvg.prototype.comment = null;
  255. /**
  256. * Block's warning icon (if any).
  257. * @type {Blockly.Warning}
  258. */
  259. Blockly.BlockSvg.prototype.warning = null;
  260. /**
  261. * Returns a list of mutator, comment, and warning icons.
  262. * @return {!Array} List of icons.
  263. */
  264. Blockly.BlockSvg.prototype.getIcons = function() {
  265. var icons = [];
  266. if (this.mutator) {
  267. icons.push(this.mutator);
  268. }
  269. // add mutator plus and minus icons for BlocksCAD
  270. if (this.mutatorPlus) {
  271. icons.push(this.mutatorPlus);
  272. }
  273. if (this.mutatorMinus) {
  274. icons.push(this.mutatorMinus);
  275. }
  276. if (this.comment) {
  277. icons.push(this.comment);
  278. }
  279. if (this.warning) {
  280. icons.push(this.warning);
  281. }
  282. return icons;
  283. };
  284. /**
  285. * Wrapper function called when a mouseUp occurs during a drag operation.
  286. * @type {Array.<!Array>}
  287. * @private
  288. */
  289. Blockly.BlockSvg.onMouseUpWrapper_ = null;
  290. /**
  291. * Wrapper function called when a mouseMove occurs during a drag operation.
  292. * @type {Array.<!Array>}
  293. * @private
  294. */
  295. Blockly.BlockSvg.onMouseMoveWrapper_ = null;
  296. /**
  297. * Stop binding to the global mouseup and mousemove events.
  298. * @package
  299. */
  300. Blockly.BlockSvg.terminateDrag = function() {
  301. Blockly.BlockSvg.disconnectUiStop_();
  302. if (Blockly.BlockSvg.onMouseUpWrapper_) {
  303. Blockly.unbindEvent_(Blockly.BlockSvg.onMouseUpWrapper_);
  304. Blockly.BlockSvg.onMouseUpWrapper_ = null;
  305. }
  306. if (Blockly.BlockSvg.onMouseMoveWrapper_) {
  307. Blockly.unbindEvent_(Blockly.BlockSvg.onMouseMoveWrapper_);
  308. Blockly.BlockSvg.onMouseMoveWrapper_ = null;
  309. }
  310. var selected = Blockly.selected;
  311. if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
  312. // Terminate a drag operation.
  313. if (selected) {
  314. // Update the connection locations.
  315. var xy = selected.getRelativeToSurfaceXY();
  316. var dxy = goog.math.Coordinate.difference(xy, selected.dragStartXY_);
  317. var event = new Blockly.Events.Move(selected);
  318. event.oldCoordinate = selected.dragStartXY_;
  319. event.recordNew();
  320. Blockly.Events.fire(event);
  321. selected.moveConnections_(dxy.x, dxy.y);
  322. delete selected.draggedBubbles_;
  323. selected.setDragging_(false);
  324. selected.render();
  325. // Ensure that any stap and bump are part of this move's event group.
  326. var group = Blockly.Events.getGroup();
  327. setTimeout(function() {
  328. Blockly.Events.setGroup(group);
  329. selected.snapToGrid();
  330. Blockly.Events.setGroup(false);
  331. }, Blockly.BUMP_DELAY / 2);
  332. setTimeout(function() {
  333. Blockly.Events.setGroup(group);
  334. selected.bumpNeighbours_();
  335. Blockly.Events.setGroup(false);
  336. }, Blockly.BUMP_DELAY);
  337. // Fire an event to allow scrollbars to resize.
  338. Blockly.resizeSvgContents(selected.workspace);
  339. }
  340. }
  341. Blockly.dragMode_ = Blockly.DRAG_NONE;
  342. Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
  343. };
  344. /**
  345. * Set parent of this block to be a new block or null.
  346. * @param {Blockly.BlockSvg} newParent New parent block.
  347. */
  348. Blockly.BlockSvg.prototype.setParent = function(newParent) {
  349. if (newParent == this.parentBlock_) {
  350. return;
  351. }
  352. var svgRoot = this.getSvgRoot();
  353. if (this.parentBlock_ && svgRoot) {
  354. // Move this block up the DOM. Keep track of x/y translations.
  355. var xy = this.getRelativeToSurfaceXY();
  356. this.workspace.getCanvas().appendChild(svgRoot);
  357. svgRoot.setAttribute('transform', 'translate(' + xy.x + ',' + xy.y + ')');
  358. }
  359. Blockly.Field.startCache();
  360. Blockly.BlockSvg.superClass_.setParent.call(this, newParent);
  361. Blockly.Field.stopCache();
  362. if (newParent) {
  363. var oldXY = this.getRelativeToSurfaceXY();
  364. newParent.getSvgRoot().appendChild(svgRoot);
  365. var newXY = this.getRelativeToSurfaceXY();
  366. // Move the connections to match the child's new position.
  367. this.moveConnections_(newXY.x - oldXY.x, newXY.y - oldXY.y);
  368. }
  369. };
  370. /**
  371. * Return the coordinates of the top-left corner of this block relative to the
  372. * drawing surface's origin (0,0).
  373. * @return {!goog.math.Coordinate} Object with .x and .y properties.
  374. */
  375. Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
  376. var x = 0;
  377. var y = 0;
  378. var element = this.getSvgRoot();
  379. if (element) {
  380. do {
  381. // Loop through this block and every parent.
  382. var xy = Blockly.getRelativeXY_(element);
  383. x += xy.x;
  384. y += xy.y;
  385. element = element.parentNode;
  386. } while (element && element != this.workspace.getCanvas());
  387. }
  388. return new goog.math.Coordinate(x, y);
  389. };
  390. /**
  391. * Move a block by a relative offset.
  392. * @param {number} dx Horizontal offset.
  393. * @param {number} dy Vertical offset.
  394. */
  395. Blockly.BlockSvg.prototype.moveBy = function(dx, dy) {
  396. goog.asserts.assert(!this.parentBlock_, 'Block has parent.');
  397. var event = new Blockly.Events.Move(this);
  398. var xy = this.getRelativeToSurfaceXY();
  399. this.getSvgRoot().setAttribute('transform',
  400. 'translate(' + (xy.x + dx) + ',' + (xy.y + dy) + ')');
  401. this.moveConnections_(dx, dy);
  402. event.recordNew();
  403. Blockly.resizeSvgContents(this.workspace);
  404. Blockly.Events.fire(event);
  405. };
  406. /**
  407. * Snap this block to the nearest grid point.
  408. */
  409. Blockly.BlockSvg.prototype.snapToGrid = function() {
  410. if (!this.workspace) {
  411. return; // Deleted block.
  412. }
  413. if (Blockly.dragMode_ != Blockly.DRAG_NONE) {
  414. return; // Don't bump blocks during a drag.
  415. }
  416. if (this.getParent()) {
  417. return; // Only snap top-level blocks.
  418. }
  419. if (this.isInFlyout) {
  420. return; // Don't move blocks around in a flyout.
  421. }
  422. if (!this.workspace.options.gridOptions ||
  423. !this.workspace.options.gridOptions['snap']) {
  424. return; // Config says no snapping.
  425. }
  426. var spacing = this.workspace.options.gridOptions['spacing'];
  427. var half = spacing / 2;
  428. var xy = this.getRelativeToSurfaceXY();
  429. var dx = Math.round((xy.x - half) / spacing) * spacing + half - xy.x;
  430. var dy = Math.round((xy.y - half) / spacing) * spacing + half - xy.y;
  431. dx = Math.round(dx);
  432. dy = Math.round(dy);
  433. if (dx != 0 || dy != 0) {
  434. this.moveBy(dx, dy);
  435. }
  436. };
  437. /**
  438. * Returns a bounding box describing the dimensions of this block
  439. * and any blocks stacked below it.
  440. * @return {!{height: number, width: number}} Object with height and width
  441. * properties.
  442. */
  443. Blockly.BlockSvg.prototype.getHeightWidth = function() {
  444. var height = this.height;
  445. var width = this.width;
  446. // Recursively add size of subsequent blocks.
  447. var nextBlock = this.getNextBlock();
  448. if (nextBlock) {
  449. var nextHeightWidth = nextBlock.getHeightWidth();
  450. height += nextHeightWidth.height - 4; // Height of tab.
  451. width = Math.max(width, nextHeightWidth.width);
  452. } else if (!this.nextConnection && !this.outputConnection) {
  453. // Add a bit of margin under blocks with no bottom tab.
  454. height += 2;
  455. }
  456. return {height: height, width: width};
  457. };
  458. /**
  459. * Returns the coordinates of a bounding box describing the dimensions of this
  460. * block and any blocks stacked below it.
  461. * @return {!{topLeft: goog.math.Coordinate, bottomRight: goog.math.Coordinate}}
  462. * Object with top left and bottom right coordinates of the bounding box.
  463. */
  464. Blockly.BlockSvg.prototype.getBoundingRectangle = function() {
  465. var blockXY = this.getRelativeToSurfaceXY(this);
  466. var tab = this.outputConnection ? Blockly.BlockSvg.TAB_WIDTH : 0;
  467. var blockBounds = this.getHeightWidth();
  468. var topLeft;
  469. var bottomRight;
  470. if (this.RTL) {
  471. // Width has the tab built into it already so subtract it here.
  472. topLeft = new goog.math.Coordinate(blockXY.x - (blockBounds.width - tab),
  473. blockXY.y);
  474. // Add the width of the tab/puzzle piece knob to the x coordinate
  475. // since X is the corner of the rectangle, not the whole puzzle piece.
  476. bottomRight = new goog.math.Coordinate(blockXY.x + tab,
  477. blockXY.y + blockBounds.height);
  478. } else {
  479. // Subtract the width of the tab/puzzle piece knob to the x coordinate
  480. // since X is the corner of the rectangle, not the whole puzzle piece.
  481. topLeft = new goog.math.Coordinate(blockXY.x - tab, blockXY.y);
  482. // Width has the tab built into it already so subtract it here.
  483. bottomRight = new goog.math.Coordinate(blockXY.x + blockBounds.width - tab,
  484. blockXY.y + blockBounds.height);
  485. }
  486. return {topLeft: topLeft, bottomRight: bottomRight};
  487. };
  488. /**
  489. * Set whether the block is collapsed or not.
  490. * @param {boolean} collapsed True if collapsed.
  491. */
  492. // Added the force parameter so that BlocksCAD can force an updated comment
  493. // to change the text on a collapsed block. This is used in STL import.
  494. Blockly.BlockSvg.prototype.setCollapsed = function(collapsed,force) {
  495. if (this.collapsed_ == collapsed && !force) {
  496. return;
  497. }
  498. var renderList = [];
  499. // Show/hide the inputs.
  500. for (var i = 0, input; input = this.inputList[i]; i++) {
  501. renderList.push.apply(renderList, input.setVisible(!collapsed));
  502. }
  503. var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
  504. if (collapsed) {
  505. var icons = this.getIcons();
  506. for (var x = 0; x < icons.length; x++) {
  507. icons[x].setVisible(false);
  508. }
  509. // when collapsing the block, display the first 27 characters
  510. // of the top block comment (if any), then if there is room
  511. // display a few characters of the normal block string
  512. // representation - jayod
  513. // Actually, I'd like to get the comment off of the first block to have one,
  514. // even if it isn't the top block. I'll write a little function to do this.
  515. var text = '';
  516. var comm = getTopComment(this); // any comments or procedure calls here?
  517. if (comm.length > Blockly.COLLAPSE_CHARS - 3) {
  518. comm = comm.substring(0,Blockly.COLLAPSE_CHARS - 3)+"...";
  519. text = comm;
  520. } else {
  521. if (!force) text = comm;
  522. if (Blockly.COLLAPSE_CHARS - comm.length > 8) {
  523. if (comm.length > 0) {
  524. if (!force) text += " - ";
  525. }
  526. text += this.toString(Blockly.COLLAPSE_CHARS - comm.length);
  527. // console.log('in collapse. this.toString is:',this.toString(Blockly.COLLAPSE_CHARS));
  528. }
  529. }
  530. if (!force) this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text,'COLLAPSE_TEXT').init();
  531. else {
  532. // remove the collapse_text field, and append a new one
  533. var inp = this.getInput(COLLAPSED_INPUT_NAME);
  534. inp.setVisible(true);
  535. inp.removeField('COLLAPSE_TEXT');
  536. inp.appendField(text,'COLLAPSE_TEXT').init();
  537. }
  538. } else {
  539. this.removeInput(COLLAPSED_INPUT_NAME);
  540. // Clear any warnings inherited from enclosed blocks.
  541. this.setWarningText(null);
  542. }
  543. Blockly.BlockSvg.superClass_.setCollapsed.call(this, collapsed);
  544. if (!renderList.length) {
  545. // No child blocks, just render this block.
  546. renderList[0] = this;
  547. }
  548. if (this.rendered) {
  549. for (var i = 0, block; block = renderList[i]; i++) {
  550. block.render();
  551. }
  552. // Don't bump neighbours.
  553. // Although bumping neighbours would make sense, users often collapse
  554. // all their functions and store them next to each other. Expanding and
  555. // bumping causes all their definitions to go out of alignment.
  556. }
  557. // BLOCKSCAD - jayod - I need to fix typed blocks after they are expanded.
  558. if (!collapsed) {
  559. Blockscad.assignBlockTypes([this]);
  560. }
  561. // END BLOCKSCAD - jayod
  562. // this.workspace.fireChangeEvent();
  563. };
  564. function getTopComment(block) { // added for BlocksCAD
  565. if (block.category && block.category != 'PROCEDURE') {
  566. var comm = '';
  567. var blockStack = block.getDescendants();
  568. for (var i = 0; i < blockStack.length; i++) {
  569. comm = blockStack[i].getCommentText();
  570. if (comm.length > 0) {
  571. return comm;
  572. }
  573. }
  574. // there was no comment. Were there any procedure calls?
  575. for (i = 1; i < blockStack.length; i++) {
  576. if (blockStack[i].type.lastIndexOf('procedures_call') != -1) {
  577. comm = blockStack[i].inputList[0].fieldRow[0].getText();
  578. return comm;
  579. }
  580. }
  581. }
  582. return '';
  583. } // end getTopComment - added for BlocksCAD
  584. /**
  585. * Open the next (or previous) FieldTextInput.
  586. * @param {Blockly.Field|Blockly.Block} start Current location.
  587. * @param {boolean} forward If true go forward, otherwise backward.
  588. */
  589. Blockly.BlockSvg.prototype.tab = function(start, forward) {
  590. // This function need not be efficient since it runs once on a keypress.
  591. // Create an ordered list of all text fields and connected inputs.
  592. var list = [];
  593. for (var i = 0, input; input = this.inputList[i]; i++) {
  594. for (var j = 0, field; field = input.fieldRow[j]; j++) {
  595. if (field instanceof Blockly.FieldTextInput) {
  596. // TODO: Also support dropdown fields.
  597. list.push(field);
  598. }
  599. }
  600. if (input.connection) {
  601. var block = input.connection.targetBlock();
  602. if (block) {
  603. list.push(block);
  604. }
  605. }
  606. }
  607. var i = list.indexOf(start);
  608. if (i == -1) {
  609. // No start location, start at the beginning or end.
  610. i = forward ? -1 : list.length;
  611. }
  612. var target = list[forward ? i + 1 : i - 1];
  613. if (!target) {
  614. // Ran off of list.
  615. var parent = this.getParent();
  616. if (parent) {
  617. parent.tab(this, forward);
  618. }
  619. } else if (target instanceof Blockly.Field) {
  620. target.showEditor_();
  621. } else {
  622. target.tab(null, forward);
  623. }
  624. };
  625. /**
  626. * Handle a mouse-down on an SVG block.
  627. * @param {!Event} e Mouse down event.
  628. * @private
  629. */
  630. Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
  631. if (this.workspace.options.readOnly) {
  632. return;
  633. }
  634. if (this.isInFlyout) {
  635. return;
  636. }
  637. this.workspace.markFocused();
  638. Blockly.terminateDrag_();
  639. this.select();
  640. Blockly.hideChaff();
  641. if (Blockly.isRightButton(e)) {
  642. // Right-click.
  643. this.showContextMenu_(e);
  644. } else if (!this.isMovable()) {
  645. // Allow immovable blocks to be selected and context menued, but not
  646. // dragged. Let this event bubble up to document, so the workspace may be
  647. // dragged instead.
  648. return;
  649. } else {
  650. if (!Blockly.Events.getGroup()) {
  651. Blockly.Events.setGroup(true);
  652. }
  653. // Left-click (or middle click)
  654. Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
  655. this.dragStartXY_ = this.getRelativeToSurfaceXY();
  656. this.workspace.startDrag(e, this.dragStartXY_);
  657. Blockly.dragMode_ = Blockly.DRAG_STICKY;
  658. Blockly.BlockSvg.onMouseUpWrapper_ = Blockly.bindEvent_(document,
  659. 'mouseup', this, this.onMouseUp_);
  660. Blockly.BlockSvg.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
  661. 'mousemove', this, this.onMouseMove_);
  662. // Build a list of bubbles that need to be moved and where they started.
  663. this.draggedBubbles_ = [];
  664. var descendants = this.getDescendants();
  665. for (var i = 0, descendant; descendant = descendants[i]; i++) {
  666. var icons = descendant.getIcons();
  667. for (var j = 0; j < icons.length; j++) {
  668. var data = icons[j].getIconLocation();
  669. data.bubble = icons[j];
  670. this.draggedBubbles_.push(data);
  671. }
  672. }
  673. }
  674. // This event has been handled. No need to bubble up to the document.
  675. e.stopPropagation();
  676. e.preventDefault();
  677. };
  678. /**
  679. * Handle a mouse-up anywhere in the SVG pane. Is only registered when a
  680. * block is clicked. We can't use mouseUp on the block since a fast-moving
  681. * cursor can briefly escape the block before it catches up.
  682. * @param {!Event} e Mouse up event.
  683. * @private
  684. */
  685. Blockly.BlockSvg.prototype.onMouseUp_ = function(e) {
  686. if (Blockly.dragMode_ != Blockly.DRAG_FREE &&
  687. !Blockly.WidgetDiv.isVisible()) {
  688. Blockly.Events.fire(
  689. new Blockly.Events.Ui(this, 'click', undefined, undefined));
  690. }
  691. Blockly.terminateDrag_();
  692. if (Blockly.selected && Blockly.highlightedConnection_) {
  693. // Connect two blocks together.
  694. Blockly.localConnection_.connect(Blockly.highlightedConnection_);
  695. if (this.rendered) {
  696. // Trigger a connection animation.
  697. // Determine which connection is inferior (lower in the source stack).
  698. var inferiorConnection = Blockly.localConnection_.isSuperior() ?
  699. Blockly.highlightedConnection_ : Blockly.localConnection_;
  700. inferiorConnection.getSourceBlock().connectionUiEffect();
  701. }
  702. if (this.workspace.trashcan) {
  703. // Don't throw an object in the trash can if it just got connected.
  704. this.workspace.trashcan.close();
  705. }
  706. } else if (!this.getParent() && Blockly.selected.isDeletable() &&
  707. this.workspace.isDeleteArea(e)) {
  708. var trashcan = this.workspace.trashcan;
  709. if (trashcan) {
  710. goog.Timer.callOnce(trashcan.close, 100, trashcan);
  711. }
  712. Blockly.selected.dispose(false, true);
  713. }
  714. if (Blockly.highlightedConnection_) {
  715. Blockly.highlightedConnection_.unhighlight();
  716. Blockly.highlightedConnection_ = null;
  717. }
  718. // for BlocksCAD - unhighlight bumped away illegal connections.
  719. if (Blockly.highlightedConnectionBad_) {
  720. Blockly.highlightedConnectionBad_.unhighlight();
  721. Blockly.highlightedConnectionBad_ = null;
  722. }
  723. Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
  724. if (!Blockly.WidgetDiv.isVisible()) {
  725. Blockly.Events.setGroup(false);
  726. }
  727. };
  728. /**
  729. * Load the block's help page in a new window.
  730. * @private
  731. */
  732. Blockly.BlockSvg.prototype.showHelp_ = function() {
  733. var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
  734. if (url) {
  735. window.open(url);
  736. }
  737. };
  738. /**
  739. * Show the context menu for this block.
  740. * @param {!Event} e Mouse event.
  741. * @private
  742. */
  743. Blockly.BlockSvg.prototype.showContextMenu_ = function(e) {
  744. if (this.workspace.options.readOnly || !this.contextMenu) {
  745. return;
  746. }
  747. // Save the current block in a variable for use in closures.
  748. var block = this;
  749. var menuOptions = [];
  750. if (this.isDeletable() && this.isMovable() && !block.isInFlyout) {
  751. // Option to duplicate this block.
  752. var duplicateOption = {
  753. text: Blockly.Msg.DUPLICATE_BLOCK,
  754. enabled: true,
  755. callback: function() {
  756. Blockly.duplicate_(block);
  757. }
  758. };
  759. if (this.getDescendants().length > this.workspace.remainingCapacity()) {
  760. duplicateOption.enabled = false;
  761. }
  762. menuOptions.push(duplicateOption);
  763. if (this.isEditable() && !this.collapsed_ &&
  764. this.workspace.options.comments) {
  765. // Option to add/remove a comment.
  766. var commentOption = {enabled: !goog.userAgent.IE};
  767. if (this.comment) {
  768. commentOption.text = Blockly.Msg.REMOVE_COMMENT;
  769. commentOption.callback = function() {
  770. block.setCommentText(null);
  771. };
  772. } else {
  773. commentOption.text = Blockly.Msg.ADD_COMMENT;
  774. commentOption.callback = function() {
  775. block.setCommentText('');
  776. };
  777. }
  778. menuOptions.push(commentOption);
  779. }
  780. // Option to make block inline.
  781. if (!this.collapsed_) {
  782. for (var i = 1; i < this.inputList.length; i++) {
  783. if (this.inputList[i - 1].type != Blockly.NEXT_STATEMENT &&
  784. this.inputList[i].type != Blockly.NEXT_STATEMENT) {
  785. // Only display this option if there are two value or dummy inputs
  786. // next to each other.
  787. var inlineOption = {enabled: true};
  788. var isInline = this.getInputsInline();
  789. inlineOption.text = isInline ?
  790. Blockly.Msg.EXTERNAL_INPUTS : Blockly.Msg.INLINE_INPUTS;
  791. inlineOption.callback = function() {
  792. block.setInputsInline(!isInline);
  793. };
  794. menuOptions.push(inlineOption);
  795. break;
  796. }
  797. }
  798. }
  799. if (this.workspace.options.collapse) {
  800. // Option to collapse/expand block.
  801. if (this.collapsed_) {
  802. var expandOption = {enabled: true};
  803. expandOption.text = Blockly.Msg.EXPAND_BLOCK;
  804. expandOption.callback = function() {
  805. block.setCollapsed(false);
  806. };
  807. menuOptions.push(expandOption);
  808. } else {
  809. var collapseOption = {enabled: true};
  810. collapseOption.text = Blockly.Msg.COLLAPSE_BLOCK;
  811. collapseOption.callback = function() {
  812. block.setCollapsed(true);
  813. };
  814. menuOptions.push(collapseOption);
  815. }
  816. }
  817. if (this.workspace.options.disable) {
  818. // Option to disable/enable block.
  819. var disableOption = {
  820. text: this.disabled ?
  821. Blockly.Msg.ENABLE_BLOCK : Blockly.Msg.DISABLE_BLOCK,
  822. enabled: !this.getInheritedDisabled(),
  823. callback: function() {
  824. block.setDisabled(!block.disabled);
  825. }
  826. };
  827. menuOptions.push(disableOption);
  828. }
  829. // Option to delete this block.
  830. // Count the number of blocks that are nested in this block.
  831. var descendantCount = this.getDescendants().length;
  832. var nextBlock = this.getNextBlock();
  833. if (nextBlock) {
  834. // Blocks in the current stack would survive this block's deletion.
  835. descendantCount -= nextBlock.getDescendants().length;
  836. }
  837. var deleteOption = {
  838. text: descendantCount == 1 ? Blockly.Msg.DELETE_BLOCK :
  839. Blockly.Msg.DELETE_X_BLOCKS.replace('%1', String(descendantCount)),
  840. enabled: true,
  841. callback: function() {
  842. Blockly.Events.setGroup(true);
  843. block.dispose(true, true);
  844. Blockly.Events.setGroup(false);
  845. }
  846. };
  847. menuOptions.push(deleteOption);
  848. }
  849. // Option to get help.
  850. var url = goog.isFunction(this.helpUrl) ? this.helpUrl() : this.helpUrl;
  851. var helpOption = {enabled: !!url};
  852. helpOption.text = Blockly.Msg.HELP;
  853. helpOption.callback = function() {
  854. block.showHelp_();
  855. };
  856. menuOptions.push(helpOption);
  857. // Allow the block to add or modify menuOptions.
  858. if (this.customContextMenu && !block.isInFlyout) {
  859. this.customContextMenu(menuOptions);
  860. }
  861. Blockly.ContextMenu.show(e, menuOptions, this.RTL);
  862. Blockly.ContextMenu.currentBlock = this;
  863. };
  864. /**
  865. * Move the connections for this block and all blocks attached under it.
  866. * Also update any attached bubbles.
  867. * @param {number} dx Horizontal offset from current location.
  868. * @param {number} dy Vertical offset from current location.
  869. * @private
  870. */
  871. Blockly.BlockSvg.prototype.moveConnections_ = function(dx, dy) {
  872. if (!this.rendered) {
  873. // Rendering is required to lay out the blocks.
  874. // This is probably an invisible block attached to a collapsed block.
  875. return;
  876. }
  877. var myConnections = this.getConnections_(false);
  878. for (var i = 0; i < myConnections.length; i++) {
  879. myConnections[i].moveBy(dx, dy);
  880. }
  881. var icons = this.getIcons();
  882. for (var i = 0; i < icons.length; i++) {
  883. icons[i].computeIconLocation();
  884. }
  885. // Recurse through all blocks attached under this one.
  886. for (var i = 0; i < this.childBlocks_.length; i++) {
  887. this.childBlocks_[i].moveConnections_(dx, dy);
  888. }
  889. };
  890. /**
  891. * Recursively adds or removes the dragging class to this node and its children.
  892. * @param {boolean} adding True if adding, false if removing.
  893. * @private
  894. */
  895. Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
  896. if (adding) {
  897. var group = this.getSvgRoot();
  898. group.translate_ = '';
  899. group.skew_ = '';
  900. this.addDragging();
  901. Blockly.draggingConnections_ =
  902. Blockly.draggingConnections_.concat(this.getConnections_(true));
  903. } else {
  904. this.removeDragging();
  905. Blockly.draggingConnections_ = [];
  906. }
  907. // Recurse through all blocks attached under this one.
  908. for (var i = 0; i < this.childBlocks_.length; i++) {
  909. this.childBlocks_[i].setDragging_(adding);
  910. }
  911. };
  912. /**
  913. * Drag this block to follow the mouse.
  914. * @param {!Event} e Mouse move event.
  915. * @private
  916. */
  917. Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
  918. if (e.type == 'mousemove' && e.clientX <= 1 && e.clientY == 0 &&
  919. e.button == 0) {
  920. /* HACK:
  921. Safari Mobile 6.0 and Chrome for Android 18.0 fire rogue mousemove
  922. events on certain touch actions. Ignore events with these signatures.
  923. This may result in a one-pixel blind spot in other browsers,
  924. but this shouldn't be noticeable. */
  925. e.stopPropagation();
  926. return;
  927. }
  928. var oldXY = this.getRelativeToSurfaceXY();
  929. var newXY = this.workspace.moveDrag(e);
  930. if (Blockly.dragMode_ == Blockly.DRAG_STICKY) {
  931. // Still dragging within the sticky DRAG_RADIUS.
  932. var dr = goog.math.Coordinate.distance(oldXY, newXY) * this.workspace.scale;
  933. if (dr > Blockly.DRAG_RADIUS) {
  934. // Switch to unrestricted dragging.
  935. Blockly.dragMode_ = Blockly.DRAG_FREE;
  936. Blockly.longStop_();
  937. if (this.parentBlock_) {
  938. // Push this block to the very top of the stack.
  939. this.unplug();
  940. var group = this.getSvgRoot();
  941. group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
  942. this.disconnectUiEffect();
  943. }
  944. this.setDragging_(true);
  945. }
  946. }
  947. if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
  948. // Unrestricted dragging.
  949. var dxy = goog.math.Coordinate.difference(oldXY, this.dragStartXY_);
  950. var group = this.getSvgRoot();
  951. group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
  952. group.setAttribute('transform', group.translate_ + group.skew_);
  953. // Drag all the nested bubbles.
  954. for (var i = 0; i < this.draggedBubbles_.length; i++) {
  955. var commentData = this.draggedBubbles_[i];
  956. commentData.bubble.setIconLocation(
  957. goog.math.Coordinate.sum(commentData, dxy));
  958. }
  959. // Check to see if any of this block's connections are within range of
  960. // another block's connection.
  961. var myConnections = this.getConnections_(false);
  962. // Also check the last connection on this stack
  963. var lastOnStack = this.lastConnectionInStack_();
  964. if (lastOnStack && lastOnStack != this.nextConnection) {
  965. myConnections.push(lastOnStack);
  966. }
  967. var closestConnection = null;
  968. var closestConnectionBad = null;
  969. var localConnection = null;
  970. var radiusConnection = Blockly.SNAP_RADIUS;
  971. // for BlocksCAD, I have a "neighbour.allowed" field and use it to find bad connections.
  972. for (var i = 0; i < myConnections.length; i++) {
  973. var myConnection = myConnections[i];
  974. var neighbour = myConnection.closest(radiusConnection, dxy);
  975. if (neighbour.connection && neighbour.allowed) {
  976. closestConnection = neighbour.connection;
  977. localConnection = myConnection;
  978. radiusConnection = neighbour.radius;
  979. }
  980. else if (neighbour.connection && !neighbour.allowed) {
  981. closestConnectionBad = neighbour.connection;
  982. localConnection = myConnection;
  983. radiusConnection = neighbour.radius;
  984. }
  985. }
  986. // Remove connection highlighting if needed.
  987. if (Blockly.highlightedConnection_ &&
  988. Blockly.highlightedConnection_ != closestConnection) {
  989. Blockly.highlightedConnection_.unhighlight();
  990. Blockly.highlightedConnection_ = null;
  991. Blockly.localConnection_ = null;
  992. }
  993. // for BlocksCAD - add or remove bad connection highlighting
  994. // Remove connection highlighting if needed.
  995. if (Blockly.highlightedConnectionBad_ &&
  996. Blockly.highlightedConnectionBad_ != closestConnectionBad) {
  997. Blockly.highlightedConnectionBad_.unhighlight();
  998. Blockly.highlightedConnectionBad_ = null;
  999. Blockly.localConnection_ = null;
  1000. }
  1001. // Add connection highlighting if needed.
  1002. if (closestConnection &&
  1003. closestConnection != Blockly.highlightedConnection_) {
  1004. closestConnection.highlight();
  1005. Blockly.highlightedConnection_ = closestConnection;
  1006. Blockly.localConnection_ = localConnection;
  1007. }
  1008. // Add connection highlighting if needed for illegal connections (BlocksCAD)
  1009. if (closestConnectionBad &&
  1010. closestConnectionBad != Blockly.highlightedConnectionBad_) {
  1011. closestConnectionBad.highlightBad();
  1012. Blockly.highlightedConnectionBad_ = closestConnectionBad;
  1013. Blockly.localConnection_ = localConnection;
  1014. }
  1015. // end of BlocksCAD bad connection highlighting
  1016. // Provide visual indication of whether the block will be deleted if
  1017. // dropped here.
  1018. if (this.isDeletable()) {
  1019. this.workspace.isDeleteArea(e);
  1020. }
  1021. }
  1022. // This event has been handled. No need to bubble up to the document.
  1023. e.stopPropagation();
  1024. e.preventDefault();
  1025. };
  1026. /**
  1027. * Add or remove the UI indicating if this block is movable or not.
  1028. */
  1029. Blockly.BlockSvg.prototype.updateMovable = function() {
  1030. if (this.isMovable()) {
  1031. Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
  1032. 'blocklyDraggable');
  1033. } else {
  1034. Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
  1035. 'blocklyDraggable');
  1036. }
  1037. };
  1038. /**
  1039. * Set whether this block is movable or not.
  1040. * @param {boolean} movable True if movable.
  1041. */
  1042. Blockly.BlockSvg.prototype.setMovable = function(movable) {
  1043. Blockly.BlockSvg.superClass_.setMovable.call(this, movable);
  1044. this.updateMovable();
  1045. };
  1046. /**
  1047. * Set whether this block is editable or not.
  1048. * @param {boolean} editable True if editable.
  1049. */
  1050. Blockly.BlockSvg.prototype.setEditable = function(editable) {
  1051. Blockly.BlockSvg.superClass_.setEditable.call(this, editable);
  1052. var icons = this.getIcons();
  1053. for (var i = 0; i < icons.length; i++) {
  1054. icons[i].updateEditable();
  1055. }
  1056. };
  1057. /**
  1058. * Set whether this block is a shadow block or not.
  1059. * @param {boolean} shadow True if a shadow.
  1060. */
  1061. Blockly.BlockSvg.prototype.setShadow = function(shadow) {
  1062. Blockly.BlockSvg.superClass_.setShadow.call(this, shadow);
  1063. this.updateColour();
  1064. };
  1065. /**
  1066. * Return the root node of the SVG or null if none exists.
  1067. * @return {Element} The root SVG node (probably a group).
  1068. */
  1069. Blockly.BlockSvg.prototype.getSvgRoot = function() {
  1070. return this.svgGroup_;
  1071. };
  1072. /**
  1073. * Dispose of this block.
  1074. * @param {boolean} healStack If true, then try to heal any gap by connecting
  1075. * the next statement with the previous statement. Otherwise, dispose of
  1076. * all children of this block.
  1077. * @param {boolean} animate If true, show a disposal animation and sound.
  1078. */
  1079. Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
  1080. Blockly.Tooltip.hide();
  1081. Blockly.Field.startCache();
  1082. // Save the block's workspace temporarily so we can resize the
  1083. // contents once the block is disposed.
  1084. var blockWorkspace = this.workspace;
  1085. // If this block is being dragged, unlink the mouse events.
  1086. if (Blockly.selected == this) {
  1087. this.unselect();
  1088. Blockly.terminateDrag_();
  1089. }
  1090. // If this block has a context menu open, close it.
  1091. if (Blockly.ContextMenu.currentBlock == this) {
  1092. Blockly.ContextMenu.hide();
  1093. }
  1094. if (animate && this.rendered) {
  1095. this.unplug(healStack);
  1096. this.disposeUiEffect();
  1097. }
  1098. // Stop rerendering.
  1099. this.rendered = false;
  1100. Blockly.Events.disable();
  1101. try {
  1102. var icons = this.getIcons();
  1103. for (var i = 0; i < icons.length; i++) {
  1104. icons[i].dispose();
  1105. }
  1106. } finally {
  1107. Blockly.Events.enable();
  1108. }
  1109. Blockly.BlockSvg.superClass_.dispose.call(this, healStack);
  1110. goog.dom.removeNode(this.svgGroup_);
  1111. Blockly.resizeSvgContents(blockWorkspace);
  1112. // Sever JavaScript to DOM connections.
  1113. this.svgGroup_ = null;
  1114. this.svgPath_ = null;
  1115. this.svgPathLight_ = null;
  1116. this.svgPathDark_ = null;
  1117. Blockly.Field.stopCache();
  1118. };
  1119. /**
  1120. * Play some UI effects (sound, animation) when disposing of a block.
  1121. */
  1122. Blockly.BlockSvg.prototype.disposeUiEffect = function() {
  1123. this.workspace.playAudio('delete');
  1124. var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_),
  1125. this.workspace);
  1126. // Deeply clone the current block.
  1127. var clone = this.svgGroup_.cloneNode(true);
  1128. clone.translateX_ = xy.x;
  1129. clone.translateY_ = xy.y;
  1130. clone.setAttribute('transform',
  1131. 'translate(' + clone.translateX_ + ',' + clone.translateY_ + ')');
  1132. this.workspace.getParentSvg().appendChild(clone);
  1133. clone.bBox_ = clone.getBBox();
  1134. // Start the animation.
  1135. Blockly.BlockSvg.disposeUiStep_(clone, this.RTL, new Date,
  1136. this.workspace.scale);
  1137. };
  1138. /**
  1139. * Animate a cloned block and eventually dispose of it.
  1140. * This is a class method, not an instace method since the original block has
  1141. * been destroyed and is no longer accessible.
  1142. * @param {!Element} clone SVG element to animate and dispose of.
  1143. * @param {boolean} rtl True if RTL, false if LTR.
  1144. * @param {!Date} start Date of animation's start.
  1145. * @param {number} workspaceScale Scale of workspace.
  1146. * @private
  1147. */
  1148. Blockly.BlockSvg.disposeUiStep_ = function(clone, rtl, start, workspaceScale) {
  1149. var ms = new Date - start;
  1150. var percent = ms / 150;
  1151. if (percent > 1) {
  1152. goog.dom.removeNode(clone);
  1153. } else {
  1154. var x = clone.translateX_ +
  1155. (rtl ? -1 : 1) * clone.bBox_.width * workspaceScale / 2 * percent;
  1156. var y = clone.translateY_ + clone.bBox_.height * workspaceScale * percent;
  1157. var scale = (1 - percent) * workspaceScale;
  1158. clone.setAttribute('transform', 'translate(' + x + ',' + y + ')' +
  1159. ' scale(' + scale + ')');
  1160. var closure = function() {
  1161. Blockly.BlockSvg.disposeUiStep_(clone, rtl, start, workspaceScale);
  1162. };
  1163. setTimeout(closure, 10);
  1164. }
  1165. };
  1166. /**
  1167. * Play some UI effects (sound, ripple) after a connection has been established.
  1168. */
  1169. Blockly.BlockSvg.prototype.connectionUiEffect = function() {
  1170. this.workspace.playAudio('click');
  1171. if (this.workspace.scale < 1) {
  1172. return; // Too small to care about visual effects.
  1173. }
  1174. // Determine the absolute coordinates of the inferior block.
  1175. var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_),
  1176. this.workspace);
  1177. // Offset the coordinates based on the two connection types, fix scale.
  1178. if (this.outputConnection) {
  1179. xy.x += (this.RTL ? 3 : -3) * this.workspace.scale;
  1180. xy.y += 13 * this.workspace.scale;
  1181. } else if (this.previousConnection) {
  1182. xy.x += (this.RTL ? -23 : 23) * this.workspace.scale;
  1183. xy.y += 3 * this.workspace.scale;
  1184. }
  1185. var ripple = Blockly.createSvgElement('circle',
  1186. {'cx': xy.x, 'cy': xy.y, 'r': 0, 'fill': 'none',
  1187. 'stroke': '#888', 'stroke-width': 10},
  1188. this.workspace.getParentSvg());
  1189. // Start the animation.
  1190. Blockly.BlockSvg.connectionUiStep_(ripple, new Date, this.workspace.scale);
  1191. };
  1192. /**
  1193. * Expand a ripple around a connection.
  1194. * @param {!Element} ripple Element to animate.
  1195. * @param {!Date} start Date of animation's start.
  1196. * @param {number} workspaceScale Scale of workspace.
  1197. * @private
  1198. */
  1199. Blockly.BlockSvg.connectionUiStep_ = function(ripple, start, workspaceScale) {
  1200. var ms = new Date - start;
  1201. var percent = ms / 150;
  1202. if (percent > 1) {
  1203. goog.dom.removeNode(ripple);
  1204. } else {
  1205. ripple.setAttribute('r', percent * 25 * workspaceScale);
  1206. ripple.style.opacity = 1 - percent;
  1207. var closure = function() {
  1208. Blockly.BlockSvg.connectionUiStep_(ripple, start, workspaceScale);
  1209. };
  1210. Blockly.BlockSvg.disconnectUiStop_.pid_ = setTimeout(closure, 10);
  1211. }
  1212. };
  1213. /**
  1214. * Play some UI effects (sound, animation) when disconnecting a block.
  1215. */
  1216. Blockly.BlockSvg.prototype.disconnectUiEffect = function() {
  1217. this.workspace.playAudio('disconnect');
  1218. if (this.workspace.scale < 1) {
  1219. return; // Too small to care about visual effects.
  1220. }
  1221. // Horizontal distance for bottom of block to wiggle.
  1222. var DISPLACEMENT = 10;
  1223. // Scale magnitude of skew to height of block.
  1224. var height = this.getHeightWidth().height;
  1225. var magnitude = Math.atan(DISPLACEMENT / height) / Math.PI * 180;
  1226. if (!this.RTL) {
  1227. magnitude *= -1;
  1228. }
  1229. // Start the animation.
  1230. Blockly.BlockSvg.disconnectUiStep_(this.svgGroup_, magnitude, new Date);
  1231. };
  1232. /**
  1233. * Animate a brief wiggle of a disconnected block.
  1234. * @param {!Element} group SVG element to animate.
  1235. * @param {number} magnitude Maximum degrees skew (reversed for RTL).
  1236. * @param {!Date} start Date of animation's start.
  1237. * @private
  1238. */
  1239. Blockly.BlockSvg.disconnectUiStep_ = function(group, magnitude, start) {
  1240. var DURATION = 200; // Milliseconds.
  1241. var WIGGLES = 3; // Half oscillations.
  1242. var ms = new Date - start;
  1243. var percent = ms / DURATION;
  1244. if (percent > 1) {
  1245. group.skew_ = '';
  1246. } else {
  1247. var skew = Math.round(Math.sin(percent * Math.PI * WIGGLES) *
  1248. (1 - percent) * magnitude);
  1249. group.skew_ = 'skewX(' + skew + ')';
  1250. var closure = function() {
  1251. Blockly.BlockSvg.disconnectUiStep_(group, magnitude, start);
  1252. };
  1253. Blockly.BlockSvg.disconnectUiStop_.group = group;
  1254. Blockly.BlockSvg.disconnectUiStop_.pid = setTimeout(closure, 10);
  1255. }
  1256. group.setAttribute('transform', group.translate_ + group.skew_);
  1257. };
  1258. /**
  1259. * Stop the disconnect UI animation immediately.
  1260. * @private
  1261. */
  1262. Blockly.BlockSvg.disconnectUiStop_ = function() {
  1263. if (Blockly.BlockSvg.disconnectUiStop_.group) {
  1264. clearTimeout(Blockly.BlockSvg.disconnectUiStop_.pid);
  1265. var group = Blockly.BlockSvg.disconnectUiStop_.group;
  1266. group.skew_ = '';
  1267. group.setAttribute('transform', group.translate_);
  1268. Blockly.BlockSvg.disconnectUiStop_.group = null;
  1269. }
  1270. };
  1271. /**
  1272. * PID of disconnect UI animation. There can only be one at a time.
  1273. * @type {number}
  1274. */
  1275. Blockly.BlockSvg.disconnectUiStop_.pid = 0;
  1276. /**
  1277. * SVG group of wobbling block. There can only be one at a time.
  1278. * @type {Element}
  1279. */
  1280. Blockly.BlockSvg.disconnectUiStop_.group = null;
  1281. /**
  1282. * Change the colour of a block.
  1283. */
  1284. Blockly.BlockSvg.prototype.updateColour = function() {
  1285. if (this.disabled) {
  1286. // Disabled blocks don't have colour.
  1287. return;
  1288. }
  1289. var hexColour = this.getColour();
  1290. var rgb = goog.color.hexToRgb(hexColour);
  1291. if (this.isShadow()) {
  1292. rgb = goog.color.lighten(rgb, 0.6);
  1293. hexColour = goog.color.rgbArrayToHex(rgb);
  1294. this.svgPathLight_.style.display = 'none';
  1295. this.svgPathDark_.setAttribute('fill', hexColour);
  1296. } else {
  1297. this.svgPathLight_.style.display = '';
  1298. var hexLight = goog.color.rgbArrayToHex(goog.color.lighten(rgb, 0.3));
  1299. var hexDark = goog.color.rgbArrayToHex(goog.color.darken(rgb, 0.2));
  1300. this.svgPathLight_.setAttribute('stroke', hexLight);
  1301. this.svgPathDark_.setAttribute('fill', hexDark);
  1302. }
  1303. this.svgPath_.setAttribute('fill', hexColour);
  1304. var icons = this.getIcons();
  1305. for (var i = 0; i < icons.length; i++) {
  1306. icons[i].updateColour();
  1307. }
  1308. // Bump every dropdown to change its colour.
  1309. for (var x = 0, input; input = this.inputList[x]; x++) {
  1310. for (var y = 0, field; field = input.fieldRow[y]; y++) {
  1311. field.setText(null);
  1312. }
  1313. }
  1314. };
  1315. /**
  1316. * Enable or disable a block.
  1317. */
  1318. Blockly.BlockSvg.prototype.updateDisabled = function() {
  1319. var hasClass = Blockly.hasClass_(/** @type {!Element} */ (this.svgGroup_),
  1320. 'blocklyDisabled');
  1321. if (this.disabled || this.getInheritedDisabled()) {
  1322. if (!hasClass) {
  1323. Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
  1324. 'blocklyDisabled');
  1325. this.svgPath_.setAttribute('fill',
  1326. 'url(#' + this.workspace.options.disabledPatternId + ')');
  1327. }
  1328. } else {
  1329. if (hasClass) {
  1330. Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
  1331. 'blocklyDisabled');
  1332. this.updateColour();
  1333. }
  1334. }
  1335. var children = this.getChildren();
  1336. for (var i = 0, child; child = children[i]; i++) {
  1337. child.updateDisabled();
  1338. }
  1339. };
  1340. /**
  1341. * Returns the comment on this block (or '' if none).
  1342. * @return {string} Block's comment.
  1343. */
  1344. Blockly.BlockSvg.prototype.getCommentText = function() {
  1345. if (this.comment) {
  1346. var comment = this.comment.getText();
  1347. // Trim off trailing whitespace.
  1348. return comment.replace(/\s+$/, '').replace(/ +\n/g, '\n');
  1349. }
  1350. return '';
  1351. };
  1352. /**
  1353. * Set this block's comment text.
  1354. * @param {?string} text The text, or null to delete.
  1355. */
  1356. Blockly.BlockSvg.prototype.setCommentText = function(text) {
  1357. var changedState = false;
  1358. if (goog.isString(text)) {
  1359. if (!this.comment) {
  1360. this.comment = new Blockly.Comment(this);
  1361. changedState = true;
  1362. }
  1363. this.comment.setText(/** @type {string} */ (text));
  1364. } else {
  1365. if (this.comment) {
  1366. this.comment.dispose();
  1367. changedState = true;
  1368. }
  1369. }
  1370. if (changedState && this.rendered) {
  1371. this.render();
  1372. // Adding or removing a comment icon will cause the block to change shape.
  1373. this.bumpNeighbours_();
  1374. }
  1375. };
  1376. /**
  1377. * Set this block's warning text.
  1378. * @param {?string} text The text, or null to delete.
  1379. * @param {string=} opt_id An optional ID for the warning text to be able to
  1380. * maintain multiple warnings.
  1381. */
  1382. Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) {
  1383. if (!this.setWarningText.pid_) {
  1384. // Create a database of warning PIDs.
  1385. // Only runs once per block (and only those with warnings).
  1386. this.setWarningText.pid_ = Object.create(null);
  1387. }
  1388. var id = opt_id || '';
  1389. if (!id) {
  1390. // Kill all previous pending processes, this edit supercedes them all.
  1391. for (var n in this.setWarningText.pid_) {
  1392. clearTimeout(this.setWarningText.pid_[n]);
  1393. delete this.setWarningText.pid_[n];
  1394. }
  1395. } else if (this.setWarningText.pid_[id]) {
  1396. // Only queue up the latest change. Kill any earlier pending process.
  1397. clearTimeout(this.setWarningText.pid_[id]);
  1398. delete this.setWarningText.pid_[id];
  1399. }
  1400. if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
  1401. // Don't change the warning text during a drag.
  1402. // Wait until the drag finishes.
  1403. var thisBlock = this;
  1404. this.setWarningText.pid_[id] = setTimeout(function() {
  1405. if (thisBlock.workspace) { // Check block wasn't deleted.
  1406. delete thisBlock.setWarningText.pid_[id];
  1407. thisBlock.setWarningText(text, id);
  1408. }
  1409. }, 100);
  1410. return;
  1411. }
  1412. if (this.isInFlyout) {
  1413. text = null;
  1414. }
  1415. // Bubble up to add a warning on top-most collapsed block.
  1416. var parent = this.getSurroundParent();
  1417. var collapsedParent = null;
  1418. while (parent) {
  1419. if (parent.isCollapsed()) {
  1420. collapsedParent = parent;
  1421. }
  1422. parent = parent.getSurroundParent();
  1423. }
  1424. if (collapsedParent) {
  1425. collapsedParent.setWarningText(text, 'collapsed ' + this.id + ' ' + id);
  1426. }
  1427. var changedState = false;
  1428. if (goog.isString(text)) {
  1429. if (!this.warning) {
  1430. this.warning = new Blockly.Warning(this);
  1431. changedState = true;
  1432. }
  1433. this.warning.setText(/** @type {string} */ (text), id);
  1434. } else {
  1435. // Dispose all warnings if no id is given.
  1436. if (this.warning && !id) {
  1437. this.warning.dispose();
  1438. changedState = true;
  1439. } else if (this.warning) {
  1440. var oldText = this.warning.getText();
  1441. this.warning.setText('', id);
  1442. var newText = this.warning.getText();
  1443. if (!newText) {
  1444. this.warning.dispose();
  1445. }
  1446. changedState = oldText == newText;
  1447. }
  1448. }
  1449. if (changedState && this.rendered) {
  1450. this.render();
  1451. // Adding or removing a warning icon will cause the block to change shape.
  1452. this.bumpNeighbours_();
  1453. }
  1454. };
  1455. /**
  1456. * Give this block a mutator dialog.
  1457. * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
  1458. */
  1459. Blockly.BlockSvg.prototype.setMutator = function(mutator) {
  1460. if (this.mutator && this.mutator !== mutator) {
  1461. this.mutator.dispose();
  1462. }
  1463. if (mutator) {
  1464. mutator.block_ = this;
  1465. this.mutator = mutator;
  1466. mutator.createIcon();
  1467. }
  1468. };
  1469. /**
  1470. * Give this block a mutator dialog.
  1471. * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
  1472. */
  1473. Blockly.BlockSvg.prototype.setMutatorPlus = function(mutatorPlus) {
  1474. if (this.mutatorPlus && this.mutatorPlus !== mutatorPlus) {
  1475. this.mutatorPlus.dispose();
  1476. }
  1477. if (mutatorPlus) {
  1478. mutatorPlus.block_ = this;
  1479. this.mutatorPlus = mutatorPlus;
  1480. if (this.rendered) {
  1481. mutatorPlus.createIcon();
  1482. }
  1483. }
  1484. };
  1485. /**
  1486. * Give this block a mutator dialog.
  1487. * @param {Blockly.Mutator} mutator A mutator dialog instance or null to remove.
  1488. */
  1489. Blockly.BlockSvg.prototype.setMutatorMinus = function(mutatorMinus) {
  1490. if (this.mutatorMinus && this.mutatorMinus !== mutatorMinus) {
  1491. this.mutatorMinus.dispose();
  1492. }
  1493. if (mutatorMinus) {
  1494. mutatorMinus.block_ = this;
  1495. this.mutatorMinus = mutatorMinus;
  1496. if (this.rendered) {
  1497. mutatorMinus.createIcon();
  1498. }
  1499. }
  1500. };
  1501. /**
  1502. * Set whether the block is disabled or not.
  1503. * @param {boolean} disabled True if disabled.
  1504. */
  1505. Blockly.BlockSvg.prototype.setDisabled = function(disabled) {
  1506. if (this.disabled != disabled) {
  1507. Blockly.BlockSvg.superClass_.setDisabled.call(this, disabled);
  1508. if (this.rendered) {
  1509. this.updateDisabled();
  1510. }
  1511. }
  1512. };
  1513. /**
  1514. * Select this block. Highlight it visually.
  1515. */
  1516. Blockly.BlockSvg.prototype.addSelect = function() {
  1517. Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
  1518. 'blocklySelected');
  1519. // Move the selected block to the top of the stack.
  1520. var block = this;
  1521. do {
  1522. var root = block.getSvgRoot();
  1523. root.parentNode.appendChild(root);
  1524. block = block.getParent();
  1525. } while (block);
  1526. };
  1527. /**
  1528. * Unselect this block. Remove its highlighting.
  1529. */
  1530. Blockly.BlockSvg.prototype.removeSelect = function() {
  1531. Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
  1532. 'blocklySelected');
  1533. };
  1534. /**
  1535. * Backlight this block. Highlight it visually. Added for BlocksCAD
  1536. */
  1537. Blockly.BlockSvg.prototype.addBacklight = function(color) {
  1538. Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
  1539. 'blocklyBacklight');
  1540. // Move the selected block to the top of the stack.
  1541. this.svgGroup_.parentNode.appendChild(this.svgGroup_);
  1542. };
  1543. /**
  1544. * Unbacklight this block. Remove its highlighting. Added for BlocksCAD
  1545. */
  1546. Blockly.BlockSvg.prototype.removeBacklight = function(color) {
  1547. Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
  1548. 'blocklyBacklight');
  1549. };
  1550. /**
  1551. * Adds the dragging class to this block.
  1552. * Also disables the highlights/shadows to improve performance.
  1553. */
  1554. Blockly.BlockSvg.prototype.addDragging = function() {
  1555. Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
  1556. 'blocklyDragging');
  1557. };
  1558. /**
  1559. * Removes the dragging class from this block.
  1560. */
  1561. Blockly.BlockSvg.prototype.removeDragging = function() {
  1562. Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
  1563. 'blocklyDragging');
  1564. };
  1565. // Overrides of functions on Blockly.Block that take into account whether the
  1566. // block has been rendered.
  1567. /**
  1568. * Change the colour of a block.
  1569. * @param {number|string} colour HSV hue value, or #RRGGBB string.
  1570. */
  1571. Blockly.BlockSvg.prototype.setColour = function(colour) {
  1572. Blockly.BlockSvg.superClass_.setColour.call(this, colour);
  1573. if (this.rendered) {
  1574. this.updateColour();
  1575. }
  1576. };
  1577. /**
  1578. * Set whether this block can chain onto the bottom of another block.
  1579. * @param {boolean} newBoolean True if there can be a previous statement.
  1580. * @param {string|Array.<string>|null|undefined} opt_check Statement type or
  1581. * list of statement types. Null/undefined if any type could be connected.
  1582. */
  1583. Blockly.BlockSvg.prototype.setPreviousStatement =
  1584. function(newBoolean, opt_check) {
  1585. /* eslint-disable indent */
  1586. Blockly.BlockSvg.superClass_.setPreviousStatement.call(this, newBoolean,
  1587. opt_check);
  1588. if (this.rendered) {
  1589. this.render();
  1590. this.bumpNeighbours_();
  1591. }
  1592. }; /* eslint-enable indent */
  1593. /**
  1594. * Set whether another block can chain onto the bottom of this block.
  1595. * @param {boolean} newBoolean True if there can be a next statement.
  1596. * @param {string|Array.<string>|null|undefined} opt_check Statement type or
  1597. * list of statement types. Null/undefined if any type could be connected.
  1598. */
  1599. Blockly.BlockSvg.prototype.setNextStatement = function(newBoolean, opt_check) {
  1600. Blockly.BlockSvg.superClass_.setNextStatement.call(this, newBoolean,
  1601. opt_check);
  1602. if (this.rendered) {
  1603. this.render();
  1604. this.bumpNeighbours_();
  1605. }
  1606. };
  1607. /**
  1608. * Set whether this block returns a value.
  1609. * @param {boolean} newBoolean True if there is an output.
  1610. * @param {string|Array.<string>|null|undefined} opt_check Returned type or list
  1611. * of returned types. Null or undefined if any type could be returned
  1612. * (e.g. variable get).
  1613. */
  1614. Blockly.BlockSvg.prototype.setOutput = function(newBoolean, opt_check) {
  1615. Blockly.BlockSvg.superClass_.setOutput.call(this, newBoolean, opt_check);
  1616. if (this.rendered) {
  1617. this.render();
  1618. this.bumpNeighbours_();
  1619. }
  1620. };
  1621. /**
  1622. * Set whether value inputs are arranged horizontally or vertically.
  1623. * @param {boolean} newBoolean True if inputs are horizontal.
  1624. */
  1625. Blockly.BlockSvg.prototype.setInputsInline = function(newBoolean) {
  1626. Blockly.BlockSvg.superClass_.setInputsInline.call(this, newBoolean);
  1627. if (this.rendered) {
  1628. this.render();
  1629. this.bumpNeighbours_();
  1630. }
  1631. };
  1632. /**
  1633. * Remove an input from this block.
  1634. * @param {string} name The name of the input.
  1635. * @param {boolean=} opt_quiet True to prevent error if input is not present.
  1636. * @throws {goog.asserts.AssertionError} if the input is not present and
  1637. * opt_quiet is not true.
  1638. */
  1639. Blockly.BlockSvg.prototype.removeInput = function(name, opt_quiet) {
  1640. Blockly.BlockSvg.superClass_.removeInput.call(this, name, opt_quiet);
  1641. if (this.rendered) {
  1642. this.render();
  1643. // Removing an input will cause the block to change shape.
  1644. this.bumpNeighbours_();
  1645. }
  1646. };
  1647. /**
  1648. * Move a numbered input to a different location on this block.
  1649. * @param {number} inputIndex Index of the input to move.
  1650. * @param {number} refIndex Index of input that should be after the moved input.
  1651. */
  1652. Blockly.BlockSvg.prototype.moveNumberedInputBefore = function(
  1653. inputIndex, refIndex) {
  1654. Blockly.BlockSvg.superClass_.moveNumberedInputBefore.call(this, inputIndex,
  1655. refIndex);
  1656. if (this.rendered) {
  1657. this.render();
  1658. // Moving an input will cause the block to change shape.
  1659. this.bumpNeighbours_();
  1660. }
  1661. };
  1662. /**
  1663. * Add a value input, statement input or local variable to this block.
  1664. * @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or
  1665. * Blockly.DUMMY_INPUT.
  1666. * @param {string} name Language-neutral identifier which may used to find this
  1667. * input again. Should be unique to this block.
  1668. * @return {!Blockly.Input} The input object created.
  1669. * @private
  1670. */
  1671. Blockly.BlockSvg.prototype.appendInput_ = function(type, name) {
  1672. var input = Blockly.BlockSvg.superClass_.appendInput_.call(this, type, name);
  1673. if (this.rendered) {
  1674. this.render();
  1675. // Adding an input will cause the block to change shape.
  1676. this.bumpNeighbours_();
  1677. }
  1678. return input;
  1679. };
  1680. /**
  1681. * Returns connections originating from this block.
  1682. * @param {boolean} all If true, return all connections even hidden ones.
  1683. * Otherwise, for a non-rendered block return an empty list, and for a
  1684. * collapsed block don't return inputs connections.
  1685. * @return {!Array.<!Blockly.Connection>} Array of connections.
  1686. * @private
  1687. */
  1688. Blockly.BlockSvg.prototype.getConnections_ = function(all) {
  1689. var myConnections = [];
  1690. if (all || this.rendered) {
  1691. if (this.outputConnection) {
  1692. myConnections.push(this.outputConnection);
  1693. }
  1694. if (this.previousConnection) {
  1695. myConnections.push(this.previousConnection);
  1696. }
  1697. if (this.nextConnection) {
  1698. myConnections.push(this.nextConnection);
  1699. }
  1700. if (all || !this.collapsed_) {
  1701. for (var i = 0, input; input = this.inputList[i]; i++) {
  1702. if (input.connection) {
  1703. myConnections.push(input.connection);
  1704. }
  1705. }
  1706. }
  1707. }
  1708. return myConnections;
  1709. };
  1710. /**
  1711. * Create a connection of the specified type.
  1712. * @param {number} type The type of the connection to create.
  1713. * @return {!Blockly.RenderedConnection} A new connection of the specified type.
  1714. * @private
  1715. */
  1716. Blockly.BlockSvg.prototype.makeConnection_ = function(type) {
  1717. return new Blockly.RenderedConnection(this, type);
  1718. };