source: EcnlProtoTool/trunk/webapp/webmrbc/Blocks/Procedures.cs@ 425

Last change on this file since 425 was 425, checked in by coas-nagasima, 4 years ago

Visual Studio 2019 と Bridge.NET でビルドできるよう更新。

  • Property svn:eol-style set to native
  • Property svn:mime-type set to text/x-csharp;charset=UTF-8
File size: 31.7 KB
Line 
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/**
22 * @fileoverview Procedure blocks for Blockly.
23 * @author fraser@google.com (Neil Fraser)
24 */
25using System;
26using System.Linq;
27using System.Collections.Generic;
28using Bridge;
29using Bridge.Html5;
30
31namespace WebMrbc
32{
33 public abstract class ProceduresDefBlock : Block
34 {
35 protected string callType_;
36 string[] paramIds_;
37 bool hasStatements_;
38 internal string[] arguments_;
39 public Connection statementConnection_;
40
41 public ProceduresDefBlock(string type)
42 : base(type)
43 {
44 }
45
46 /**
47 * Add or remove the statement block from this function definition.
48 * @param {boolean} hasStatements True if a statement block is needed.
49 * @this Blockly.Block
50 */
51 public void setStatements_(bool hasStatements)
52 {
53 if (this.hasStatements_ == hasStatements) {
54 return;
55 }
56 if (hasStatements) {
57 this.appendStatementInput("STACK")
58 .appendField(Msg.PROCEDURES_DEFNORETURN_DO);
59 if (this.getInput("RETURN") != null) {
60 this.moveInputBefore("STACK", "RETURN");
61 }
62 }
63 else {
64 this.removeInput("STACK", true);
65 }
66 this.hasStatements_ = hasStatements;
67 }
68
69 /**
70 * Update the display of parameters for this procedure definition block.
71 * Display a warning if there are duplicately named parameters.
72 * @private
73 * @this Blockly.Block
74 */
75 public void updateParams_()
76 {
77 // Check for duplicated arguments.
78 var badArg = false;
79 var hash = new { };
80 for (var i = 0; i < this.arguments_.Length; i++) {
81 if (Script.Get<bool>(hash, "arg_" + this.arguments_[i].ToLower())) {
82 badArg = true;
83 break;
84 }
85 hash["arg_" + this.arguments_[i].ToLower()] = true;
86 }
87 if (badArg) {
88 this.setWarningText(Msg.PROCEDURES_DEF_DUPLICATE_WARNING);
89 }
90 else {
91 this.setWarningText(null);
92 }
93 // Merge the arguments into a human-readable list.
94 var paramString = "";
95 if (this.arguments_.Length != 0) {
96 paramString = Msg.PROCEDURES_BEFORE_PARAMS +
97 " " + this.arguments_.Join(", ");
98 }
99 // The params field is deterministic based on the mutation,
100 // no need to fire a change event.
101 Blockly.Events.disable();
102 try {
103 this.setFieldValue(paramString, "PARAMS");
104 }
105 finally {
106 Blockly.Events.enable();
107 }
108 }
109
110 /**
111 * Create XML to represent the argument inputs.
112 * @param {=boolean} opt_paramIds If true include the IDs of the parameter
113 * quarks. Used by Blockly.Procedures.mutateCallers for reconnection.
114 * @return {!Element} XML storage element.
115 * @this Blockly.Block
116 */
117 public Element mutationToDom(bool opt_paramIds)
118 {
119 var container = Document.CreateElement("mutation");
120 if (opt_paramIds) {
121 container.SetAttribute("name", this.getFieldValue("NAME"));
122 }
123 for (var i = 0; i < this.arguments_.Length; i++) {
124 var parameter = Document.CreateElement("arg");
125 parameter.SetAttribute("name", this.arguments_[i]);
126 if (opt_paramIds && this.paramIds_ != null) {
127 parameter.SetAttribute("paramId", this.paramIds_[i]);
128 }
129 container.AppendChild(parameter);
130 }
131
132 // Save whether the statement input is visible.
133 if (!this.hasStatements_) {
134 container.SetAttribute("statements", "false");
135 }
136 return container;
137 }
138
139 /**
140 * Parse XML to restore the argument inputs.
141 * @param {!Element} xmlElement XML storage element.
142 * @this Blockly.Block
143 */
144 public void domToMutation(Element xmlElement)
145 {
146 this.arguments_ = new string[0];
147 Element childNode;
148 for (var i = 0; (childNode = (dynamic)xmlElement.ChildNodes[i]) != null; i++) {
149 if (childNode.NodeName.ToLower() == "arg") {
150 this.arguments_.Push(childNode.GetAttribute("name"));
151 }
152 }
153 this.updateParams_();
154 Blockly.Procedures.mutateCallers(this);
155
156 // Show or hide the statement input.
157 this.setStatements_(xmlElement.GetAttribute("statements") != "false");
158 }
159
160 /**
161 * Populate the mutator's dialog with this block's components.
162 * @param {!Workspace} workspace Mutator's workspace.
163 * @return {!Blockly.Block} Root block in mutator.
164 * @this Blockly.Block
165 */
166 public Block decompose(Workspace workspace)
167 {
168 var containerBlock = workspace.newBlock(ProceduresMutatorcontainerBlock.type_name);
169 containerBlock.initSvg();
170
171 // Check/uncheck the allow statement box.
172 if (this.getInput("RETURN") != null) {
173 containerBlock.setFieldValue(this.hasStatements_ ? "TRUE" : "FALSE", "STATEMENTS");
174 }
175 else {
176 containerBlock.getInput("STATEMENT_INPUT").setVisible(false);
177 }
178
179 // Parameter list.
180 var connection = containerBlock.getInput("STACK").connection;
181 for (var i = 0; i < this.arguments_.Length; i++) {
182 var paramBlock = (ProceduresMutatorargBlock)workspace.newBlock(ProceduresMutatorargBlock.type_name);
183 paramBlock.initSvg();
184 paramBlock.setFieldValue(this.arguments_[i], "NAME");
185 // Store the old location.
186 paramBlock.oldLocation = i;
187 connection.connect(paramBlock.previousConnection);
188 connection = paramBlock.nextConnection;
189 }
190 // Initialize procedure's callers with blank IDs.
191 Blockly.Procedures.mutateCallers(this);
192 return containerBlock;
193 }
194
195 /**
196 * Reconfigure this block based on the mutator dialog's components.
197 * @param {!Blockly.Block} containerBlock Root block in mutator.
198 * @this Blockly.Block
199 */
200 public void compose(Block containerBlock)
201 {
202 // Parameter list.
203 this.arguments_ = new string[0];
204 this.paramIds_ = new string[0];
205 var paramBlock = containerBlock.getInputTargetBlock("STACK");
206 while (paramBlock != null) {
207 this.arguments_.Push(paramBlock.getFieldValue("NAME"));
208 this.paramIds_.Push(paramBlock.id);
209 paramBlock = (paramBlock.nextConnection != null) ?
210 paramBlock.nextConnection.targetBlock() : null;
211 }
212 this.updateParams_();
213 Blockly.Procedures.mutateCallers(this);
214
215 // Show/hide the statement input.
216 var hasStatements_ = containerBlock.getFieldValue("STATEMENTS");
217 if (hasStatements_ != null) {
218 var hasStatements = hasStatements_ == "TRUE";
219 if (this.hasStatements_ != hasStatements) {
220 if (hasStatements) {
221 this.setStatements_(true);
222 // Restore the stack, if one was saved.
223 Mutator.reconnect(this.statementConnection_, this, "STACK");
224 this.statementConnection_ = null;
225 }
226 else {
227 // Save the stack, then disconnect it.
228 var stackConnection = this.getInput("STACK").connection;
229 this.statementConnection_ = stackConnection.targetConnection;
230 if (this.statementConnection_ != null) {
231 var stackBlock = stackConnection.targetBlock();
232 stackBlock.unplug();
233 stackBlock.bumpNeighbours_();
234 }
235 this.setStatements_(false);
236 }
237 }
238 }
239 }
240
241 /**
242 * Return all variables referenced by this block.
243 * @return {!Array.<string>} List of variable names.
244 * @this Blockly.Block
245 */
246 public override string[] getVars()
247 {
248 return this.arguments_;
249 }
250
251 /**
252 * Notification that a variable is renaming.
253 * If the name matches one of this block's variables, rename it.
254 * @param {string} oldName Previous name of variable.
255 * @param {string} newName Renamed variable.
256 * @this Blockly.Block
257 */
258 public override void renameVar(string oldName, string newName)
259 {
260 var change = false;
261 for (var i = 0; i < this.arguments_.Length; i++) {
262 if (Blockly.Names.equals(oldName, this.arguments_[i])) {
263 this.arguments_[i] = newName;
264 change = true;
265 }
266 }
267 if (change) {
268 this.updateParams_();
269 // Update the mutator's variables if the mutator is open.
270 if (this.mutator.isVisible()) {
271 var blocks = this.mutator.workspace_.getAllBlocks();
272 Block block;
273 for (var i = 0; (block = blocks[i]) != null; i++) {
274 if (block.type == ProceduresMutatorargBlock.type_name &&
275 Blockly.Names.equals(oldName, block.getFieldValue("NAME"))) {
276 block.setFieldValue(newName, "NAME");
277 }
278 }
279 }
280 }
281 }
282
283 /**
284 * Add custom menu options to this block's context menu.
285 * @param {!Array} options List of menu options to add to.
286 * @this Blockly.Block
287 */
288 public void customContextMenu(object[] options)
289 {
290 // Add option to create caller.
291 var option = new ContextMenuOption() { enabled = true };
292 var name = this.getFieldValue("NAME");
293 option.text = Msg.PROCEDURES_CREATE_DO.Replace("%1", name);
294 var xmlMutation = goog.dom.createDom("mutation");
295 xmlMutation.SetAttribute("name", name);
296 for (var i = 0; i < this.arguments_.Length; i++) {
297 var xmlArg = goog.dom.createDom("arg");
298 xmlArg.SetAttribute("name", this.arguments_[i]);
299 xmlMutation.AppendChild(xmlArg);
300 }
301 var xmlBlock = goog.dom.createDom("block", null, xmlMutation);
302 xmlBlock.SetAttribute("type", this.callType_);
303 option.callback = ContextMenu.callbackFactory(this, xmlBlock);
304 options.Push(option);
305
306 // Add options to create getters for each parameter.
307 if (!this.isCollapsed()) {
308 for (var i = 0; i < this.arguments_.Length; i++) {
309 option = new ContextMenuOption() { enabled = true };
310 name = this.arguments_[i];
311 option.text = Msg.VARIABLES_SET_CREATE_GET.Replace("%1", name);
312 var xmlField = goog.dom.createDom("field", null, name);
313 xmlField.SetAttribute("name", "VAR");
314 xmlBlock = goog.dom.createDom("block", null, xmlField);
315 xmlBlock.SetAttribute("type", VariablesGetBlock.type_name);
316 option.callback = ContextMenu.callbackFactory(this, xmlBlock);
317 options.Push(option);
318 }
319 }
320 }
321
322 public abstract Tuple<string, string[], bool> getProcedureDef();
323 }
324
325 public class ProceduresDefnoreturnBlock : ProceduresDefBlock
326 {
327 public const string type_name = "procedures_defnoreturn";
328
329 public ProceduresDefnoreturnBlock()
330 : base(type_name)
331 {
332 callType_ = ProceduresCallnoreturnBlock.type_name;
333 }
334
335 /**
336 * Block for defining a procedure with no return value.
337 * @this Blockly.Block
338 */
339 public void init()
340 {
341 var nameField = new FieldTextInput(
342 Msg.PROCEDURES_DEFNORETURN_PROCEDURE);
343 nameField.setValidator((str) => { return Blockly.Procedures.rename(nameField, str); });
344 nameField.setSpellcheck(false);
345 this.appendDummyInput()
346 .appendField(Msg.PROCEDURES_DEFNORETURN_TITLE)
347 .appendField(nameField, "NAME")
348 .appendField("", "PARAMS");
349 this.setMutator(new Mutator(new[] { ProceduresMutatorargBlock.type_name }));
350 if ((this.workspace.options.comments ||
351 (this.workspace.options.parentWorkspace != null &&
352 this.workspace.options.parentWorkspace.options.comments)) &&
353 !String.IsNullOrEmpty(Msg.PROCEDURES_DEFNORETURN_COMMENT)) {
354 this.setCommentText(Msg.PROCEDURES_DEFNORETURN_COMMENT);
355 }
356 this.setColour(Blockly.Procedures.HUE);
357 this.setTooltip(Msg.PROCEDURES_DEFNORETURN_TOOLTIP);
358 this.setHelpUrl(Msg.PROCEDURES_DEFNORETURN_HELPURL);
359 this.arguments_ = new string[0];
360 this.setStatements_(true);
361 this.statementConnection_ = null;
362 }
363
364 /**
365 * Return the signature of this procedure definition.
366 * @return {!Array} Tuple containing three elements:
367 * - the name of the defined procedure,
368 * - a list of all its arguments,
369 * - that it DOES NOT have a return value.
370 * @this Blockly.Block
371 */
372 public override Tuple<string, string[], bool> getProcedureDef()
373 {
374 return new Tuple<string, string[], bool>(this.getFieldValue("NAME"), this.arguments_, false);
375 }
376 }
377
378 public class ProceduresDefreturnBlock : ProceduresDefBlock
379 {
380 public const string type_name = "procedures_defreturn";
381
382 public ProceduresDefreturnBlock()
383 : base(type_name)
384 {
385 callType_ = ProceduresCallreturnBlock.type_name;
386 }
387
388 /**
389 * Block for defining a procedure with a return value.
390 * @this Blockly.Block
391 */
392 public void init()
393 {
394 var nameField = new FieldTextInput(
395 Msg.PROCEDURES_DEFRETURN_PROCEDURE);
396 nameField.setValidator((str) => { return Blockly.Procedures.rename(nameField, str); });
397 nameField.setSpellcheck(false);
398 this.appendDummyInput()
399 .appendField(Msg.PROCEDURES_DEFRETURN_TITLE)
400 .appendField(nameField, "NAME")
401 .appendField("", "PARAMS");
402 this.appendValueInput("RETURN")
403 .setAlign(Blockly.ALIGN_RIGHT)
404 .appendField(Msg.PROCEDURES_DEFRETURN_RETURN);
405 this.setMutator(new Mutator(new[] { ProceduresMutatorargBlock.type_name }));
406 if ((this.workspace.options.comments ||
407 (this.workspace.options.parentWorkspace != null &&
408 this.workspace.options.parentWorkspace.options.comments)) &&
409 !String.IsNullOrEmpty(Msg.PROCEDURES_DEFRETURN_COMMENT)) {
410 this.setCommentText(Msg.PROCEDURES_DEFRETURN_COMMENT);
411 }
412 this.setColour(Blockly.Procedures.HUE);
413 this.setTooltip(Msg.PROCEDURES_DEFRETURN_TOOLTIP);
414 this.setHelpUrl(Msg.PROCEDURES_DEFRETURN_HELPURL);
415 this.arguments_ = new string[0];
416 this.setStatements_(true);
417 this.statementConnection_ = null;
418 }
419
420 /**
421 * Return the signature of this procedure definition.
422 * @return {!Array} Tuple containing three elements:
423 * - the name of the defined procedure,
424 * - a list of all its arguments,
425 * - that it DOES have a return value.
426 * @this Blockly.Block
427 */
428 public override Tuple<string, string[], bool> getProcedureDef()
429 {
430 return new Tuple<string, string[], bool>(this.getFieldValue("NAME"), this.arguments_, true);
431 }
432 }
433
434 public class ProceduresMutatorcontainerBlock : Block
435 {
436 public const string type_name = "procedures_mutatorcontainer";
437
438 public ProceduresMutatorcontainerBlock()
439 : base(type_name)
440 {
441 }
442
443 /**
444 * Mutator block for procedure container.
445 * @this Blockly.Block
446 */
447 public void init()
448 {
449 this.appendDummyInput()
450 .appendField(Msg.PROCEDURES_MUTATORCONTAINER_TITLE);
451 this.appendStatementInput("STACK");
452 this.appendDummyInput("STATEMENT_INPUT")
453 .appendField(Msg.PROCEDURES_ALLOW_STATEMENTS)
454 .appendField(new FieldCheckbox("TRUE"), "STATEMENTS");
455 this.setColour(Blockly.Procedures.HUE);
456 this.setTooltip(Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);
457 this.contextMenu = false;
458 }
459 }
460
461 [IgnoreCast]
462 public class ProceduresMutatorargBlock : Block
463 {
464 public const string type_name = "procedures_mutatorarg";
465 public int oldLocation;
466
467 public ProceduresMutatorargBlock()
468 : base(type_name)
469 {
470 }
471
472 /**
473 * Mutator block for procedure argument.
474 * @this Blockly.Block
475 */
476 public void init()
477 {
478 var field = new FieldTextInput("x", this.validator_);
479 this.appendDummyInput()
480 .appendField(Msg.PROCEDURES_MUTATORARG_TITLE)
481 .appendField(field, "NAME");
482 this.setPreviousStatement(true);
483 this.setNextStatement(true);
484 this.setColour(Blockly.Procedures.HUE);
485 this.setTooltip(Msg.PROCEDURES_MUTATORARG_TOOLTIP);
486 this.contextMenu = false;
487
488 // Create the default variable when we drag the block in from the flyout.
489 // Have to do this after installing the field on the block.
490 field.onFinishEditing_ = this.createNewVar_;
491 field.onFinishEditing_("x");
492 }
493
494 /**
495 * Obtain a valid name for the procedure.
496 * Merge runs of whitespace. Strip leading and trailing whitespace.
497 * Beyond this, all names are legal.
498 * @param {string} newVar User-supplied name.
499 * @return {?string} Valid name, or null if a name was not specified.
500 * @private
501 * @this Blockly.Block
502 */
503 public string validator_(string newVar)
504 {
505 newVar = newVar.Replace(new RegExp("[\\s\\xa0]+", "g"), " ").Replace(new RegExp("^ | $", "g"), "");
506 return newVar;
507 }
508
509 /**
510 * Called when focusing away from the text field.
511 * Creates a new variable with this name.
512 * @param {string} newText The new variable name.
513 * @private
514 * @this FieldTextInput
515 */
516 public void createNewVar_(string newText)
517 {
518 var source = this/*((FieldTextInput)this).sourceBlock_*/;
519 if (source != null && source.workspace != null && source.workspace.options != null
520 && source.workspace.options.parentWorkspace != null) {
521 source.workspace.options.parentWorkspace.createVariable(newText);
522 }
523 }
524 }
525
526 public class ProceduresCallBlock : Block
527 {
528 internal string[] arguments_;
529 protected Dictionary<string, Connection> quarkConnections_;
530 protected string[] quarkIds_;
531 protected bool rendered;
532 protected string defType_;
533
534 public ProceduresCallBlock(string type)
535 : base(type)
536 {
537 }
538
539 /**
540 * Returns the name of the procedure this block calls.
541 * @return {string} Procedure name.
542 * @this Blockly.Block
543 */
544 public string getProcedureCall()
545 {
546 // The NAME field is guaranteed to exist, null will never be returned.
547 return /** @type {string} */ (this.getFieldValue("NAME"));
548 }
549
550 /**
551 * Notification that a procedure is renaming.
552 * If the name matches this block's procedure, rename it.
553 * @param {string} oldName Previous name of procedure.
554 * @param {string} newName Renamed procedure.
555 * @this Blockly.Block
556 */
557 public void renameProcedure(string oldName, string newName)
558 {
559 if (Blockly.Names.equals(oldName, this.getProcedureCall())) {
560 this.setFieldValue(newName, "NAME");
561 this.setTooltip(
562 (this.outputConnection != null ? Msg.PROCEDURES_CALLRETURN_TOOLTIP :
563 Msg.PROCEDURES_CALLNORETURN_TOOLTIP)
564 .Replace("%1", newName));
565 }
566 }
567
568 /**
569 * Notification that the procedure's parameters have changed.
570 * @param {!Array.<string>} paramNames New param names, e.g. ["x", "y", "z"].
571 * @param {!Array.<string>} paramIds IDs of params (consistent for each
572 * parameter through the life of a mutator, regardless of param renaming),
573 * e.g. ["piua", "f8b_", "oi.o"].
574 * @private
575 * @this Blockly.Block
576 */
577 public void setProcedureParameters_(string[] paramNames, string[] paramIds)
578 {
579 // Data structures:
580 // this.arguments = ["x", "y"]
581 // Existing param names.
582 // this.quarkConnections_ {piua: null, f8b_: Blockly.Connection}
583 // Look-up of paramIds to connections plugged into the call block.
584 // this.quarkIds_ = ["piua", "f8b_"]
585 // Existing param IDs.
586 // Note that quarkConnections_ may include IDs that no longer exist, but
587 // which might reappear if a param is reattached in the mutator.
588 var defBlock = Blockly.Procedures.getDefinition(this.getProcedureCall(),
589 this.workspace);
590 var mutatorOpen = defBlock != null && defBlock.mutator != null &&
591 defBlock.mutator.isVisible();
592 if (!mutatorOpen) {
593 this.quarkConnections_ = new Dictionary<string, Connection>();
594 this.quarkIds_ = null;
595 }
596 if (paramIds == null) {
597 // Reset the quarks (a mutator is about to open).
598 return;
599 }
600 if (goog.array.equals(this.arguments_, paramNames)) {
601 // No change.
602 this.quarkIds_ = paramIds;
603 return;
604 }
605 if (paramIds.Length != paramNames.Length) {
606 throw new Exception("Error: paramNames and paramIds must be the same length.");
607 }
608 this.setCollapsed(false);
609 if (this.quarkIds_ == null) {
610 // Initialize tracking for this block.
611 this.quarkConnections_ = new Dictionary<string, Connection>();
612 if (paramNames.Join("\n") == this.arguments_.Join("\n")) {
613 // No change to the parameters, allow quarkConnections_ to be
614 // populated with the existing connections.
615 this.quarkIds_ = paramIds;
616 }
617 else {
618 this.quarkIds_ = new string[0];
619 }
620 }
621 // Switch off rendering while the block is rebuilt.
622 var savedRendered = this.rendered;
623 this.rendered = false;
624 // Update the quarkConnections_ with existing connections.
625 for (var i = 0; i < this.arguments_.Length; i++) {
626 var input = this.getInput("ARG" + i);
627 if (input != null) {
628 var connection = input.connection.targetConnection;
629 this.quarkConnections_[this.quarkIds_[i]] = connection;
630 if (mutatorOpen && connection != null &&
631 Array.IndexOf(paramIds, this.quarkIds_[i]) == -1) {
632 // This connection should no longer be attached to this block.
633 connection.disconnect();
634 connection.getSourceBlock().bumpNeighbours_();
635 }
636 }
637 }
638 // Rebuild the block's arguments.
639 this.arguments_ = (new string[0]).Concat(paramNames).ToArray();
640 this.updateShape_();
641 this.quarkIds_ = paramIds;
642 // Reconnect any child blocks.
643 if (this.quarkIds_ != null) {
644 for (var i = 0; i < this.arguments_.Length; i++) {
645 var quarkId = this.quarkIds_[i];
646 Connection connection;
647 if (this.quarkConnections_.TryGetValue(quarkId, out connection)) {
648 if (!Mutator.reconnect(connection, this, "ARG" + i)) {
649 // Block no longer exists or has been attached elsewhere.
650 Script.Delete(this.quarkConnections_[quarkId]);
651 }
652 }
653 }
654 }
655 // Restore rendering and show the changes.
656 this.rendered = savedRendered;
657 if (this.rendered) {
658 this.render();
659 }
660 }
661
662 /**
663 * Modify this block to have the correct number of arguments.
664 * @private
665 * @this Blockly.Block
666 */
667 private void updateShape_()
668 {
669 int i;
670 for (i = 0; i < this.arguments_.Length; i++) {
671 var field = this.getField("ARGNAME" + i);
672 if (field != null) {
673 // Ensure argument name is up to date.
674 // The argument name field is deterministic based on the mutation,
675 // no need to fire a change event.
676 Blockly.Events.disable();
677 try {
678 field.setValue(this.arguments_[i]);
679 }
680 finally {
681 Blockly.Events.enable();
682 }
683 }
684 else {
685 // Add new input.
686 field = new FieldLabel(this.arguments_[i]);
687 var input = this.appendValueInput("ARG" + i)
688 .setAlign(Blockly.ALIGN_RIGHT)
689 .appendField(field, "ARGNAME" + i);
690 input.init();
691 }
692 }
693 // Remove deleted inputs.
694 while (this.getInput("ARG" + i) != null) {
695 this.removeInput("ARG" + i);
696 i++;
697 }
698 // Add "with:" if there are parameters, remove otherwise.
699 var topRow = this.getInput("TOPROW");
700 if (topRow != null) {
701 if (this.arguments_.Length != 0) {
702 if (this.getField("WITH") == null) {
703 topRow.appendField(Msg.PROCEDURES_CALL_BEFORE_PARAMS, "WITH");
704 topRow.init();
705 }
706 }
707 else {
708 if (this.getField("WITH") != null) {
709 topRow.removeField("WITH");
710 }
711 }
712 }
713 }
714
715 /**
716 * Create XML to represent the (non-editable) name and arguments.
717 * @return {!Element} XML storage element.
718 * @this Blockly.Block
719 */
720 public Element mutationToDom()
721 {
722 var container = Document.CreateElement("mutation");
723 container.SetAttribute("name", this.getProcedureCall());
724 for (var i = 0; i < this.arguments_.Length; i++) {
725 var parameter = Document.CreateElement("arg");
726 parameter.SetAttribute("name", this.arguments_[i]);
727 container.AppendChild(parameter);
728 }
729 return container;
730 }
731
732 /**
733 * Parse XML to restore the (non-editable) name and parameters.
734 * @param {!Element} xmlElement XML storage element.
735 * @this Blockly.Block
736 */
737 public void domToMutation(Element xmlElement)
738 {
739 var name = xmlElement.GetAttribute("name");
740 this.renameProcedure(this.getProcedureCall(), name);
741 var args = new string[0];
742 var paramIds = new string[0];
743 Element childNode;
744 for (var i = 0; (childNode = (dynamic)xmlElement.ChildNodes[i]) != null; i++) {
745 if (childNode.NodeName.ToLower() == "arg") {
746 args.Push(childNode.GetAttribute("name"));
747 paramIds.Push(childNode.GetAttribute("paramId"));
748 }
749 }
750 this.setProcedureParameters_(args, paramIds);
751 }
752
753 /**
754 * Notification that a variable is renaming.
755 * If the name matches one of this block's variables, rename it.
756 * @param {string} oldName Previous name of variable.
757 * @param {string} newName Renamed variable.
758 * @this Blockly.Block
759 */
760 public override void renameVar(string oldName, string newName)
761 {
762 for (var i = 0; i < this.arguments_.Length; i++) {
763 if (Blockly.Names.equals(oldName, this.arguments_[i])) {
764 this.arguments_[i] = newName;
765 this.getField("ARGNAME" + i).setValue(newName);
766 }
767 }
768 }
769
770 /**
771 * Procedure calls cannot exist without the corresponding procedure
772 * definition. Enforce this link whenever an event is fired.
773 * @this Blockly.Block
774 */
775 public void onchange(Abstract e)
776 {
777 if (this.workspace == null || this.workspace.isFlyout) {
778 // Block is deleted or is in a flyout.
779 return;
780 }
781 if (e.type == Events.CREATE &&
782 Array.IndexOf(((Create)e).ids, this.id) != -1) {
783 // Look for the case where a procedure call was created (usually through
784 // paste) and there is no matching definition. In this case, create
785 // an empty definition block with the correct signature.
786 var name = this.getProcedureCall();
787 var def = Blockly.Procedures.getDefinition(name, this.workspace);
788 if (def != null && (def.type != this.defType_ ||
789 JSON.Stringify(((dynamic)def).arguments_) != JSON.Stringify(this.arguments_))) {
790 // The signatures don"t match.
791 def = null;
792 }
793 if (def == null) {
794 Blockly.Events.setGroup(e.group);
795 /**
796 * Create matching definition block.
797 * <xml>
798 * <block type="procedures_defreturn" x="10" y="20">
799 * <mutation name="test">
800 * <arg name="x"></arg>
801 * </mutation>
802 * <field name="NAME">test</field>
803 * </block>
804 * </xml>
805 */
806 var xml = goog.dom.createDom("xml");
807 var block = goog.dom.createDom("block");
808 block.SetAttribute("type", this.defType_);
809 var xy = this.getRelativeToSurfaceXY();
810 var x = xy.x + Blockly.SNAP_RADIUS * (this.RTL ? -1 : 1);
811 var y = xy.y + Blockly.SNAP_RADIUS * 2;
812 block.SetAttribute("x", x.ToString());
813 block.SetAttribute("y", y.ToString());
814 var mutation = this.mutationToDom();
815 block.AppendChild(mutation);
816 var field = goog.dom.createDom("field");
817 field.SetAttribute("name", "NAME");
818 field.AppendChild(Document.CreateTextNode(this.getProcedureCall()));
819 block.AppendChild(field);
820 xml.AppendChild(block);
821 Blockly.Xml.domToWorkspace(xml, this.workspace);
822 Blockly.Events.setGroup(false.ToString());
823 }
824 }
825 else if (e.type == Events.DELETE) {
826 // Look for the case where a procedure definition has been deleted,
827 // leaving this block (a procedure call) orphaned. In this case, delete
828 // the orphan.
829 var name = this.getProcedureCall();
830 var def = Blockly.Procedures.getDefinition(name, this.workspace);
831 if (def == null) {
832 Blockly.Events.setGroup(e.group);
833 this.dispose(true/*, false*/);
834 Blockly.Events.setGroup(false.ToString());
835 }
836 }
837 }
838 }
839
840 public class ProceduresCallnoreturnBlock : ProceduresCallBlock
841 {
842 public const string type_name = "procedures_callnoreturn";
843
844 public ProceduresCallnoreturnBlock()
845 : base(type_name)
846 {
847 defType_ = ProceduresDefnoreturnBlock.type_name;
848 }
849
850 /**
851 * Block for calling a procedure with no return value.
852 * @this Blockly.Block
853 */
854 public void init()
855 {
856 this.appendDummyInput("TOPROW")
857 .appendField(this.id, "NAME");
858 this.setPreviousStatement(true);
859 this.setNextStatement(true);
860 this.setColour(Blockly.Procedures.HUE);
861 // Tooltip is set in renameProcedure.
862 this.setHelpUrl(Msg.PROCEDURES_CALLNORETURN_HELPURL);
863 this.arguments_ = new string[0];
864 this.quarkConnections_ = new Dictionary<string, Connection>();
865 this.quarkIds_ = null;
866 }
867
868 /**
869 * Add menu option to find the definition block for this call.
870 * @param {!Array} options List of menu options to add to.
871 * @this Blockly.Block
872 */
873 public void customContextMenu(object[] options)
874 {
875 var option = new ContextMenuOption() { enabled = true };
876 option.text = Msg.PROCEDURES_HIGHLIGHT_DEF;
877 var name = this.getProcedureCall();
878 var workspace = this.workspace;
879 option.callback = () => {
880 var def = Blockly.Procedures.getDefinition(name, workspace);
881 if (def != null)
882 def.select();
883 };
884 options.Push(option);
885 }
886 }
887
888 public class ProceduresCallreturnBlock : ProceduresCallBlock
889 {
890 public const string type_name = "procedures_callreturn";
891
892 public ProceduresCallreturnBlock()
893 : base(type_name)
894 {
895 defType_ = ProceduresDefreturnBlock.type_name;
896 }
897
898 /**
899 * Block for calling a procedure with a return value.
900 * @this Blockly.Block
901 */
902 public void init()
903 {
904 this.appendDummyInput("TOPROW")
905 .appendField("", "NAME");
906 this.setOutput(true);
907 this.setColour(Blockly.Procedures.HUE);
908 // Tooltip is set in domToMutation.
909 this.setHelpUrl(Msg.PROCEDURES_CALLRETURN_HELPURL);
910 this.arguments_ = new string[0];
911 this.quarkConnections_ = new Dictionary<string, Connection>();
912 this.quarkIds_ = null;
913 }
914 }
915
916 public class ProceduresIfreturnBlock : Block
917 {
918 public const string type_name = "procedures_ifreturn";
919 internal bool hasReturnValue_;
920
921 public ProceduresIfreturnBlock()
922 : base(type_name)
923 {
924 }
925
926 /**
927 * Block for conditionally returning a value from a procedure.
928 * @this Blockly.Block
929 */
930 public void init()
931 {
932 this.appendValueInput("CONDITION")
933 .setCheck("Boolean")
934 .appendField(Msg.CONTROLS_IF_MSG_IF);
935 this.appendValueInput("VALUE")
936 .appendField(Msg.PROCEDURES_DEFRETURN_RETURN);
937 this.setInputsInline(true);
938 this.setPreviousStatement(true);
939 this.setNextStatement(true);
940 this.setColour(Blockly.Procedures.HUE);
941 this.setTooltip(Msg.PROCEDURES_IFRETURN_TOOLTIP);
942 this.setHelpUrl(Msg.PROCEDURES_IFRETURN_HELPURL);
943 this.hasReturnValue_ = true;
944 }
945
946 /**
947 * Create XML to represent whether this block has a return value.
948 * @return {!Element} XML storage element.
949 * @this Blockly.Block
950 */
951 public Element mutationToDom()
952 {
953 var container = Document.CreateElement("mutation");
954 container.SetAttribute("value", this.hasReturnValue_ ? "1" : "0");
955 return container;
956 }
957
958 /**
959 * Parse XML to restore whether this block has a return value.
960 * @param {!Element} xmlElement XML storage element.
961 * @this Blockly.Block
962 */
963 public void domToMutation(Element xmlElement)
964 {
965 var value = xmlElement.GetAttribute("value");
966 this.hasReturnValue_ = (value == "1");
967 if (!this.hasReturnValue_) {
968 this.removeInput("VALUE");
969 this.appendDummyInput("VALUE")
970 .appendField(Msg.PROCEDURES_DEFRETURN_RETURN);
971 }
972 }
973
974 /**
975 * Called whenever anything on the workspace changes.
976 * Add warning if this flow block is not nested inside a loop.
977 * @param {!Abstract} e Change event.
978 * @this Blockly.Block
979 */
980 public void onchange(Abstract e)
981 {
982 if (((WorkspaceSvg)this.workspace).isDragging()) {
983 return; // Don't change state at the start of a drag.
984 }
985 var legal = false;
986 // Is the block nested in a procedure?
987 var block = (Block)this;
988 do {
989 if (Array.IndexOf(this.FUNCTION_TYPES, block.type) != -1) {
990 legal = true;
991 break;
992 }
993 block = block.getSurroundParent();
994 } while (block != null);
995 if (legal) {
996 // If needed, toggle whether this block has a return value.
997 if (block.type == ProceduresDefnoreturnBlock.type_name && this.hasReturnValue_) {
998 this.removeInput("VALUE");
999 this.appendDummyInput("VALUE")
1000 .appendField(Msg.PROCEDURES_DEFRETURN_RETURN);
1001 this.hasReturnValue_ = false;
1002 }
1003 else if (block.type == ProceduresDefreturnBlock.type_name && !this.hasReturnValue_) {
1004 this.removeInput("VALUE");
1005 this.appendValueInput("VALUE")
1006 .appendField(Msg.PROCEDURES_DEFRETURN_RETURN);
1007 this.hasReturnValue_ = true;
1008 }
1009 this.setWarningText(null);
1010 if (!this.isInFlyout) {
1011 this.setDisabled(false);
1012 }
1013 }
1014 else {
1015 this.setWarningText(Msg.PROCEDURES_IFRETURN_WARNING);
1016 if (!this.isInFlyout && !this.getInheritedDisabled()) {
1017 this.setDisabled(true);
1018 }
1019 }
1020 }
1021
1022 /**
1023 * List of block types that are functions and thus do not need warnings.
1024 * To add a new function type add this to your code:
1025 * Blockly.Blocks["procedures_ifreturn"].FUNCTION_TYPES.push("custom_func");
1026 */
1027 string[] FUNCTION_TYPES = new[] { ProceduresDefnoreturnBlock.type_name, ProceduresDefreturnBlock.type_name };
1028 }
1029}
Note: See TracBrowser for help on using the repository browser.