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

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

mruby版ECNLプロトタイピング・ツールを追加

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