procedures.js 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474
  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 Procedure blocks for Blockly.
  22. * @author fraser@google.com (Neil Fraser)
  23. */
  24. 'use strict';
  25. goog.provide('Blockly.Blocks.procedures');
  26. goog.require('Blockly.Blocks');
  27. Blockly.Blocks['procedures_defnoreturn'] = {
  28. /**
  29. * Block for defining a procedure with no return value.
  30. * @this Blockly.Block
  31. */
  32. init: function() {
  33. this.category = 'PROCEDURE'; // for blocksCAD
  34. this.myType_ = ['CSG','CAG']; // for blocksCAD
  35. this.backlightBlocks = []; // for blocksCAD
  36. var nameField = new Blockly.FieldTextInput(
  37. Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE,
  38. Blockly.Procedures.rename);
  39. nameField.setSpellcheck(false);
  40. this.appendDummyInput()
  41. .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE)
  42. .appendField(nameField, 'NAME')
  43. .appendField('', 'PARAMS');
  44. this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
  45. this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);
  46. this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
  47. this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);
  48. this.arguments_ = [];
  49. this.setStatements_(true, 'VariableSet');
  50. this.statementConnection_ = null;
  51. },
  52. /**
  53. * if this procedure has statements, use them to determine the
  54. * type of this procedure, then update types of any callers..
  55. * @this Blockly.Block
  56. */
  57. // setType: function(type,drawMe) { // for blocksCAD
  58. // if (!this.workspace) {
  59. // // Block has been deleted.
  60. // return;
  61. // }
  62. // console.log("starting proc ST with oldtype:" + this.myType_ + " and newtype:" + type);
  63. // if (this.myType_ == type)
  64. // return;
  65. // console.log("in modules's setType");
  66. // var oldtype = this.myType_;
  67. // var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace);
  68. // var numBumped = [];
  69. // var notBumped = [];
  70. // // I need to find out what my caller stacks think their types are.
  71. // if (callers.length) {
  72. // for (var i = 0; i < callers.length; i++) {
  73. // var areaType = Blockscad.findBlockType(callers[i],callers);
  74. // //console.log("caller area type is",areaType);
  75. // //console.log("caller category is", callers[i].category);
  76. // // console.log("parent type is changing to",type);
  77. // if (!goog.isArray(type) && areaType != 'EITHER' && areaType != type) {
  78. // // call blocks are going to be kicked out.
  79. // // console.log("warning message! call block id", callers[i].id, "will be kicked out and backlit");
  80. // numBumped.push(callers[i]);
  81. // // If the call block is in a collapsed stack, find the collapsed parent and expand them.
  82. // var topBlock = callers[i].collapsedParents();
  83. // if (topBlock)
  84. // for (var j=0; j < topBlock.length; j++)
  85. // topBlock[j].setCollapsed(false);
  86. // }
  87. // else notBumped.push(callers[i]);
  88. // }
  89. // }
  90. // if (numBumped.length) {
  91. // var text = '';
  92. // // text += numBumped.length + " ";
  93. // // took out the name so I wouldn't have to deal with renaming the proc.
  94. // //text += this.getFieldValue('NAME') + " ";
  95. // text += Blockscad.Msg.BLOCKS_BUMPED_OUT_DIMENSIONS.replace("%1", numBumped.length);
  96. // this.setWarningText(text);
  97. // }
  98. // this.myType_ = type;
  99. // // some of my callers don't need to be bumped. I'll set their category to "BLAH"
  100. // // temporarily (note this is NOT a valid category),
  101. // // reset the types of the blocks around them, then set them to their new type.
  102. // // this should prevent them getting bumped out incorrectly.
  103. // // if (notBumped.length) {
  104. // // for (var j = 0; j < notBumped.length; j++) {
  105. // // notBumped[j].category = 'BLAH';
  106. // // notBumped[j].previousConnection.setCheck(['CSG','CAG']);
  107. // // }
  108. // // for (j = 0; j < notBumped.length; j++) {
  109. // // console.log("in proc set_type, calling assignBT with BLAH");
  110. // // this.myType_ = type;
  111. // // Blockscad.assignBlockTypes([notBumped[j]]);
  112. // // }
  113. // // }
  114. // // this section actually re-sets the type that triggers the bumping. This needs to be done before backlighting.
  115. // // to make undo work, I need to set the group of the next events.
  116. // if (numBumped.length) {
  117. // // console.log("firing events now - something should have been bumped");
  118. // // lets get the group event
  119. // var eventGroup = true;
  120. // if (Blockscad.workspace.undoStack_.length)
  121. // eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group;
  122. // // console.log("event group is: ", eventGroup);
  123. // Blockly.Events.setGroup(eventGroup);
  124. // }
  125. // if (callers.length > 0) {
  126. // for (var i = 0; i < callers.length; i++) {
  127. // callers[i].previousConnection.setCheck(type);
  128. // // if I'm bumping something, I need to put the typing event into the undoStack_
  129. // // so that on undo the caller doesn't get immediately bumped out again!
  130. // // So first I set the check (which will prompt the bump) THEN fire the type event
  131. // // so that when it is run backwards I untype first then move back into place.
  132. // if (Blockly.Events.isEnabled() && numBumped.length) {
  133. // Blockly.Events.fire(new Blockly.Events.Typing(callers[i], oldtype,type));
  134. // }
  135. // if (type == 'CSG')
  136. // callers[i].category = 'PRIMITIVE_CSG'
  137. // else if (type == 'CAG')
  138. // callers[i].category = 'PRIMITIVE_CAG';
  139. // else callers[i].category = 'UNKNOWN';
  140. // // if the top block isn't the procedure definition (recursion!), then assign their types
  141. // var topBlock = callers[i].getRootBlock();
  142. // if (!(topBlock.category && topBlock.category == 'PROCEDURE')) {
  143. // console.log("calling assignBlockTypes from proc ST (this is prob the bad one");
  144. // this.myType_ = type;
  145. // Blockscad.assignBlockTypes([callers[i]]);
  146. // }
  147. // }
  148. // }
  149. // // // the system will be done now with unplugging all the blocks that need it.
  150. // // set the backlighting and warning message here (with a delay) so that the events that occur during the
  151. // // bumping don't overwrite the backlighting of the caller blocks.
  152. // for (var k = 0; k < numBumped.length; k++) {
  153. // numBumped[k].backlight();
  154. // this.backlightBlocks.push(numBumped[k].id);
  155. // }
  156. // if (numBumped.length) {
  157. // // Blockly.Events.Filter
  158. // Blockly.Events.setGroup(false);
  159. // }
  160. // // events aren't all getting the group setting. Walk back through the undoStack_ and make sure the group is set.
  161. // // for (var i = Blockscad.workspace.undoStack_.length - 1; i > 0; i--) {
  162. // // if
  163. // // }
  164. // this.myType_ = type;
  165. // }, // end for blocksCAD
  166. // // for BlocksCAD - check to see if my callers are still backlight?
  167. // onchange: function() {
  168. // var found_it;
  169. // // go through my backlight id list, see if I have any blocks on it that are not on
  170. // // the general backlight list (they must have been unhighlighted!)
  171. // for (var i=0; i < this.backlightBlocks.length; i++) {
  172. // found_it = 0;
  173. // for (var j=0; j<Blockly.backlight.length; j++) {
  174. // if (this.backlightBlocks[i] === Blockly.backlight[j]) {
  175. // found_it = 1;
  176. // break;
  177. // }
  178. // }
  179. // if (!found_it) { // this block needs to come off our list
  180. // this.backlightBlocks.splice(i,1);
  181. // }
  182. // }
  183. // if (!this.backlightBlocks.length) {
  184. // // console.log("turning off warning text");
  185. // this.setWarningText(null);
  186. // }
  187. // },
  188. // second stab at setType.
  189. setType: function(type, drawMe) {
  190. if (!this.workspace) {
  191. // Block has been deleted.
  192. return;
  193. }
  194. // console.log("starting proc ST with oldtype:" + this.myType_ + " and newtype:" + type);
  195. // console.log("arrays: " + goog.isArray(this.myType_) + ', ' + goog.isArray(type));
  196. if (!goog.isArray(type))
  197. type = [type];
  198. // compare to see if type matches this.myType_
  199. if (Blockscad.arraysEqual(type, this.myType_))
  200. return;
  201. // console.log("in modules's setType");
  202. var oldtype = this.myType_;
  203. var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace);
  204. var numBumped = [];
  205. // first, set my type (the module definition's type)
  206. this.myType_ = type;
  207. // start grouping events in case some blocks are bumped out, so that undo will work easily.
  208. var eventGroup = true;
  209. if (Blockscad.workspace.undoStack_.length)
  210. eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group;
  211. // console.log("event group is: ", eventGroup);
  212. Blockly.Events.setGroup(eventGroup);
  213. // now, set my caller block's types
  214. if (callers.length) {
  215. for (var i = 0; i < callers.length; i++) {
  216. // find what the type is of the stack the caller is in.
  217. var areaType = Blockscad.findBlockType(callers[i],callers);
  218. // if the stack's type doesn't match the caller's new type, bumpage!
  219. // mark that block that will be bumped
  220. if (areaType != 'EITHER' && areaType != type[0]) {
  221. // console.log("warning message! call block id", callers[i].id, "will be kicked out and backlit");
  222. // console.log("parent accepts: " + parentAccepts + ", type is:", type[0]);
  223. numBumped.push(callers[i]);
  224. // If the call block is in a collapsed stack, find the collapsed parent and expand them.
  225. var topBlock = callers[i].collapsedParents();
  226. if (topBlock)
  227. for (var j=0; j < topBlock.length; j++)
  228. topBlock[j].setCollapsed(false);
  229. }
  230. // change caller's type - this is the command that actually prompts Blockly to bump blocks out
  231. callers[i].previousConnection.setCheck(type);
  232. // if it was a bumping change, fire a typing event
  233. if (Blockly.Events.isEnabled() && numBumped.length) {
  234. Blockly.Events.fire(new Blockly.Events.Typing(callers[i], oldtype,type));
  235. }
  236. // procedure callers also have a "category" because once typed they are a shape
  237. // CSG, CAG, or UNKNOWN.
  238. if (type[0] == 'CSG' && type.length == 1)
  239. callers[i].category = 'PRIMITIVE_CSG'
  240. else if (type[0] == 'CAG')
  241. callers[i].category = 'PRIMITIVE_CAG';
  242. else callers[i].category = 'UNKNOWN';
  243. // if caller is inside of another setter block, that setter's type needs to be changed. Do so.
  244. // note that this can lead to an infinite loop if procedures are circularly defined - that is why
  245. // setType MUST exit immediately if it is called with the type not changing.
  246. // what if a parent is a variables_set of a different variable?
  247. // then I want to call Blockscad.assignVarTypes for that parent.
  248. var setterParent = Blockscad.hasParentOfType(callers[i], "procedures_defnoreturn");
  249. if (setterParent) {
  250. setTimeout(function() {
  251. // console.log("this caller is inside a setter: ", setterParent.id);
  252. if (setterParent) setterParent.setType(type);
  253. }, 0);
  254. }
  255. // if the caller was inside a non setter, I still want to type that parent.
  256. var parent = callers[i].getParent();
  257. if (parent) {
  258. Blockscad.assignBlockTypes(parent)
  259. }
  260. }
  261. } // end of going through all callers to set their types.
  262. // turn off event grouping
  263. Blockly.Events.setGroup(false);
  264. // handle backlighting and warning text - do this later so that
  265. // the bumping process itself (which now selects and deselects the blocks) doesn't
  266. // just immediately turn the backlighting off.
  267. for (var k = 0; k < numBumped.length; k++) {
  268. // console.log("backlighting a block:", numBumped[k].id);
  269. numBumped[k].backlight();
  270. this.backlightBlocks.push(numBumped[k].id);
  271. // finally, set a warning message on the procedure definition that counts how many callers were bumped.
  272. var text = '';
  273. text += Blockscad.Msg.BLOCKS_BUMPED_OUT_DIMENSIONS.replace("%1", numBumped.length);
  274. this.setWarningText(text);
  275. }
  276. },
  277. /**
  278. * Add or remove the statement block from this function definition.
  279. * @param {boolean} hasStatements True if a statement block is needed.
  280. * @this Blockly.Block
  281. */
  282. setStatements_: function(hasStatements) {
  283. if (this.hasStatements_ === hasStatements) {
  284. return;
  285. }
  286. if (hasStatements) {
  287. this.appendStatementInput('STACK')
  288. .appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO);
  289. if (this.getInput('RETURN')) {
  290. this.moveInputBefore('STACK', 'RETURN');
  291. }
  292. } else {
  293. this.removeInput('STACK', true);
  294. }
  295. this.hasStatements_ = hasStatements;
  296. },
  297. /**
  298. * Update the display of parameters for this procedure definition block.
  299. * Display a warning if there are duplicately named parameters.
  300. * @private
  301. * @this Blockly.Block
  302. */
  303. updateParams_: function() {
  304. // Check for duplicated arguments.
  305. var badArg = false;
  306. var hash = {};
  307. for (var i = 0; i < this.arguments_.length; i++) {
  308. if (hash['arg_' + this.arguments_[i].toLowerCase()]) {
  309. badArg = true;
  310. break;
  311. }
  312. hash['arg_' + this.arguments_[i].toLowerCase()] = true;
  313. }
  314. if (badArg) {
  315. this.setWarningText(Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING);
  316. } else {
  317. this.setWarningText(null);
  318. }
  319. // Merge the arguments into a human-readable list.
  320. var paramString = '';
  321. if (this.arguments_.length) {
  322. paramString = Blockly.Msg.PROCEDURES_BEFORE_PARAMS +
  323. ' ' + this.arguments_.join(', ');
  324. }
  325. // The params field is deterministic based on the mutation,
  326. // no need to fire a change event.
  327. Blockly.Events.disable();
  328. try {
  329. this.setFieldValue(paramString, 'PARAMS');
  330. } finally {
  331. Blockly.Events.enable();
  332. }
  333. },
  334. /**
  335. * Create XML to represent the argument inputs.
  336. * @param {=boolean} opt_paramIds If true include the IDs of the parameter
  337. * quarks. Used by Blockly.Procedures.mutateCallers for reconnection.
  338. * @return {!Element} XML storage element.
  339. * @this Blockly.Block
  340. */
  341. mutationToDom: function(opt_paramIds) {
  342. var container = document.createElement('mutation');
  343. if (opt_paramIds) {
  344. container.setAttribute('name', this.getFieldValue('NAME'));
  345. }
  346. for (var i = 0; i < this.arguments_.length; i++) {
  347. var parameter = document.createElement('arg');
  348. parameter.setAttribute('name', this.arguments_[i]);
  349. if (opt_paramIds && this.paramIds_) {
  350. parameter.setAttribute('paramId', this.paramIds_[i]);
  351. }
  352. container.appendChild(parameter);
  353. }
  354. // Save whether the statement input is visible.
  355. if (!this.hasStatements_) {
  356. container.setAttribute('statements', 'false');
  357. }
  358. return container;
  359. },
  360. /**
  361. * Parse XML to restore the argument inputs.
  362. * @param {!Element} xmlElement XML storage element.
  363. * @this Blockly.Block
  364. */
  365. domToMutation: function(xmlElement) {
  366. this.arguments_ = [];
  367. for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
  368. if (childNode.nodeName.toLowerCase() == 'arg') {
  369. this.arguments_.push(childNode.getAttribute('name'));
  370. }
  371. }
  372. this.updateParams_();
  373. Blockly.Procedures.mutateCallers(this);
  374. // Show or hide the statement input.
  375. this.setStatements_(xmlElement.getAttribute('statements') !== 'false');
  376. },
  377. /**
  378. * Populate the mutator's dialog with this block's components.
  379. * @param {!Blockly.Workspace} workspace Mutator's workspace.
  380. * @return {!Blockly.Block} Root block in mutator.
  381. * @this Blockly.Block
  382. */
  383. decompose: function(workspace) {
  384. var containerBlock = workspace.newBlock('procedures_mutatorcontainer');
  385. containerBlock.initSvg();
  386. // Check/uncheck the allow statement box.
  387. // for blocksCAD - take out the if statements here so that we always don't show the
  388. // statement checkbox in the mutator. both statements required.
  389. containerBlock.setFieldValue(this.hasStatements_ ? 'TRUE' : 'FALSE',
  390. 'STATEMENTS');
  391. containerBlock.getInput('STATEMENT_INPUT').setVisible(false);
  392. // Parameter list.
  393. var connection = containerBlock.getInput('STACK').connection;
  394. for (var i = 0; i < this.arguments_.length; i++) {
  395. var paramBlock = workspace.newBlock('procedures_mutatorarg');
  396. paramBlock.initSvg();
  397. paramBlock.setFieldValue(this.arguments_[i], 'NAME');
  398. // Store the old location.
  399. paramBlock.oldLocation = i;
  400. connection.connect(paramBlock.previousConnection);
  401. connection = paramBlock.nextConnection;
  402. }
  403. // Initialize procedure's callers with blank IDs.
  404. Blockly.Procedures.mutateCallers(this);
  405. return containerBlock;
  406. },
  407. /**
  408. * Reconfigure this block based on the mutator dialog's components.
  409. * @param {!Blockly.Block} containerBlock Root block in mutator.
  410. * @this Blockly.Block
  411. */
  412. compose: function(containerBlock) {
  413. // Parameter list.
  414. this.arguments_ = [];
  415. this.paramIds_ = [];
  416. var paramBlock = containerBlock.getInputTargetBlock('STACK');
  417. while (paramBlock) {
  418. this.arguments_.push(paramBlock.getFieldValue('NAME'));
  419. this.paramIds_.push(paramBlock.id);
  420. paramBlock = paramBlock.nextConnection &&
  421. paramBlock.nextConnection.targetBlock();
  422. }
  423. this.updateParams_();
  424. Blockly.Procedures.mutateCallers(this);
  425. // Show/hide the statement input.
  426. var hasStatements = containerBlock.getFieldValue('STATEMENTS');
  427. if (hasStatements !== null) {
  428. hasStatements = hasStatements == 'TRUE';
  429. if (this.hasStatements_ != hasStatements) {
  430. if (hasStatements) {
  431. this.setStatements_(true);
  432. // Restore the stack, if one was saved.
  433. Blockly.Mutator.reconnect(this.statementConnection_, this, 'STACK');
  434. this.statementConnection_ = null;
  435. } else {
  436. // Save the stack, then disconnect it.
  437. var stackConnection = this.getInput('STACK').connection;
  438. this.statementConnection_ = stackConnection.targetConnection;
  439. if (this.statementConnection_) {
  440. var stackBlock = stackConnection.targetBlock();
  441. stackBlock.unplug();
  442. stackBlock.bumpNeighbours_();
  443. }
  444. this.setStatements_(false);
  445. }
  446. }
  447. }
  448. },
  449. /**
  450. * Return the signature of this procedure definition.
  451. * @return {!Array} Tuple containing three elements:
  452. * - the name of the defined procedure,
  453. * - a list of all its arguments,
  454. * - that it DOES NOT have a return value.
  455. * @this Blockly.Block
  456. */
  457. getProcedureDef: function() {
  458. // console.log("in getProcedureDef for noreturn for:",this.getFieldValue('NAME'));
  459. return [this.getFieldValue('NAME'), this.arguments_, false];
  460. },
  461. /**
  462. * Return all variables referenced by this block.
  463. * @return {!Array.<string>} List of variable names.
  464. * @this Blockly.Block
  465. */
  466. getVars: function() {
  467. return this.arguments_;
  468. },
  469. /**
  470. * Notification that a variable is renaming.
  471. * If the name matches one of this block's variables, rename it.
  472. * @param {string} oldName Previous name of variable.
  473. * @param {string} newName Renamed variable.
  474. * @this Blockly.Block
  475. */
  476. renameVar: function(oldName, newName) {
  477. var change = false;
  478. for (var i = 0; i < this.arguments_.length; i++) {
  479. if (Blockly.Names.equals(oldName, this.arguments_[i])) {
  480. this.arguments_[i] = newName;
  481. change = true;
  482. }
  483. }
  484. if (change) {
  485. this.updateParams_();
  486. // Update the mutator's variables if the mutator is open.
  487. if (this.mutator.isVisible()) {
  488. var blocks = this.mutator.workspace_.getAllBlocks();
  489. for (var i = 0, block; block = blocks[i]; i++) {
  490. if (block.type == 'procedures_mutatorarg' &&
  491. Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) {
  492. block.setFieldValue(newName, 'NAME');
  493. }
  494. }
  495. }
  496. }
  497. },
  498. /**
  499. * Add custom menu options to this block's context menu.
  500. * @param {!Array} options List of menu options to add to.
  501. * @this Blockly.Block
  502. */
  503. customContextMenu: function(options) {
  504. // Add option to create caller.
  505. var option = {enabled: true};
  506. var name = this.getFieldValue('NAME');
  507. option.text = Blockly.Msg.PROCEDURES_CREATE_DO.replace('%1', name);
  508. var xmlMutation = goog.dom.createDom('mutation');
  509. xmlMutation.setAttribute('name', name);
  510. for (var i = 0; i < this.arguments_.length; i++) {
  511. var xmlArg = goog.dom.createDom('arg');
  512. xmlArg.setAttribute('name', this.arguments_[i]);
  513. xmlMutation.appendChild(xmlArg);
  514. }
  515. var xmlBlock = goog.dom.createDom('block', null, xmlMutation);
  516. xmlBlock.setAttribute('type', this.callType_);
  517. option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
  518. options.push(option);
  519. // Add options to create getters for each parameter.
  520. if (!this.isCollapsed()) {
  521. for (var i = 0; i < this.arguments_.length; i++) {
  522. var option = {enabled: true};
  523. var name = this.arguments_[i];
  524. option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name);
  525. var xmlField = goog.dom.createDom('field', null, name);
  526. xmlField.setAttribute('name', 'VAR');
  527. var xmlBlock = goog.dom.createDom('block', null, xmlField);
  528. xmlBlock.setAttribute('type', 'variables_get');
  529. option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
  530. options.push(option);
  531. }
  532. }
  533. // for BlocksCAD,
  534. var option = {enabled: true};
  535. var name = this.getFieldValue('NAME');
  536. option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1", name);
  537. var workspace = this.workspace;
  538. option.callback = function() {
  539. var def = Blockly.Procedures.getDefinition(name, workspace);
  540. if (def) {
  541. var callers = Blockly.Procedures.getCallers(name, workspace);
  542. workspace.clearBacklight();
  543. Blockly.selected.unselect();
  544. for (var i = 0; callers && i < callers.length; i++) {
  545. callers[i] && callers[i].backlight();
  546. // if caller block is in a collapsed parent, highlight collapsed parent too
  547. var others = callers[i].collapsedParents();
  548. if (others)
  549. for (var j=0; j < others.length; j++)
  550. others[j].backlight();
  551. }
  552. }
  553. };
  554. options.push(option);
  555. },
  556. callType_: 'procedures_callnoreturn'
  557. };
  558. Blockly.Blocks['procedures_defreturn'] = {
  559. /**
  560. * Block for a blockSCAD function: no statements, a return value.
  561. *
  562. * @this Blockly.Block
  563. */
  564. init: function() {
  565. this.category = 'PROCEDURE'; // for blockscad
  566. this.myType_ = null; // for blocksCAD
  567. this.backlightBlocks = []; // for blocksCAD
  568. var nameField = new Blockly.FieldTextInput(
  569. Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE,
  570. Blockly.Procedures.rename);
  571. nameField.setSpellcheck(false);
  572. this.appendDummyInput()
  573. .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_TITLE)
  574. .appendField(nameField, 'NAME')
  575. .appendField('', 'PARAMS');
  576. this.appendValueInput('RETURN')
  577. .setAlign(Blockly.ALIGN_RIGHT)
  578. .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  579. this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
  580. this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
  581. this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP);
  582. this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL);
  583. this.arguments_ = [];
  584. this.setStatements_(false); // set false for blockscad - jayod
  585. this.statementConnection_ = null;
  586. },
  587. /**
  588. * if this procedure has statements, use them to determine the
  589. * type of this procedure, then update types of any callers..
  590. * @this Blockly.Block
  591. */
  592. // setType: function(type,drawMe) { // for blocksCAD
  593. // if (!this.workspace) {
  594. // // Block has been deleted.
  595. // return;
  596. // }
  597. // var ret = this.getInput('RETURN');
  598. // console.log("in setType for function. here is the input:",ret);
  599. // if (ret.connection.targetConnection) {
  600. // if (ret.connection.targetConnection.check_ == 'Number')
  601. // this.myType_ = ret.connection.check_ = 'Number';
  602. // else if (ret.connection.targetConnection.check_ == 'Boolean')
  603. // this.myType_ = ret.connection.check_ = 'Boolean';
  604. // }
  605. // else this.myType_ = ret.connection.check_ = null;
  606. // var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace);
  607. // var numBumped = [];
  608. // var conType = null;
  609. // // I need to find out what my caller stacks think their types are.
  610. // if (callers.length) {
  611. // for (var i = 0; i < callers.length; i++) {
  612. // // console.log("callers.length is:",callers.length);
  613. // // get caller's connection type here
  614. // if (callers[i].outputConnection.targetConnection)
  615. // conType = callers[i].outputConnection.targetConnection.check_;
  616. // if (!goog.isArray(conType)) conType = [conType];
  617. // // conType is an array.
  618. // // console.log("caller type is",conType);
  619. // // console.log(this.myType_);
  620. // if (this.myType_ && conType && conType.indexOf(this.myType_) == -1) {
  621. // // call blocks are going to be kicked out.
  622. // console.log("warning message! call block id", callers[i].id, "will be kicked out");
  623. // // there is a bug here - if we add to the numBumped stack, then we get an infinite loop. ???
  624. // // if (numBumped[numBumped.length] != callers[i])
  625. // // numBumped.push(callers[i]);
  626. // // If the call block is in a collapsed stack, find the collapsed parent and expand them.
  627. // var topBlock = callers[i].collapsedParents();
  628. // if (topBlock)
  629. // for (var j=0; j < topBlock.length; j++)
  630. // topBlock[j].setCollapsed(false);
  631. // }
  632. // }
  633. // }
  634. // if (numBumped.length) {
  635. // // console.log("blah");
  636. // var text = '';
  637. // // text += numBumped.length + " ";
  638. // // text += this.getFieldValue('NAME') + " ";
  639. // text += Blockscad.Msg.BLOCKS_BUMPED_OUT_TYPES.replace("%1", numBumped.length + " " + this.getFieldValue('NAME'));
  640. // this.setWarningText(text);
  641. // }
  642. // if (callers.length > 0) {
  643. // for (var i = 0; i < callers.length; i++) {
  644. // callers[i].outputConnection.setCheck(this.myType_);
  645. // if (this.myType_ == 'Number')
  646. // callers[i].category = 'NUMBER'
  647. // else if (this.myType_ == 'Boolean')
  648. // callers[i].category = 'BOOLEAN';
  649. // else callers[i].category = 'UNKNOWN';
  650. // // console.log("tried to set caller type to ",this.myType_, callers[i]);
  651. // }
  652. // }
  653. // // the system will be done now with unplugging all the blocks that need it.
  654. // // Time to fire a workspaceChanged() so our list of parentIDs will be current.
  655. // if (numBumped.length)
  656. // Blockscad.workspaceChanged();
  657. // }, // end for blocksCAD
  658. // second stab at setType.
  659. setType: function(type, drawMe) {
  660. if (!this.workspace) {
  661. // Block has been deleted.
  662. return;
  663. }
  664. // // compare to see if type matches this.myType_
  665. var oldtype = this.myType_;
  666. var ret = this.getInput('RETURN');
  667. // console.log("in setType for function. here is the input:" + ret.connection);
  668. if (ret.connection.targetConnection) {
  669. if (ret.connection.targetConnection.check_ == 'Number')
  670. type = 'Number';
  671. else if (ret.connection.targetConnection.check_ == 'Boolean')
  672. type = 'Boolean';
  673. else if (ret.connection.targetConnection.check_ == 'String')
  674. type = 'String';
  675. else
  676. type = null;
  677. }
  678. else type = null;
  679. // console.log("starting func ST with oldtype:" + this.myType_ + " and newtype:" + type);
  680. if (this.myType_ == type) {
  681. // console.log("in func ST. returning because types didn't change.");
  682. return;
  683. }
  684. // set the function def's type to what is now connected to its output
  685. this.myType_ = type;
  686. var callers = Blockly.Procedures.getCallers(this.getFieldValue('NAME'), this.workspace);
  687. var numBumped = [];
  688. var conType = null; // type of a caller's output connection
  689. var parentAccepts;
  690. // start grouping events in case some blocks are bumped out, so that undo will work easily.
  691. var eventGroup = true;
  692. if (Blockscad.workspace.undoStack_.length)
  693. eventGroup = Blockscad.workspace.undoStack_[Blockscad.workspace.undoStack_.length - 1].group;
  694. // console.log("event group is: ", eventGroup);
  695. Blockly.Events.setGroup(eventGroup);
  696. // now, set my caller block's types
  697. if (callers.length) {
  698. for (var i = 0; i < callers.length; i++) {
  699. if (!callers[i])
  700. continue;
  701. // the caller block only gets bumped if it has a parent.
  702. var parent = callers[i].getParent();
  703. // get caller's connection type here
  704. if (parent) {
  705. // console.log("found instance with parent: ", parent.type);
  706. parentAccepts = callers[i].outputConnection.targetConnection.check_;
  707. // console.log(parentAccepts);
  708. // make sure that parentAccepts is an array so that we can check for a match
  709. if (parentAccepts && !(goog.isArray(parentAccepts)))
  710. parentAccepts = [parentAccepts];
  711. if (parentAccepts) {
  712. var found_match = 0;
  713. // check to see if my type matches any type accepted by the parent - if so, it will bump.
  714. for (var j = 0; j < parentAccepts.length; j++) {
  715. if (parentAccepts[j] == this.myType_) {
  716. found_match = 1;
  717. }
  718. }
  719. if (!found_match) {
  720. // I have a type mismatch with this variable. it is going to be bumped.
  721. // console.log("warning message! call block id", callers[i].id, "will be kicked out and backlit");
  722. // console.log("parent accepts: " + parentAccepts + ", type is:", this.myType_);
  723. numBumped.push(callers[i]);
  724. // instances[i].backlight();
  725. // this.backlightBlocks.push(instances[i].id);
  726. // if the instance is in a collapsed stack, find collapsed parent and expand
  727. var topBlock = callers[i].collapsedParents();
  728. if (topBlock)
  729. for (var j = 0; j < topBlock.length; j++)
  730. topBlock[j].setCollapsed(false);
  731. }
  732. }
  733. } // end if (parent)
  734. // change caller's type - this is the command that actually prompts Blockly to bump blocks out
  735. // console.log("Set the caller's output check to ", this.myType_);
  736. if (callers[i]) {
  737. callers[i].outputConnection.setCheck(this.myType_);
  738. if (this.myType_ == 'Number')
  739. callers[i].category = 'NUMBER'
  740. else if (this.myType_ == 'Boolean')
  741. callers[i].category = 'BOOLEAN';
  742. else if (this.myType_ == 'String')
  743. callers[i].category = 'STRING';
  744. else {
  745. callers[i].category = 'UNKNOWN';
  746. // console.log("function caller with type UNKNOWN");
  747. }
  748. }
  749. // console.log("tried to set caller type to ",this.myType_, callers[i]);
  750. // if it was a bumping change, fire a typing event
  751. // if (Blockly.Events.isEnabled() && numBumped.length) {
  752. // Blockly.Events.fire(new Blockly.Events.Typing(callers[i], oldtype,type));
  753. // }
  754. // if caller is inside of another setter block, that setter's type needs to be changed. Do so.
  755. // note that this can lead to an infinite loop if procedures are circularly defined - that is why
  756. // setType MUST exit immediately if it is called with the type not changing.
  757. // what if a parent is a variables_set of a different variable?
  758. // then I want to call Blockscad.assignVarTypes for that parent.
  759. var setterParent = Blockscad.hasParentOfType(callers[i], "procedures_defreturn");
  760. if (!setterParent)
  761. setterParent = Blockscad.hasParentOfType(callers[i],"variables_set");
  762. if (setterParent) {
  763. setTimeout(function() {
  764. // console.log("this caller function is inside a setter. Set its type to: ",type);
  765. setterParent.setType(type);
  766. }, 0);
  767. }
  768. else {
  769. // if the caller was inside a non setter, I still want to type that parent.
  770. var parent = false;
  771. if (callers[i])
  772. parent = callers[i].getParent();
  773. if (parent) {
  774. Blockscad.assignBlockTypes(parent)
  775. }
  776. }
  777. }
  778. } // end of going through all callers to set their types.
  779. // turn off event grouping
  780. Blockly.Events.setGroup(false);
  781. // handle backlighting and warning text - do this later so that
  782. // the bumping process itself (which now selects and deselects the blocks) doesn't
  783. // just immediately turn the backlighting off.
  784. for (var k = 0; k < numBumped.length; k++) {
  785. numBumped[k].backlight();
  786. this.backlightBlocks.push(numBumped[k].id);
  787. // finally, set a warning message on the procedure definition that counts how many callers were bumped.
  788. var text = '';
  789. text += Blockscad.Msg.BLOCKS_BUMPED_OUT_TYPES.replace("%1", numBumped.length).replace("%2", parentAccepts).replace("%3",type);
  790. this.setWarningText(text);
  791. }
  792. },
  793. setStatements_: Blockly.Blocks['procedures_defnoreturn'].setStatements_,
  794. updateParams_: Blockly.Blocks['procedures_defnoreturn'].updateParams_,
  795. mutationToDom: Blockly.Blocks['procedures_defnoreturn'].mutationToDom,
  796. domToMutation: Blockly.Blocks['procedures_defnoreturn'].domToMutation,
  797. decompose: Blockly.Blocks['procedures_defnoreturn'].decompose,
  798. compose: Blockly.Blocks['procedures_defnoreturn'].compose,
  799. /**
  800. * Return the signature of this procedure definition.
  801. * @return {!Array} Tuple containing three elements:
  802. * - the name of the defined procedure,
  803. * - a list of all its arguments,
  804. * - that it DOES have a return value.
  805. * @this Blockly.Block
  806. */
  807. getProcedureDef: function() {
  808. return [this.getFieldValue('NAME'), this.arguments_, true];
  809. },
  810. getVars: Blockly.Blocks['procedures_defnoreturn'].getVars,
  811. renameVar: Blockly.Blocks['procedures_defnoreturn'].renameVar,
  812. customContextMenu: Blockly.Blocks['procedures_defnoreturn'].customContextMenu,
  813. callType_: 'procedures_callreturn'
  814. };
  815. Blockly.Blocks['procedures_mutatorcontainer'] = {
  816. /**
  817. * Mutator block for procedure container.
  818. * @this Blockly.Block
  819. */
  820. init: function() {
  821. this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
  822. this.appendDummyInput()
  823. .appendField(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE);
  824. this.appendStatementInput('STACK');
  825. this.appendDummyInput('STATEMENT_INPUT')
  826. .appendField(Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS)
  827. .appendField(new Blockly.FieldCheckbox('TRUE'), 'STATEMENTS');
  828. this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);
  829. this.contextMenu = false;
  830. }
  831. };
  832. Blockly.Blocks['procedures_mutatorarg'] = {
  833. /**
  834. * Mutator block for procedure argument.
  835. * @this Blockly.Block
  836. */
  837. init: function() {
  838. this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
  839. this.appendDummyInput()
  840. .appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE)
  841. .appendField(new Blockly.FieldTextInput('x', this.validator_), 'NAME');
  842. this.setPreviousStatement(true);
  843. this.setNextStatement(true);
  844. this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);
  845. this.contextMenu = false;
  846. },
  847. /**
  848. * Obtain a valid name for the procedure.
  849. * Merge runs of whitespace. Strip leading and trailing whitespace.
  850. * Beyond this, all names are legal.
  851. * @param {string} newVar User-supplied name.
  852. * @return {?string} Valid name, or null if a name was not specified.
  853. * @private
  854. * @this Blockly.Block
  855. */
  856. validator_: function(newVar) {
  857. newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
  858. return newVar || null;
  859. }
  860. };
  861. Blockly.Blocks['procedures_callnoreturn'] = {
  862. /**
  863. * Block for calling a procedure with no return value.
  864. * @this Blockly.Block
  865. */
  866. init: function() {
  867. this.category = 'UNKNOWN'; // for blocksCAD
  868. this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL);
  869. this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
  870. this.appendDummyInput('TOPROW')
  871. .appendField(this.id, 'NAME');
  872. this.setPreviousStatement(true);
  873. //this.setNextStatement(true); // for Blockscad, we don't want this to have a next. Breaks difference.
  874. // Tooltip is set in domToMutation.
  875. this.arguments_ = [];
  876. this.quarkConnections_ = {};
  877. this.quarkIds_ = null;
  878. this.setType();
  879. },
  880. /**
  881. * on being added, this will be called to set the parent procedure type for BlocksCAD
  882. */
  883. setType: function() { // for blockscad - jayod
  884. var parent = Blockly.Procedures.getDefinition(this.getProcedureCall(),this.workspace);
  885. // console.log("in caller's setType func. defined by: ",parent);
  886. if (parent) {
  887. var myType = parent.myType_;
  888. // console.log("found a parent procedure with type: ",parent.myType_);
  889. if (myType) {
  890. this.previousConnection.setCheck(myType);
  891. //this.nextConnection.setCheck(myType); // no more next connection
  892. if (myType == 'CSG' || myType == ['CSG'])
  893. this.category = 'PRIMITIVE_CSG'
  894. else if (myType == 'CAG' || myType == ['CAG'])
  895. this.category = 'PRIMITIVE_CAG';
  896. else this.category = 'UNKNOWN';
  897. }
  898. }
  899. },
  900. /**
  901. * Returns the name of the procedure this block calls.
  902. * @return {string} Procedure name.
  903. * @this Blockly.Block
  904. */
  905. getProcedureCall: function() {
  906. // The NAME field is guaranteed to exist, null will never be returned.
  907. return /** @type {string} */ (this.getFieldValue('NAME'));
  908. },
  909. /**
  910. * Notification that a procedure is renaming.
  911. * If the name matches this block's procedure, rename it.
  912. * @param {string} oldName Previous name of procedure.
  913. * @param {string} newName Renamed procedure.
  914. * @this Blockly.Block
  915. */
  916. renameProcedure: function(oldName, newName) {
  917. if (Blockly.Names.equals(oldName, this.getProcedureCall())) {
  918. this.setFieldValue(newName, 'NAME');
  919. this.setTooltip(
  920. (this.outputConnection ? Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP :
  921. Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP)
  922. .replace('%1', newName));
  923. }
  924. },
  925. /**
  926. * Notification that the procedure's parameters have changed.
  927. * @param {!Array.<string>} paramNames New param names, e.g. ['x', 'y', 'z'].
  928. * @param {!Array.<string>} paramIds IDs of params (consistent for each
  929. * parameter through the life of a mutator, regardless of param renaming),
  930. * e.g. ['piua', 'f8b_', 'oi.o'].
  931. * @private
  932. * @this Blockly.Block
  933. */
  934. setProcedureParameters_: function(paramNames, paramIds) {
  935. // Data structures:
  936. // this.arguments = ['x', 'y']
  937. // Existing param names.
  938. // this.quarkConnections_ {piua: null, f8b_: Blockly.Connection}
  939. // Look-up of paramIds to connections plugged into the call block.
  940. // this.quarkIds_ = ['piua', 'f8b_']
  941. // Existing param IDs.
  942. // Note that quarkConnections_ may include IDs that no longer exist, but
  943. // which might reappear if a param is reattached in the mutator.
  944. var defBlock = Blockly.Procedures.getDefinition(this.getProcedureCall(),
  945. this.workspace);
  946. var mutatorOpen = defBlock && defBlock.mutator &&
  947. defBlock.mutator.isVisible();
  948. if (!mutatorOpen) {
  949. this.quarkConnections_ = {};
  950. this.quarkIds_ = null;
  951. }
  952. if (!paramIds) {
  953. // Reset the quarks (a mutator is about to open).
  954. return;
  955. }
  956. if (goog.array.equals(this.arguments_, paramNames)) {
  957. // No change.
  958. this.quarkIds_ = paramIds;
  959. return;
  960. }
  961. if (paramIds.length != paramNames.length) {
  962. throw 'Error: paramNames and paramIds must be the same length.';
  963. }
  964. this.setCollapsed(false);
  965. if (!this.quarkIds_) {
  966. // Initialize tracking for this block.
  967. this.quarkConnections_ = {};
  968. if (paramNames.join('\n') == this.arguments_.join('\n')) {
  969. // No change to the parameters, allow quarkConnections_ to be
  970. // populated with the existing connections.
  971. this.quarkIds_ = paramIds;
  972. } else {
  973. this.quarkIds_ = [];
  974. }
  975. }
  976. // Switch off rendering while the block is rebuilt.
  977. var savedRendered = this.rendered;
  978. this.rendered = false;
  979. // Update the quarkConnections_ with existing connections.
  980. for (var i = 0; i < this.arguments_.length; i++) {
  981. var input = this.getInput('ARG' + i);
  982. if (input) {
  983. var connection = input.connection.targetConnection;
  984. this.quarkConnections_[this.quarkIds_[i]] = connection;
  985. if (mutatorOpen && connection &&
  986. paramIds.indexOf(this.quarkIds_[i]) == -1) {
  987. // This connection should no longer be attached to this block.
  988. connection.disconnect();
  989. connection.getSourceBlock().bumpNeighbours_();
  990. }
  991. }
  992. }
  993. // Rebuild the block's arguments.
  994. this.arguments_ = [].concat(paramNames);
  995. this.updateShape_();
  996. this.quarkIds_ = paramIds;
  997. // Reconnect any child blocks.
  998. if (this.quarkIds_) {
  999. for (var i = 0; i < this.arguments_.length; i++) {
  1000. var quarkId = this.quarkIds_[i];
  1001. if (quarkId in this.quarkConnections_) {
  1002. var connection = this.quarkConnections_[quarkId];
  1003. if (!Blockly.Mutator.reconnect(connection, this, 'ARG' + i)) {
  1004. // Block no longer exists or has been attached elsewhere.
  1005. delete this.quarkConnections_[quarkId];
  1006. }
  1007. }
  1008. }
  1009. }
  1010. // Restore rendering and show the changes.
  1011. this.rendered = savedRendered;
  1012. if (this.rendered) {
  1013. this.render();
  1014. }
  1015. },
  1016. /**
  1017. * Modify this block to have the correct number of arguments.
  1018. * @private
  1019. * @this Blockly.Block
  1020. */
  1021. updateShape_: function() {
  1022. for (var i = 0; i < this.arguments_.length; i++) {
  1023. var field = this.getField('ARGNAME' + i);
  1024. if (field) {
  1025. // Ensure argument name is up to date.
  1026. // The argument name field is deterministic based on the mutation,
  1027. // no need to fire a change event.
  1028. Blockly.Events.disable();
  1029. try {
  1030. field.setValue(this.arguments_[i]);
  1031. } finally {
  1032. Blockly.Events.enable();
  1033. }
  1034. } else {
  1035. // Add new input.
  1036. field = new Blockly.FieldLabel(this.arguments_[i]);
  1037. var input = this.appendValueInput('ARG' + i)
  1038. .setAlign(Blockly.ALIGN_RIGHT)
  1039. .appendField(field, 'ARGNAME' + i);
  1040. input.init();
  1041. }
  1042. }
  1043. // Remove deleted inputs.
  1044. while (this.getInput('ARG' + i)) {
  1045. this.removeInput('ARG' + i);
  1046. i++;
  1047. }
  1048. // Add 'with:' if there are parameters, remove otherwise.
  1049. var topRow = this.getInput('TOPROW');
  1050. if (topRow) {
  1051. if (this.arguments_.length) {
  1052. if (!this.getField('WITH')) {
  1053. topRow.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS, 'WITH');
  1054. topRow.init();
  1055. }
  1056. } else {
  1057. if (this.getField('WITH')) {
  1058. topRow.removeField('WITH');
  1059. }
  1060. }
  1061. }
  1062. },
  1063. /**
  1064. * Create XML to represent the (non-editable) name and arguments.
  1065. * @return {!Element} XML storage element.
  1066. * @this Blockly.Block
  1067. */
  1068. mutationToDom: function() {
  1069. var container = document.createElement('mutation');
  1070. container.setAttribute('name', this.getProcedureCall());
  1071. for (var i = 0; i < this.arguments_.length; i++) {
  1072. var parameter = document.createElement('arg');
  1073. parameter.setAttribute('name', this.arguments_[i]);
  1074. container.appendChild(parameter);
  1075. }
  1076. return container;
  1077. },
  1078. /**
  1079. * Parse XML to restore the (non-editable) name and parameters.
  1080. * @param {!Element} xmlElement XML storage element.
  1081. * @this Blockly.Block
  1082. */
  1083. domToMutation: function(xmlElement) {
  1084. var name = xmlElement.getAttribute('name');
  1085. this.renameProcedure(this.getProcedureCall(), name);
  1086. var args = [];
  1087. var paramIds = [];
  1088. for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
  1089. if (childNode.nodeName.toLowerCase() == 'arg') {
  1090. args.push(childNode.getAttribute('name'));
  1091. paramIds.push(childNode.getAttribute('paramId'));
  1092. }
  1093. }
  1094. this.setProcedureParameters_(args, paramIds);
  1095. },
  1096. /**
  1097. * Notification that a variable is renaming.
  1098. * If the name matches one of this block's variables, rename it.
  1099. * @param {string} oldName Previous name of variable.
  1100. * @param {string} newName Renamed variable.
  1101. * @this Blockly.Block
  1102. */
  1103. renameVar: function(oldName, newName) {
  1104. for (var i = 0; i < this.arguments_.length; i++) {
  1105. if (Blockly.Names.equals(oldName, this.arguments_[i])) {
  1106. this.arguments_[i] = newName;
  1107. this.getField('ARGNAME' + i).setValue(newName);
  1108. }
  1109. }
  1110. },
  1111. /**
  1112. * Procedure calls cannot exist without the corresponding procedure
  1113. * definition. Enforce this link whenever an event is fired.
  1114. * @this Blockly.Block
  1115. */
  1116. onchange: function(event) {
  1117. if (!this.workspace || this.workspace.isFlyout) {
  1118. // Block is deleted or is in a flyout.
  1119. return;
  1120. }
  1121. if (event.type == Blockly.Events.CREATE &&
  1122. event.ids.indexOf(this.id) != -1) {
  1123. // Look for the case where a procedure call was created (usually through
  1124. // paste) and there is no matching definition. In this case, create
  1125. // an empty definition block with the correct signature.
  1126. var name = this.getProcedureCall();
  1127. var def = Blockly.Procedures.getDefinition(name, this.workspace);
  1128. if (def && (def.type != this.defType_ ||
  1129. JSON.stringify(def.arguments_) != JSON.stringify(this.arguments_))) {
  1130. // The signatures don't match.
  1131. def = null;
  1132. }
  1133. if (!def) {
  1134. Blockly.Events.setGroup(event.group);
  1135. /**
  1136. * Create matching definition block.
  1137. * <xml>
  1138. * <block type="procedures_defreturn" x="10" y="20">
  1139. * <mutation name="test">
  1140. * <arg name="x"></arg>
  1141. * </mutation>
  1142. * <field name="NAME">test</field>
  1143. * </block>
  1144. * </xml>
  1145. */
  1146. var xml = goog.dom.createDom('xml');
  1147. var block = goog.dom.createDom('block');
  1148. block.setAttribute('type', this.defType_);
  1149. var xy = this.getRelativeToSurfaceXY();
  1150. var x = xy.x + Blockly.SNAP_RADIUS * (this.RTL ? -1 : 1);
  1151. var y = xy.y + Blockly.SNAP_RADIUS * 2;
  1152. block.setAttribute('x', x);
  1153. block.setAttribute('y', y);
  1154. var mutation = this.mutationToDom();
  1155. block.appendChild(mutation);
  1156. var field = goog.dom.createDom('field');
  1157. field.setAttribute('name', 'NAME');
  1158. field.appendChild(document.createTextNode(this.getProcedureCall()));
  1159. block.appendChild(field);
  1160. xml.appendChild(block);
  1161. Blockly.Xml.domToWorkspace(xml, this.workspace);
  1162. Blockly.Events.setGroup(false);
  1163. }
  1164. } else if (event.type == Blockly.Events.DELETE) {
  1165. // Look for the case where a procedure definition has been deleted,
  1166. // leaving this block (a procedure call) orphaned. In this case, delete
  1167. // the orphan.
  1168. var name = this.getProcedureCall();
  1169. var def = Blockly.Procedures.getDefinition(name, this.workspace);
  1170. if (!def) {
  1171. Blockly.Events.setGroup(event.group);
  1172. this.dispose(true, false);
  1173. Blockly.Events.setGroup(false);
  1174. }
  1175. }
  1176. },
  1177. /**
  1178. * Add menu option to find the definition block for this call.
  1179. * @param {!Array} options List of menu options to add to.
  1180. * @this Blockly.Block
  1181. */
  1182. customContextMenu: function(options) {
  1183. var option = {enabled: true};
  1184. option.text = Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;
  1185. var name = this.getProcedureCall();
  1186. var workspace = this.workspace;
  1187. option.callback = function() {
  1188. var def = Blockly.Procedures.getDefinition(name, workspace);
  1189. workspace.clearBacklight();
  1190. // def && def.backlight();
  1191. def && def.select();
  1192. };
  1193. options.push(option);
  1194. // for BlocksCAD,
  1195. var option = {enabled: true};
  1196. var name = this.getProcedureCall();
  1197. option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1", name);
  1198. var workspace = this.workspace;
  1199. option.callback = function() {
  1200. var def = Blockly.Procedures.getDefinition(name, workspace);
  1201. if (def) {
  1202. var callers = Blockly.Procedures.getCallers(name, workspace);
  1203. workspace.clearBacklight();
  1204. Blockly.selected.unselect();
  1205. for (var i = 0; callers && i < callers.length; i++) {
  1206. callers[i] && callers[i].backlight();
  1207. // if caller block is in a collapsed parent, highlight collapsed parent too
  1208. var others = callers[i].collapsedParents();
  1209. if (others)
  1210. for (var j=0; j < others.length; j++)
  1211. others[j].backlight();
  1212. }
  1213. }
  1214. };
  1215. options.push(option);
  1216. },
  1217. defType_: 'procedures_defnoreturn'
  1218. };
  1219. Blockly.Blocks['procedures_callreturn'] = {
  1220. /**
  1221. * Block for calling a procedure with a return value.
  1222. * @this Blockly.Block
  1223. */
  1224. init: function() {
  1225. this.category = 'UNKNOWN'; // for blockscad - jayod
  1226. this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);
  1227. this.setColour(Blockscad.Toolbox.HEX_PROCEDURE);
  1228. this.appendDummyInput('TOPROW')
  1229. .appendField('', 'NAME');
  1230. this.setOutput(true);
  1231. // Tooltip is set in domToMutation.
  1232. this.arguments_ = [];
  1233. this.quarkConnections_ = {};
  1234. this.quarkIds_ = null;
  1235. // set the initial type.
  1236. this.setType();
  1237. },
  1238. /**
  1239. * on being added, this will be called to get the parent procedure type for BlocksCAD
  1240. */
  1241. setType: function() { // for blockscad - jayod
  1242. var parent = Blockly.Procedures.getDefinition(this.getFieldValue('NAME'),this.workspace);
  1243. if (parent) {
  1244. var myType = parent.myType_;
  1245. if (myType) {
  1246. // console.log("setting type for new function caller to:",myType);
  1247. this.outputConnection.setCheck(myType);
  1248. if (myType == 'Number')
  1249. this.category = 'NUMBER'
  1250. else if (myType == 'Boolean')
  1251. this.category = 'BOOLEAN';
  1252. else this.category = 'UNKNOWN';
  1253. }
  1254. }
  1255. },
  1256. getProcedureCall: Blockly.Blocks['procedures_callnoreturn'].getProcedureCall,
  1257. renameProcedure: Blockly.Blocks['procedures_callnoreturn'].renameProcedure,
  1258. setProcedureParameters_:
  1259. Blockly.Blocks['procedures_callnoreturn'].setProcedureParameters_,
  1260. updateShape_: Blockly.Blocks['procedures_callnoreturn'].updateShape_,
  1261. mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom,
  1262. domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation,
  1263. renameVar: Blockly.Blocks['procedures_callnoreturn'].renameVar,
  1264. onchange: Blockly.Blocks['procedures_callnoreturn'].onchange,
  1265. /**
  1266. * Add menu option to find the definition block for this call.
  1267. * @param {!Array} options List of menu options to add to.
  1268. * @this Blockly.Block
  1269. */
  1270. customContextMenu: function(options) {
  1271. var option = {enabled: true};
  1272. option.text = Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;
  1273. var name = this.getProcedureCall();
  1274. var workspace = this.workspace;
  1275. option.callback = function() {
  1276. var def = Blockly.Procedures.getDefinition(name, workspace);
  1277. workspace.clearBacklight();
  1278. def && def.backlight();
  1279. };
  1280. options.push(option);
  1281. // for BlocksCAD,
  1282. var name = this.getProcedureCall();
  1283. var option = {enabled: true};
  1284. option.text = Blockscad.Msg.HIGHLIGHT_INSTANCES.replace("%1",name);
  1285. var workspace = this.workspace;
  1286. option.callback = function() {
  1287. var def = Blockly.Procedures.getDefinition(name, workspace);
  1288. if (def) {
  1289. var callers = Blockly.Procedures.getCallers(name, workspace);
  1290. workspace.clearBacklight();
  1291. Blockly.selected.unselect();
  1292. for (var i = 0; callers && i < callers.length; i++) {
  1293. callers[i] && callers[i].backlight();
  1294. // if caller block is in a collapsed parent, highlight collapsed parent too
  1295. var others = callers[i].collapsedParents();
  1296. if (others)
  1297. for (var j=0; j < others.length; j++)
  1298. others[j].backlight();
  1299. }
  1300. }
  1301. };
  1302. options.push(option);
  1303. },
  1304. defType_: 'procedures_defreturn'
  1305. };
  1306. // Blockly.Blocks['procedures_ifreturn'] = { // commented out for Blockscad
  1307. // /**
  1308. // * Block for conditionally returning a value from a procedure.
  1309. // * @this Blockly.Block
  1310. // */
  1311. // init: function() {
  1312. // this.setHelpUrl('http://c2.com/cgi/wiki?GuardClause');
  1313. // this.setColour(290);
  1314. // this.appendValueInput('CONDITION')
  1315. // .setCheck('Boolean')
  1316. // .appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
  1317. // this.appendValueInput('VALUE')
  1318. // .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  1319. // this.setInputsInline(true);
  1320. // this.setPreviousStatement(true);
  1321. // this.setNextStatement(true);
  1322. // this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);
  1323. // this.hasReturnValue_ = true;
  1324. // },
  1325. // /**
  1326. // * Create XML to represent whether this block has a return value.
  1327. // * @return {Element} XML storage element.
  1328. // * @this Blockly.Block
  1329. // */
  1330. // mutationToDom: function() {
  1331. // var container = document.createElement('mutation');
  1332. // container.setAttribute('value', Number(this.hasReturnValue_));
  1333. // return container;
  1334. // },
  1335. // /**
  1336. // * Parse XML to restore whether this block has a return value.
  1337. // * @param {!Element} xmlElement XML storage element.
  1338. // * @this Blockly.Block
  1339. // */
  1340. // domToMutation: function(xmlElement) {
  1341. // var value = xmlElement.getAttribute('value');
  1342. // this.hasReturnValue_ = (value == 1);
  1343. // if (!this.hasReturnValue_) {
  1344. // this.removeInput('VALUE');
  1345. // this.appendDummyInput('VALUE')
  1346. // .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  1347. // }
  1348. // },
  1349. // /**
  1350. // * Called whenever anything on the workspace changes.
  1351. // * Add warning if this flow block is not nested inside a loop.
  1352. // * @this Blockly.Block
  1353. // */
  1354. // onchange: function() {
  1355. // if (!this.workspace) {
  1356. // // Block has been deleted.
  1357. // return;
  1358. // }
  1359. // var legal = false;
  1360. // // Is the block nested in a procedure?
  1361. // var block = this;
  1362. // do {
  1363. // if (block.type == 'procedures_defnoreturn' ||
  1364. // block.type == 'procedures_defreturn') {
  1365. // legal = true;
  1366. // break;
  1367. // }
  1368. // block = block.getSurroundParent();
  1369. // } while (block);
  1370. // if (legal) {
  1371. // // If needed, toggle whether this block has a return value.
  1372. // if (block.type == 'procedures_defnoreturn' && this.hasReturnValue_) {
  1373. // this.removeInput('VALUE');
  1374. // this.appendDummyInput('VALUE')
  1375. // .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  1376. // this.hasReturnValue_ = false;
  1377. // } else if (block.type == 'procedures_defreturn' &&
  1378. // !this.hasReturnValue_) {
  1379. // this.removeInput('VALUE');
  1380. // this.appendValueInput('VALUE')
  1381. // .appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
  1382. // this.hasReturnValue_ = true;
  1383. // }
  1384. // this.setWarningText(null);
  1385. // } else {
  1386. // this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING);
  1387. // }
  1388. // }
  1389. // };