[WIP] Rebase from Blockly SVN r1757 to git 641d720

This commit is contained in:
Evan W. Patton
2016-09-28 10:36:40 -04:00
parent 641d720842
commit 16d1a6b362
69 changed files with 15087 additions and 31 deletions

View File

@@ -160,6 +160,11 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
this.onchangeWrapper_ = this.onchange.bind(this);
this.workspace.addChangeListener(this.onchangeWrapper_);
}
// Bind an onchange function, if it exists.
if ((!this.isInFlyout) && goog.isFunction(this.onchange)) {
Blockly.bindEvent_(workspace.getCanvas(), 'blocklyWorkspaceChange', this,
this.onchange);
}
};
/**
@@ -366,6 +371,8 @@ Blockly.Block.prototype.bumpNeighbours_ = function() {
}
}
}
// Remove any associated errors or warnings.
Blockly.WarningHandler.checkDisposedBlock.call(this);
};
/**
@@ -460,6 +467,11 @@ Blockly.Block.prototype.setParent = function(newParent) {
children.splice(x, 1);
break;
}
if (descendant.errorIcon) {
var data = descendant.errorIcon.getIconLocation();
data.bubble = descendant.errorIcon;
this.draggedBubbles_.push(data);
}
}
// Disconnect from superior blocks.
@@ -476,6 +488,9 @@ Blockly.Block.prototype.setParent = function(newParent) {
// Remove this block from the workspace's list of top-most blocks.
this.workspace.removeTopBlock(this);
}
if (this.errorIcon) {
this.errorIcon.computeIconLocation();
}
this.parentBlock_ = newParent;
if (newParent) {
@@ -969,6 +984,17 @@ Blockly.Block.prototype.appendStatementInput = function(name) {
return this.appendInput_(Blockly.NEXT_STATEMENT, name);
};
/**
* Shortcut for appending an inline value input row.
* @param {string} name Language-neutral identifier which may used to find this
* input again. Should be unique to this block.
* @return {!Blockly.Input} The input object created.
*/
Blockly.Block.prototype.appendIndentedValueInput = function(name) {
return this.appendInput_(Blockly.INDENTED_VALUE, name);
};
/**
* Shortcut for appending a dummy input row.
* @param {string=} opt_name Language-neutral identifier which may used to find
@@ -1165,8 +1191,8 @@ Blockly.Block.prototype.interpolate_ = function(message, args, lastDummyAlign) {
/**
* Add a value input, statement input or local variable to this block.
* @param {number} type Either Blockly.INPUT_VALUE or Blockly.NEXT_STATEMENT or
* Blockly.DUMMY_INPUT.
* @param {number} type Either Blockly.INPUT_VALUE, Blockly.NEXT_STATEMENT, Blockly.DUMMY_INPUT,
* or subtypes Blockly.INDENTED_VALUE.
* @param {string} name Language-neutral identifier which may used to find this
* input again. Should be unique to this block.
* @return {!Blockly.Input} The input object created.

1364
core/block.js.orig Normal file

File diff suppressed because it is too large Load Diff

510
core/block.js.rej Normal file
View File

@@ -0,0 +1,510 @@
***************
*** 26,40 ****
goog.provide('Blockly.Block');
goog.require('Blockly.BlockSvg');
goog.require('Blockly.Blocks');
goog.require('Blockly.Comment');
goog.require('Blockly.Connection');
goog.require('Blockly.ContextMenu');
goog.require('Blockly.Input');
goog.require('Blockly.Msg');
goog.require('Blockly.Mutator');
goog.require('Blockly.Warning');
goog.require('Blockly.Workspace');
goog.require('Blockly.Xml');
goog.require('goog.Timer');
--- 26,43 ----
goog.provide('Blockly.Block');
+ goog.require('Blockly.Instrument'); // lyn's instrumentation code
goog.require('Blockly.BlockSvg');
goog.require('Blockly.Blocks');
goog.require('Blockly.Comment');
goog.require('Blockly.Connection');
goog.require('Blockly.ContextMenu');
+ goog.require('Blockly.ErrorIcon');
goog.require('Blockly.Input');
goog.require('Blockly.Msg');
goog.require('Blockly.Mutator');
goog.require('Blockly.Warning');
+ goog.require('Blockly.WarningHandler');
goog.require('Blockly.Workspace');
goog.require('Blockly.Xml');
goog.require('goog.Timer');
***************
*** 149,154 ****
this.workspace = workspace;
this.isInFlyout = workspace.isFlyout;
// Copy the type-specific functions and data from the prototype.
if (prototypeName) {
--- 152,159 ----
this.workspace = workspace;
this.isInFlyout = workspace.isFlyout;
+ // This is missing from our latest version
+ //workspace.addTopBlock(this);
// Copy the type-specific functions and data from the prototype.
if (prototypeName) {
***************
*** 202,207 ****
Blockly.Block.prototype.warning = null;
/**
* Returns a list of mutator, comment, and warning icons.
* @return {!Array} List of icons.
*/
--- 212,223 ----
Blockly.Block.prototype.warning = null;
/**
+ * Block's error icon (if any).
+ * @type {Blockly.ErrorIcon}
+ */
+ Blockly.Block.prototype.errorIcon = null;
+
+ /**
* Returns a list of mutator, comment, and warning icons.
* @return {!Array} List of icons.
*/
***************
*** 216,221 ****
if (this.warning) {
icons.push(this.warning);
}
return icons;
};
--- 232,240 ----
if (this.warning) {
icons.push(this.warning);
}
+ if (this.errorIcon) {
+ icons.push(this.errorIcon);
+ }
return icons;
};
***************
*** 374,379 ****
for (var x = 0; x < icons.length; x++) {
icons[x].dispose();
}
// Dispose of all inputs and their fields.
for (var x = 0, input; input = this.inputList[x]; x++) {
input.dispose();
--- 393,402 ----
for (var x = 0; x < icons.length; x++) {
icons[x].dispose();
}
+ if (this.errorIcon) {
+ this.errorIcon.dispose();
+ }
+
// Dispose of all inputs and their fields.
for (var x = 0, input; input = this.inputList[x]; x++) {
input.dispose();
***************
*** 448,464 ****
* @return {!Object} Object with height and width properties.
*/
Blockly.Block.prototype.getHeightWidth = function() {
var height = this.svg_.height;
var width = this.svg_.width;
// Recursively add size of subsequent blocks.
var nextBlock = this.getNextBlock();
if (nextBlock) {
- var nextHeightWidth = nextBlock.getHeightWidth();
height += nextHeightWidth.height - 4; // Height of tab.
width = Math.max(width, nextHeightWidth.width);
}
return {height: height, width: width};
- };
/**
* Handle a mouse-down on an SVG block.
--- 473,534 ----
* @return {!Object} Object with height and width properties.
*/
Blockly.Block.prototype.getHeightWidth = function() {
+ var start = new Date().getTime();
+ var result;
+ if (Blockly.Instrument.useNeilGetHeightWidthFix) {
+ result = this.getHeightWidthNeil();
+ } else {
+ // Old inexplicably quadratic version of getHeightWidth
+ // The quadratic nature has something to do with getBBox that Lyn
+ // and others never figured out.
+ try {
+ // var bBox = this.getSvgRoot().getBBox();
+ var root = this.getSvgRoot(); //***lyn
+ var bBox = root.getBBox(); //***lyn
+ var height = bBox.height;
+
+ } catch (e) {
+ // Firefox has trouble with hidden elements (Bug 528969).
+ return {height: 0, width: 0};
+ }
+ if (Blockly.BROKEN_CONTROL_POINTS) {
+ /* HACK:
+ WebKit bug 67298 causes control points to be included in the reported
+ bounding box. The render functions (below) add two 5px spacer control
+ points that we need to subtract.
+ */
+ height -= 10;
+ if (this.nextConnection) {
+ // Bottom control point partially masked by lower tab.
+ height += 4;
+ }
+ }
+ // Subtract one from the height due to the shadow.
+ height -= 1;
+ result = {height: height, width: bBox.width}; //Why is width handled differently here
+ }
+ var stop = new Date().getTime();
+ var timeDiff = stop - start;
+ Blockly.Instrument.stats.getHeightWidthCalls++;
+ Blockly.Instrument.stats.getHeightWidthTime += timeDiff;
+ return result;
+ };
+
+ /**
+ * Neil's getHeightWidth
+ */
+ Blockly.Block.prototype.getHeightWidthNeil = function() {
var height = this.svg_.height;
var width = this.svg_.width;
// Recursively add size of subsequent blocks.
var nextBlock = this.getNextBlock();
if (nextBlock) {
+ var nextHeightWidth = nextBlock.getHeightWidthNeil();
height += nextHeightWidth.height - 4; // Height of tab.
width = Math.max(width, nextHeightWidth.width);
}
return {height: height, width: width};
+ }
/**
* Handle a mouse-down on an SVG block.
***************
*** 474,480 ****
* @private
*/
Blockly.Block.prototype.onMouseUp_ = function(e) {
var this_ = this;
Blockly.doCommand(function() {
Blockly.terminateDrag_();
if (Blockly.selected && Blockly.highlightedConnection_) {
--- 549,558 ----
* @private
*/
Blockly.Block.prototype.onMouseUp_ = function(e) {
+ var start = new Date().getTime();
+ Blockly.Instrument.initializeStats("onMouseUp");
var this_ = this;
+ Blockly.resetWorkspaceArrangements();
Blockly.doCommand(function() {
Blockly.terminateDrag_();
if (Blockly.selected && Blockly.highlightedConnection_) {
***************
*** 509,514 ****
Blockly.highlightedConnection_ = null;
}
});
};
/**
--- 587,602 ----
Blockly.highlightedConnection_ = null;
}
});
+ if (! Blockly.Instrument.avoidRenderWorkspaceInMouseUp) {
+ // [lyn, 04/01/14] rendering a workspace takes a *long* time and is *not* necessary!
+ // This is the key source of the laggy drag problem. Remove it!
+ Blockly.mainWorkspace.render();
+ }
+ Blockly.WarningHandler.checkAllBlocksForWarningsAndErrors();
+ var stop = new Date().getTime();
+ var timeDiff = stop - start;
+ Blockly.Instrument.stats.totalTime = timeDiff;
+ Blockly.Instrument.displayStats("onMouseUp");
};
/**
***************
*** 833,850 ****
/**
* Get the colour of a block.
- * @return {number} HSV hue value.
*/
Blockly.Block.prototype.getColour = function() {
- return this.colourHue_;
};
/**
* Change the colour of a block.
- * @param {number} colourHue HSV hue value.
*/
- Blockly.Block.prototype.setColour = function(colourHue) {
- this.colourHue_ = colourHue;
if (this.svg_) {
this.svg_.updateColour();
}
--- 924,954 ----
/**
* Get the colour of a block.
+ * @return {number|Array} HSV hue value or RGB Array.
*/
Blockly.Block.prototype.getColour = function() {
+ return (this.rgbArray_ == null ? this.colourHue_ : this.rgbArray_);
};
/**
* Change the colour of a block.
+ * @param {number|Array} hueOrRGBArray HSV hue value or array of RGB values.
*/
+ Blockly.Block.prototype.setColour = function(hueOrRGBArray) {
+ if(Array.isArray(hueOrRGBArray)) {
+ this.rgbArray_ = hueOrRGBArray;
+ this.colourHue_ = null;
+ } else {
+ this.colourHue_ = hueOrRGBArray;
+ this.rgbArray_ = null;
+ }
+ this.updateColour();
+ };
+
+ /**
+ * Update the colour of a block.
+ */
+ Blockly.Block.prototype.updateColour = function() {
if (this.svg_) {
this.svg_.updateColour();
}
***************
*** 852,857 ****
for (var x = 0; x < icons.length; x++) {
icons[x].updateColour();
}
if (this.rendered) {
// Bump every dropdown to change its colour.
for (var x = 0, input; input = this.inputList[x]; x++) {
--- 956,964 ----
for (var x = 0; x < icons.length; x++) {
icons[x].updateColour();
}
+ if (this.errorIcon) {
+ this.errorIcon.updateColour();
+ }
if (this.rendered) {
// Bump every dropdown to change its colour.
for (var x = 0, input; input = this.inputList[x]; x++) {
***************
*** 1095,1105 ****
if (this.collapsed_ == collapsed) {
return;
}
this.collapsed_ = collapsed;
var renderList = [];
// Show/hide the inputs.
- for (var x = 0, input; input = this.inputList[x]; x++) {
renderList.push.apply(renderList, input.setVisible(!collapsed));
}
var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
--- 1202,1225 ----
if (this.collapsed_ == collapsed) {
return;
}
+ var start = new Date().getTime();
this.collapsed_ = collapsed;
var renderList = [];
+ // //Prepare the string for collapsing if needed
+ // if (collapsed){
+ // if (this.prepareCollapsedText && goog.isFunction(this.prepareCollapsedText))
+ // this.prepareCollapsedText();
+ // }
// Show/hide the inputs.
+ if (Blockly.Instrument.useRenderDown) {
+ for (var x = 0, input; input = this.inputList[x]; x++) {
+ // No need to collect renderList if rendering down.
+ input.setVisible(!collapsed);
+ }
+ } else {
+ for (var x = 0, input; input = this.inputList[x]; x++) {
renderList.push.apply(renderList, input.setVisible(!collapsed));
+ }
}
var COLLAPSED_INPUT_NAME = '_TEMP_COLLAPSED_INPUT';
***************
*** 1108,1113 ****
for (var x = 0; x < icons.length; x++) {
icons[x].setVisible(false);
}
var text = this.toString(Blockly.COLLAPSE_CHARS);
this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text);
} else {
--- 1228,1236 ----
for (var x = 0; x < icons.length; x++) {
icons[x].setVisible(false);
}
+ if (this.errorIcon) {
+ this.errorIcon.setVisible(false);
+ }
var text = this.toString(Blockly.COLLAPSE_CHARS);
this.appendDummyInput(COLLAPSED_INPUT_NAME).appendField(text);
} else {
***************
*** 1118,1129 ****
// No child blocks, just render this block.
renderList[0] = this;
}
if (this.rendered) {
- for (var x = 0, block; block = renderList[x]; x++) {
- block.render();
}
this.bumpNeighbours_();
}
};
/**
--- 1241,1264 ----
// No child blocks, just render this block.
renderList[0] = this;
}
+
if (this.rendered) {
+ if (Blockly.Instrument.useRenderDown) {
+ this.renderDown();
+ } else {
+ for (var x = 0, block; block = renderList[x]; x++) {
+ block.render();
+ }
}
this.bumpNeighbours_();
}
+
+ var stop = new Date().getTime();
+ var timeDiff = stop - start;
+ if (! collapsed) {
+ Blockly.Instrument.stats.expandCollapsedCalls++;
+ Blockly.Instrument.stats.expandCollapsedTime += timeDiff;
+ }
};
/**
***************
*** 1174,1180 ****
*/
Blockly.Block.prototype.appendInput_ = function(type, name) {
var connection = null;
- if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT) {
connection = new Blockly.Connection(this, type);
}
var input = new Blockly.Input(type, name, this, connection);
--- 1320,1326 ----
*/
Blockly.Block.prototype.appendInput_ = function(type, name) {
var connection = null;
+ if (type == Blockly.INPUT_VALUE || type == Blockly.NEXT_STATEMENT || type == Blockly.INDENTED_VALUE) {
connection = new Blockly.Connection(this, type);
}
var input = new Blockly.Input(type, name, this, connection);
***************
*** 1389,1400 ****
};
/**
* Render the block.
* Lays out and reflows a block based on its contents and settings.
*/
Blockly.Block.prototype.render = function() {
- goog.asserts.assertObject(this.svg_,
- 'Uninitialized block cannot be rendered. Call block.initSvg()');
- this.svg_.render();
- Blockly.Realtime.blockChanged(this);
};
--- 1535,1609 ----
};
/**
+ * Set this block's warning text.
+ * @param {?string} text The text, or null to delete.
+ */
+ Blockly.Block.prototype.setErrorIconText = function(text) {
+ if (!Blockly.ErrorIcon) {
+ throw 'Warnings not supported.';
+ }
+ var changedState = false;
+ if (goog.isString(text)) {
+ if (!this.errorIcon) {
+ this.errorIcon = new Blockly.ErrorIcon(this);
+ changedState = true;
+ }
+ this.errorIcon.setText(/** @type {string} */ (text));
+ } else {
+ if (this.errorIcon) {
+ this.errorIcon.dispose();
+ changedState = true;
+ }
+ }
+ if (this.rendered) {
+ this.render();
+ if (changedState) {
+ // Adding or removing a warning icon will cause the block to change shape.
+ this.bumpNeighbours_();
+ }
+ }
+ };
+
+ /**
+ * [lyn, 04/01/14] Global flag to control whether rendering is done.
+ * There is no need to render blocks in Blocky.SaveFile.load.
+ * We only need to render them when a Screen is loaded in the Blockly editor.
+ * This flag is used to turn off rendering for first case and turn it on for the second.
+ * @type {boolean}
+ */
+ Blockly.Block.isRenderingOn = true;
+
+ /**
* Render the block.
* Lays out and reflows a block based on its contents and settings.
*/
Blockly.Block.prototype.render = function() {
+ if (Blockly.Block.isRenderingOn) {
+ goog.asserts.assertObject(this.svg_,
+ 'Uninitialized block cannot be rendered. Call block.initSvg()');
+ this.svg_.render();
+ if (Blockly.Realtime.isEnabled() && !Blockly.Realtime.withinSync) {
+ Blockly.Realtime.blockChanged(this);
+ }
+ Blockly.Instrument.stats.renderCalls++;
+ // [lyn, 04/08/14] Because render is recursive, doesn't make sense to track its time here.
+ }
+ };
+
+ /**
+ * [lyn, 04/01/14] Render a tree of blocks from top down rather than bottom up.
+ * This is in contrast to render(), which renders a block and all its antecedents.
+ */
+ Blockly.Block.prototype.renderDown = function() {
+ if (Blockly.Block.isRenderingOn) {
+ goog.asserts.assertObject(this.svg_,
+ ' Uninitialized block cannot be renderedDown. Call block.initSvg()');
+ this.svg_.renderDown();
+ Blockly.Instrument.stats.renderDownCalls++; //***lyn
+ if (Blockly.Realtime.isEnabled() && !Blockly.Realtime.withinSync) {
+ Blockly.Realtime.blockChanged(this);
+ }
+ }
+ // [lyn, 04/08/14] Because renderDown is recursive, doesn't make sense to track its time here.
};
+

1629
core/block_svg.js.orig Normal file

File diff suppressed because it is too large Load Diff

231
core/block_svg.js.rej Normal file
View File

@@ -0,0 +1,231 @@
***************
*** 26,31 ****
goog.provide('Blockly.BlockSvg');
goog.require('goog.userAgent');
--- 26,32 ----
goog.provide('Blockly.BlockSvg');
+ goog.require('Blockly.Instrument'); // lyn's instrumentation code
goog.require('goog.userAgent');
***************
*** 154,167 ****
* @const
*/
Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) *
- (Blockly.BlockSvg.CORNER_RADIUS - 1) + 1;
/**
* Distance from shape edge to intersect with a curved corner at 45 degrees.
* Applies to highlighting on around the outside of a curve.
* @const
*/
Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) *
- (Blockly.BlockSvg.CORNER_RADIUS + 1) - 1;
/**
* SVG path for drawing next/previous notch from left to right.
* @const
--- 155,168 ----
* @const
*/
Blockly.BlockSvg.DISTANCE_45_INSIDE = (1 - Math.SQRT1_2) *
+ (Blockly.BlockSvg.CORNER_RADIUS - 1) + 1;
/**
* Distance from shape edge to intersect with a curved corner at 45 degrees.
* Applies to highlighting on around the outside of a curve.
* @const
*/
Blockly.BlockSvg.DISTANCE_45_OUTSIDE = (1 - Math.SQRT1_2) *
+ (Blockly.BlockSvg.CORNER_RADIUS + 1) - 1;
/**
* SVG path for drawing next/previous notch from left to right.
* @const
***************
*** 483,488 ****
Blockly.BlockSvg.prototype.render = function() {
this.block_.rendered = true;
var cursorX = Blockly.BlockSvg.SEP_SPACE_X;
if (Blockly.RTL) {
cursorX = -cursorX;
--- 484,530 ----
Blockly.BlockSvg.prototype.render = function() {
this.block_.rendered = true;
+ this.renderHere();
+
+ // Render all blocks above this one (propagate a reflow).
+ var parentBlock = this.block_.getParent();
+ if (parentBlock) {
+ parentBlock.render();
+ }
+ };
+
+ /**
+ * [lyn, 04/01/14] Render a tree of blocks.
+ * In general, this is more efficient than calling render() on all the leaves of the tree,
+ * because that will:
+ * (1) repeat the rendering of all internal nodes; and
+ * (2) will unnecessarily call Blockly.fireUiEvent(window, 'resize') in the
+ * case where the parentPointer hasn't been set yet (particularly for
+ * value, statement, and next connections in Xml.domToBlock).
+ * These two factors account for much of the slow project loading times in Blockly
+ * and previous versions of AI2.
+ */
+ Blockly.BlockSvg.prototype.renderDown = function() {
+ this.block_.rendered = true;
+
+ // Recursively renderDown all my children (as long as I'm not collapsed)
+ if (! (Blockly.Instrument.avoidRenderDownOnCollapsedSubblocks && this.block_.isCollapsed())) {
+ var childBlocks = this.block_.childBlocks_;
+ for (var c = 0, childBlock; childBlock = childBlocks[c]; c++) {
+ childBlock.renderDown();
+ }
+ }
+
+ // Render me after all my children have been rendered.
+ this.renderHere();
+ };
+
+ /**
+ * Render this block. Assumes descendants have already been rendered.
+ */
+ Blockly.BlockSvg.prototype.renderHere = function() {
+ var start = new Date().getTime();
+ // Now render me (even if I am collapsed, since still need to show collapsed block)
var cursorX = Blockly.BlockSvg.SEP_SPACE_X;
if (Blockly.RTL) {
cursorX = -cursorX;
***************
*** 500,514 ****
var inputRows = this.renderCompute_(cursorX);
this.renderDraw_(cursorX, inputRows);
- // Render all blocks above this one (propagate a reflow).
var parentBlock = this.block_.getParent();
- if (parentBlock) {
- parentBlock.render();
- } else {
// Top-most block. Fire an event to allow scrollbars to resize.
Blockly.fireUiEvent(window, 'resize');
}
- };
/**
* Render a list of fields starting at the specified location.
--- 542,557 ----
var inputRows = this.renderCompute_(cursorX);
this.renderDraw_(cursorX, inputRows);
var parentBlock = this.block_.getParent();
+ if (!parentBlock) {
// Top-most block. Fire an event to allow scrollbars to resize.
Blockly.fireUiEvent(window, 'resize');
}
+ var stop = new Date().getTime();
+ var timeDiff = stop-start;
+ Blockly.Instrument.stats.renderHereCalls++;
+ Blockly.Instrument.stats.renderHereTime += timeDiff;
+ }
/**
* Render a list of fields starting at the specified location.
***************
*** 578,583 ****
row = [];
if (isInline && input.type != Blockly.NEXT_STATEMENT) {
row.type = Blockly.BlockSvg.INLINE;
} else {
row.type = input.type;
}
--- 621,629 ----
row = [];
if (isInline && input.type != Blockly.NEXT_STATEMENT) {
row.type = Blockly.BlockSvg.INLINE;
+ } else if (input.subtype) {
+ row.type = input.type;
+ row.subtype = input.subtype;
} else {
row.type = input.type;
}
***************
*** 924,932 ****
}
}
this.renderFields_(input.fieldRow, fieldX, fieldY);
- steps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
- var v = row.height - Blockly.BlockSvg.TAB_HEIGHT
- steps.push('v', v);
if (Blockly.RTL) {
// Highlight around back of tab.
highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
--- 970,1006 ----
}
}
this.renderFields_(input.fieldRow, fieldX, fieldY);
+ if (row.subtype == Blockly.INDENTED_VALUE){
+ cursorX = inputRows.statementEdge;
+ steps.push('H', cursorX+input.fieldWidth+8);
+ steps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
+ steps.push('V',cursorY+row.height);
+ steps.push('H', inputRows.rightEdge);
+ steps.push('v 8');
+ if (Blockly.RTL) {
+ highlightSteps.push('M',
+ (cursorX - Blockly.BlockSvg.NOTCH_WIDTH +
+ Blockly.BlockSvg.DISTANCE_45_OUTSIDE) +
+ ',' + (cursorY + Blockly.BlockSvg.DISTANCE_45_OUTSIDE));
+ highlightSteps.push(
+ Blockly.BlockSvg.INNER_TOP_LEFT_CORNER_HIGHLIGHT_RTL);
+ highlightSteps.push('v',
+ row.height - 2 * Blockly.BlockSvg.CORNER_RADIUS);
+ highlightSteps.push(
+ Blockly.BlockSvg.INNER_BOTTOM_LEFT_CORNER_HIGHLIGHT_RTL);
+ highlightSteps.push('H', inputRows.rightEdge - 1);
+ } else {
+ highlightSteps.push('M',
+ (cursorX + 9 + input.fieldWidth) + ',' +
+ (cursorY + row.height));
+ highlightSteps.push('H', inputRows.rightEdge);
+ }
+ cursorY+=8;
+ } else {
+ steps.push(Blockly.BlockSvg.TAB_PATH_DOWN);
+ var v = row.height - Blockly.BlockSvg.TAB_HEIGHT
+ steps.push('v', v);
+ }
if (Blockly.RTL) {
// Highlight around back of tab.
highlightSteps.push(Blockly.BlockSvg.TAB_PATH_DOWN_HIGHLIGHT_RTL);
***************
*** 939,947 ****
',-1.8');
}
// Create external input connection.
- connectionX = connectionsXY.x +
- (Blockly.RTL ? -inputRows.rightEdge - 1 : inputRows.rightEdge + 1);
- connectionY = connectionsXY.y + cursorY;
input.connection.moveTo(connectionX, connectionY);
if (input.connection.targetConnection) {
input.connection.tighten_();
--- 1013,1027 ----
',-1.8');
}
// Create external input connection.
+ if (row.subtype == Blockly.INDENTED_VALUE){
+ connectionX = connectionsXY.x +
+ (Blockly.RTL ? -inputRows.statementEdge - 1: inputRows.statementEdge + 9 + input.fieldWidth);
+ connectionY = connectionsXY.y + cursorY-8;
+ } else {
+ connectionX = connectionsXY.x +
+ (Blockly.RTL ? -inputRows.rightEdge - 1 : inputRows.rightEdge + 1);
+ connectionY = connectionsXY.y + cursorY;
+ }
input.connection.moveTo(connectionX, connectionY);
if (input.connection.targetConnection) {
input.connection.tighten_();

View File

@@ -63,6 +63,21 @@ var CLOSURE_DEFINES = {'goog.DEBUG': false};
*/
Blockly.mainWorkspace = null;
/**
* Workspace blocks arrangements
*/
Blockly.BLKS_HORIZONTAL = 0;
Blockly.BLKS_VERTICAL = 1;
Blockly.BLKS_CATEGORY = 2;
/**
* Current Workspace arrangement state for position (horizontal or vertical),
* and for type (category)
*/
Blockly.workspace_arranged_position = null;
Blockly.workspace_arranged_latest_position = null; //used to default to (previous is used for menus)
Blockly.workspace_arranged_type = null;
/**
* Currently selected block.
* @type {Blockly.Block}

453
core/blockly.js.orig Normal file
View File

@@ -0,0 +1,453 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2011 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Core JavaScript library for Blockly.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
// Top level object for Blockly.
goog.provide('Blockly');
goog.require('Blockly.BlockSvg.render');
goog.require('Blockly.Events');
goog.require('Blockly.FieldAngle');
goog.require('Blockly.FieldCheckbox');
goog.require('Blockly.FieldColour');
// Date picker commented out since it increases footprint by 60%.
// Add it only if you need it.
//goog.require('Blockly.FieldDate');
goog.require('Blockly.FieldDropdown');
goog.require('Blockly.FieldImage');
goog.require('Blockly.FieldTextInput');
goog.require('Blockly.FieldNumber');
goog.require('Blockly.FieldVariable');
goog.require('Blockly.Generator');
goog.require('Blockly.Msg');
goog.require('Blockly.Procedures');
goog.require('Blockly.Toolbox');
goog.require('Blockly.WidgetDiv');
goog.require('Blockly.WorkspaceSvg');
goog.require('Blockly.constants');
goog.require('Blockly.inject');
goog.require('Blockly.utils');
goog.require('goog.color');
goog.require('goog.userAgent');
// Turn off debugging when compiled.
var CLOSURE_DEFINES = {'goog.DEBUG': false};
/**
* The main workspace most recently used.
* Set by Blockly.WorkspaceSvg.prototype.markFocused
* @type {Blockly.Workspace}
*/
Blockly.mainWorkspace = null;
/**
* Currently selected block.
* @type {Blockly.Block}
*/
Blockly.selected = null;
/**
* Currently highlighted connection (during a drag).
* @type {Blockly.Connection}
* @private
*/
Blockly.highlightedConnection_ = null;
/**
* Connection on dragged block that matches the highlighted connection.
* @type {Blockly.Connection}
* @private
*/
Blockly.localConnection_ = null;
/**
* All of the connections on blocks that are currently being dragged.
* @type {!Array.<!Blockly.Connection>}
* @private
*/
Blockly.draggingConnections_ = [];
/**
* Contents of the local clipboard.
* @type {Element}
* @private
*/
Blockly.clipboardXml_ = null;
/**
* Source of the local clipboard.
* @type {Blockly.WorkspaceSvg}
* @private
*/
Blockly.clipboardSource_ = null;
/**
* Is the mouse dragging a block?
* DRAG_NONE - No drag operation.
* DRAG_STICKY - Still inside the sticky DRAG_RADIUS.
* DRAG_FREE - Freely draggable.
* @private
*/
Blockly.dragMode_ = Blockly.DRAG_NONE;
/**
* Wrapper function called when a touch mouseUp occurs during a drag operation.
* @type {Array.<!Array>}
* @private
*/
Blockly.onTouchUpWrapper_ = null;
/**
* Convert a hue (HSV model) into an RGB hex triplet.
* @param {number} hue Hue on a colour wheel (0-360).
* @return {string} RGB code, e.g. '#5ba65b'.
*/
Blockly.hueToRgb = function(hue) {
return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION,
Blockly.HSV_VALUE * 255);
};
/**
* Returns the dimensions of the specified SVG image.
* @param {!Element} svg SVG image.
* @return {!Object} Contains width and height properties.
*/
Blockly.svgSize = function(svg) {
return {width: svg.cachedWidth_,
height: svg.cachedHeight_};
};
/**
* Size the workspace when the contents change. This also updates
* scrollbars accordingly.
* @param {!Blockly.WorkspaceSvg} workspace The workspace to resize.
*/
Blockly.resizeSvgContents = function(workspace) {
workspace.resizeContents();
};
/**
* Size the SVG image to completely fill its container. Call this when the view
* actually changes sizes (e.g. on a window resize/device orientation change).
* See Blockly.resizeSvgContents to resize the workspace when the contents
* change (e.g. when a block is added or removed).
* Record the height/width of the SVG image.
* @param {!Blockly.WorkspaceSvg} workspace Any workspace in the SVG.
*/
Blockly.svgResize = function(workspace) {
var mainWorkspace = workspace;
while (mainWorkspace.options.parentWorkspace) {
mainWorkspace = mainWorkspace.options.parentWorkspace;
}
var svg = mainWorkspace.getParentSvg();
var div = svg.parentNode;
if (!div) {
// Workspace deleted, or something.
return;
}
var width = div.offsetWidth;
var height = div.offsetHeight;
if (svg.cachedWidth_ != width) {
svg.setAttribute('width', width + 'px');
svg.cachedWidth_ = width;
}
if (svg.cachedHeight_ != height) {
svg.setAttribute('height', height + 'px');
svg.cachedHeight_ = height;
}
mainWorkspace.resize();
};
/**
* Handle a mouse-up anywhere on the page.
* @param {!Event} e Mouse up event.
* @private
*/
Blockly.onMouseUp_ = function(e) {
var workspace = Blockly.getMainWorkspace();
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
workspace.dragMode_ = Blockly.DRAG_NONE;
// Unbind the touch event if it exists.
if (Blockly.onTouchUpWrapper_) {
Blockly.unbindEvent_(Blockly.onTouchUpWrapper_);
Blockly.onTouchUpWrapper_ = null;
}
if (Blockly.onMouseMoveWrapper_) {
Blockly.unbindEvent_(Blockly.onMouseMoveWrapper_);
Blockly.onMouseMoveWrapper_ = null;
}
};
/**
* Handle a mouse-move on SVG drawing surface.
* @param {!Event} e Mouse move event.
* @private
*/
Blockly.onMouseMove_ = function(e) {
if (e.touches && e.touches.length >= 2) {
return; // Multi-touch gestures won't have e.clientX.
}
var workspace = Blockly.getMainWorkspace();
if (workspace.dragMode_ != Blockly.DRAG_NONE) {
var dx = e.clientX - workspace.startDragMouseX;
var dy = e.clientY - workspace.startDragMouseY;
var metrics = workspace.startDragMetrics;
var x = workspace.startScrollX + dx;
var y = workspace.startScrollY + dy;
x = Math.min(x, -metrics.contentLeft);
y = Math.min(y, -metrics.contentTop);
x = Math.max(x, metrics.viewWidth - metrics.contentLeft -
metrics.contentWidth);
y = Math.max(y, metrics.viewHeight - metrics.contentTop -
metrics.contentHeight);
// Move the scrollbars and the page will scroll automatically.
workspace.scrollbar.set(-x - metrics.contentLeft,
-y - metrics.contentTop);
// Cancel the long-press if the drag has moved too far.
if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) {
Blockly.longStop_();
workspace.dragMode_ = Blockly.DRAG_FREE;
}
e.stopPropagation();
e.preventDefault();
}
};
/**
* Handle a key-down on SVG drawing surface.
* @param {!Event} e Key down event.
* @private
*/
Blockly.onKeyDown_ = function(e) {
if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) {
// No key actions on readonly workspaces.
// When focused on an HTML text input widget, don't trap any keys.
return;
}
var deleteBlock = false;
if (e.keyCode == 27) {
// Pressing esc closes the context menu.
Blockly.hideChaff();
} else if (e.keyCode == 8 || e.keyCode == 46) {
// Delete or backspace.
// Stop the browser from going back to the previous page.
// Do this first to prevent an error in the delete code from resulting in
// data loss.
e.preventDefault();
if (Blockly.selected && Blockly.selected.isDeletable()) {
deleteBlock = true;
}
} else if (e.altKey || e.ctrlKey || e.metaKey) {
if (Blockly.selected &&
Blockly.selected.isDeletable() && Blockly.selected.isMovable()) {
if (e.keyCode == 67) {
// 'c' for copy.
Blockly.hideChaff();
Blockly.copy_(Blockly.selected);
} else if (e.keyCode == 88) {
// 'x' for cut.
Blockly.copy_(Blockly.selected);
deleteBlock = true;
}
}
if (e.keyCode == 86) {
// 'v' for paste.
if (Blockly.clipboardXml_) {
Blockly.Events.setGroup(true);
Blockly.clipboardSource_.paste(Blockly.clipboardXml_);
Blockly.Events.setGroup(false);
}
} else if (e.keyCode == 90) {
// 'z' for undo 'Z' is for redo.
Blockly.hideChaff();
Blockly.mainWorkspace.undo(e.shiftKey);
}
}
if (deleteBlock) {
// Common code for delete and cut.
Blockly.Events.setGroup(true);
Blockly.hideChaff();
var heal = Blockly.dragMode_ != Blockly.DRAG_FREE;
Blockly.selected.dispose(heal, true);
if (Blockly.highlightedConnection_) {
Blockly.highlightedConnection_.unhighlight();
Blockly.highlightedConnection_ = null;
}
Blockly.Events.setGroup(false);
}
};
/**
* Stop binding to the global mouseup and mousemove events.
* @private
*/
Blockly.terminateDrag_ = function() {
Blockly.BlockSvg.terminateDrag();
Blockly.Flyout.terminateDrag_();
};
/**
* PID of queued long-press task.
* @private
*/
Blockly.longPid_ = 0;
/**
* Context menus on touch devices are activated using a long-press.
* Unfortunately the contextmenu touch event is currently (2015) only suported
* by Chrome. This function is fired on any touchstart event, queues a task,
* which after about a second opens the context menu. The tasks is killed
* if the touch event terminates early.
* @param {!Event} e Touch start event.
* @param {!Blockly.Block|!Blockly.WorkspaceSvg} uiObject The block or workspace
* under the touchstart event.
* @private
*/
Blockly.longStart_ = function(e, uiObject) {
Blockly.longStop_();
Blockly.longPid_ = setTimeout(function() {
e.button = 2; // Simulate a right button click.
uiObject.onMouseDown_(e);
}, Blockly.LONGPRESS);
};
/**
* Nope, that's not a long-press. Either touchend or touchcancel was fired,
* or a drag hath begun. Kill the queued long-press task.
* @private
*/
Blockly.longStop_ = function() {
if (Blockly.longPid_) {
clearTimeout(Blockly.longPid_);
Blockly.longPid_ = 0;
}
};
/**
* Copy a block onto the local clipboard.
* @param {!Blockly.Block} block Block to be copied.
* @private
*/
Blockly.copy_ = function(block) {
var xmlBlock = Blockly.Xml.blockToDom(block);
if (Blockly.dragMode_ != Blockly.DRAG_FREE) {
Blockly.Xml.deleteNext(xmlBlock);
}
// Encode start position in XML.
var xy = block.getRelativeToSurfaceXY();
xmlBlock.setAttribute('x', block.RTL ? -xy.x : xy.x);
xmlBlock.setAttribute('y', xy.y);
Blockly.clipboardXml_ = xmlBlock;
Blockly.clipboardSource_ = block.workspace;
};
/**
* Duplicate this block and its children.
* @param {!Blockly.Block} block Block to be copied.
* @private
*/
Blockly.duplicate_ = function(block) {
// Save the clipboard.
var clipboardXml = Blockly.clipboardXml_;
var clipboardSource = Blockly.clipboardSource_;
// Create a duplicate via a copy/paste operation.
Blockly.copy_(block);
block.workspace.paste(Blockly.clipboardXml_);
// Restore the clipboard.
Blockly.clipboardXml_ = clipboardXml;
Blockly.clipboardSource_ = clipboardSource;
};
/**
* Cancel the native context menu, unless the focus is on an HTML input widget.
* @param {!Event} e Mouse down event.
* @private
*/
Blockly.onContextMenu_ = function(e) {
if (!Blockly.isTargetInput_(e)) {
// When focused on an HTML text input widget, don't cancel the context menu.
e.preventDefault();
}
};
/**
* Close tooltips, context menus, dropdown selections, etc.
* @param {boolean=} opt_allowToolbox If true, don't close the toolbox.
*/
Blockly.hideChaff = function(opt_allowToolbox) {
Blockly.Tooltip.hide();
Blockly.WidgetDiv.hide();
if (!opt_allowToolbox) {
var workspace = Blockly.getMainWorkspace();
if (workspace.toolbox_ &&
workspace.toolbox_.flyout_ &&
workspace.toolbox_.flyout_.autoClose) {
workspace.toolbox_.clearSelection();
}
}
};
/**
* When something in Blockly's workspace changes, call a function.
* @param {!Function} func Function to call.
* @return {!Array.<!Array>} Opaque data that can be passed to
* removeChangeListener.
* @deprecated April 2015
*/
Blockly.addChangeListener = function(func) {
// Backwards compatability from before there could be multiple workspaces.
console.warn('Deprecated call to Blockly.addChangeListener, ' +
'use workspace.addChangeListener instead.');
return Blockly.getMainWorkspace().addChangeListener(func);
};
/**
* Returns the main workspace. Returns the last used main workspace (based on
* focus). Try not to use this function, particularly if there are multiple
* Blockly instances on a page.
* @return {!Blockly.Workspace} The main workspace.
*/
Blockly.getMainWorkspace = function() {
return Blockly.mainWorkspace;
};
// IE9 does not have a console. Create a stub to stop errors.
if (!goog.global['console']) {
goog.global['console'] = {
'log': function() {},
'warn': function() {}
};
}
// Export symbols that would otherwise be renamed by Closure compiler.
if (!goog.global['Blockly']) {
goog.global['Blockly'] = {};
}
goog.global['Blockly']['getMainWorkspace'] = Blockly.getMainWorkspace;
goog.global['Blockly']['addChangeListener'] = Blockly.addChangeListener;

461
core/blockly.js.rej Normal file
View File

@@ -0,0 +1,461 @@
***************
*** 24,32 ****
*/
'use strict';
// Top level object for Blockly.
goog.provide('Blockly');
// Blockly core dependencies.
goog.require('Blockly.Block');
goog.require('Blockly.Connection');
--- 24,38 ----
*/
'use strict';
+ /**
+ * [lyn, 10/10/13] Modified Blockly.hideChaff() method to hide single instance of Blockly.FieldFlydown.
+ */
+
// Top level object for Blockly.
goog.provide('Blockly');
+ goog.require('Blockly.Instrument'); // lyn's instrumentation code
+
// Blockly core dependencies.
goog.require('Blockly.Block');
goog.require('Blockly.Connection');
***************
*** 41,47 ****
goog.require('Blockly.Msg');
goog.require('Blockly.Procedures');
goog.require('Blockly.Realtime');
- goog.require('Blockly.Toolbox');
goog.require('Blockly.WidgetDiv');
goog.require('Blockly.Workspace');
goog.require('Blockly.inject');
--- 47,54 ----
goog.require('Blockly.Msg');
goog.require('Blockly.Procedures');
goog.require('Blockly.Realtime');
+ //goog.require('Blockly.Toolbox');
+ goog.require('Blockly.TypeBlock');
goog.require('Blockly.WidgetDiv');
goog.require('Blockly.Workspace');
goog.require('Blockly.inject');
***************
*** 99,107 ****
* @param {number} hue Hue on a colour wheel (0-360).
* @return {string} RGB code, e.g. '#5ba65b'.
*/
- Blockly.makeColour = function(hue) {
- return goog.color.hsvToHex(hue, Blockly.HSV_SATURATION,
Blockly.HSV_VALUE * 256);
};
/**
--- 106,118 ----
* @param {number} hue Hue on a colour wheel (0-360).
* @return {string} RGB code, e.g. '#5ba65b'.
*/
+ Blockly.makeColour = function(hueOrRGBArray) {
+ if(Array.isArray(hueOrRGBArray)){
+ return goog.color.rgbArrayToHex(hueOrRGBArray);
+ } else {
+ return goog.color.hsvToHex(hueOrRGBArray, Blockly.HSV_SATURATION,
Blockly.HSV_VALUE * 256);
+ }
};
/**
***************
*** 131,136 ****
Blockly.DUMMY_INPUT = 5;
/**
* ENUM for left alignment.
* @const
*/
--- 142,154 ----
Blockly.DUMMY_INPUT = 5;
/**
+ * ENUM for an indented value input. Similar to next_statement but with value
+ * input shape.
+ * @const
+ */
+ Blockly.INDENTED_VALUE = 6;
+
+ /**
* ENUM for left alignment.
* @const
*/
***************
*** 163,176 ****
};
/**
* Handle a mouse-down on SVG drawing surface.
* @param {!Event} e Mouse down event.
* @private
*/
Blockly.onMouseDown_ = function(e) {
Blockly.svgResize();
Blockly.terminateDrag_(); // In case mouse-up event was lost.
Blockly.hideChaff();
var isTargetSvg = e.target && e.target.nodeName &&
e.target.nodeName.toLowerCase() == 'svg';
if (!Blockly.readOnly && Blockly.selected && isTargetSvg) {
--- 196,237 ----
};
/**
+ * latest clicked position is used to open the type blocking suggestions window
+ * Initial position is 0,0
+ * @type {{x: number, y:number}}
+ */
+ Blockly.latestClick = { x: 0, y: 0 };
+
+ /**
* Handle a mouse-down on SVG drawing surface.
* @param {!Event} e Mouse down event.
* @private
*/
Blockly.onMouseDown_ = function(e) {
+ Blockly.latestClick = { x: e.clientX, y: e.clientY }; // Might be needed?
Blockly.svgResize();
Blockly.terminateDrag_(); // In case mouse-up event was lost.
Blockly.hideChaff();
+ //if drawer exists and supposed to close
+ if(Blockly.Drawer && Blockly.Drawer.flyout_.autoClose) {
+ Blockly.Drawer.hide();
+ }
+
+ //Closes mutators
+ var blocks = Blockly.mainWorkspace.getAllBlocks();
+ var numBlocks = blocks.length;
+ var temp_block = null;
+ for(var i =0; i<numBlocks; i++){
+ temp_block = blocks[i];
+ if(temp_block.mutator){
+ //deselect block in mutator workspace
+ if(Blockly.selected && Blockly.selected.workspace && Blockly.selected.workspace!=Blockly.mainWorkspace){
+ Blockly.selected.unselect();
+ }
+ blocks[i].mutator.setVisible(false);
+ }
+ }
+
var isTargetSvg = e.target && e.target.nodeName &&
e.target.nodeName.toLowerCase() == 'svg';
if (!Blockly.readOnly && Blockly.selected && isTargetSvg) {
***************
*** 265,272 ****
// Delete or backspace.
try {
if (Blockly.selected && Blockly.selected.isDeletable()) {
- Blockly.hideChaff();
- Blockly.selected.dispose(true, true);
}
} finally {
// Stop the browser from going back to the previous page.
--- 326,347 ----
// Delete or backspace.
try {
if (Blockly.selected && Blockly.selected.isDeletable()) {
+ var descendantCount = Blockly.selected.getDescendants().length;
+ if (Blockly.selected.nextConnection && Blockly.selected.nextConnection.targetConnection) {
+ descendantCount -= Blockly.selected.nextConnection.targetBlock().
+ getDescendants().length;
+ }
+ // Ask for confirmation before deleting 3 or more blocks
+ if (descendantCount >= 3) {
+ if (confirm("Are you sure you want to delete all " + descendantCount + " of these blocks?")) {
+ Blockly.hideChaff();
+ Blockly.selected.dispose(true, true);
+ }
+ }
+ else {
+ Blockly.hideChaff();
+ Blockly.selected.dispose(true, true);
+ }
}
} finally {
// Stop the browser from going back to the previous page.
***************
*** 363,368 ****
ms += COLLAPSE_DELAY;
}
}
};
options.push(collapseOption);
--- 438,444 ----
ms += COLLAPSE_DELAY;
}
}
+ Blockly.resetWorkspaceArrangements();
};
options.push(collapseOption);
***************
*** 370,390 ****
var expandOption = {enabled: hasCollapsedBlocks};
expandOption.text = Blockly.Msg.EXPAND_ALL;
expandOption.callback = function() {
- var ms = 0;
- for (var i = 0; i < topBlocks.length; i++) {
- var block = topBlocks[i];
- while (block) {
- setTimeout(block.setCollapsed.bind(block, false), ms);
- block = block.getNextBlock();
- ms += COLLAPSE_DELAY;
- }
- }
};
options.push(expandOption);
}
Blockly.ContextMenu.show(e, options);
};
/**
* Cancel the native context menu, unless the focus is on an HTML input widget.
--- 446,621 ----
var expandOption = {enabled: hasCollapsedBlocks};
expandOption.text = Blockly.Msg.EXPAND_ALL;
expandOption.callback = function() {
+ Blockly.Instrument.initializeStats("expandAllCollapsedBlocks");
+ Blockly.Instrument.timer(
+ function () {
+ var ms = 0;
+ for (var i = 0; i < topBlocks.length; i++) {
+ var block = topBlocks[i];
+ while (block) {
+ setTimeout(block.setCollapsed.bind(block, false), ms);
+ block = block.getNextBlock();
+ ms += COLLAPSE_DELAY;
+ }
+ }
+ Blockly.resetWorkspaceArrangements();
+ },
+ function (result, timeDiff) {
+ Blockly.Instrument.stats.totalTime = timeDiff;
+ Blockly.Instrument.displayStats("expandAllCollapsedBlocks");
+ }
+ );
};
options.push(expandOption);
}
+ // Arrange blocks in row order.
+ var arrangeOptionH = {enabled: (Blockly.workspace_arranged_position !== Blockly.BLKS_HORIZONTAL)};
+ arrangeOptionH.text = Blockly.Msg.ARRANGE_H;
+ arrangeOptionH.callback = function() {
+ Blockly.workspace_arranged_position = Blockly.BLKS_HORIZONTAL;
+ Blockly.workspace_arranged_latest_position= Blockly.BLKS_HORIZONTAL;
+ arrangeBlocks(Blockly.BLKS_HORIZONTAL);
+ };
+ options.push(arrangeOptionH);
+
+ // Arrange blocks in column order.
+ var arrangeOptionV = {enabled: (Blockly.workspace_arranged_position !== Blockly.BLKS_VERTICAL)};
+ arrangeOptionV.text = Blockly.Msg.ARRANGE_V;
+ arrangeOptionV.callback = function() {
+ Blockly.workspace_arranged_position = Blockly.BLKS_VERTICAL;
+ Blockly.workspace_arranged_latest_position = Blockly.BLKS_VERTICAL;
+ arrangeBlocks(Blockly.BLKS_VERTICAL);
+ };
+ options.push(arrangeOptionV);
+
+ /**
+ * Function that returns a name to be used to sort blocks.
+ * The general comparator is the block.category attribute.
+ * In the case of 'Components' the comparator is the instanceName of the component if it exists
+ * (it does not exist for generic components).
+ * In the case of Procedures the comparator is the NAME(for definitions) or PROCNAME (for calls)
+ * @param {!Blockly.Block} the block that will be compared in the sortByCategory function
+ * @returns {string} text to be used in the comparison
+ */
+ function comparisonName(block){
+ if (block.category === 'Component' && block.instanceName)
+ return block.instanceName;
+ if (block.category === 'Procedures')
+ return (block.getFieldValue('NAME') || block.getFieldValue('PROCNAME'));
+ return block.category;
+ }
+
+ /**
+ * Function used to sort blocks by Category.
+ * @param {!Blockly.Block} a first block to be compared
+ * @param {!Blockly.Block} b second block to be compared
+ * @returns {number} returns 0 if the blocks are equal, and -1 or 1 if they are not
+ */
+ function sortByCategory(a,b) {
+ var comparatorA = comparisonName(a).toLowerCase();
+ var comparatorB = comparisonName(b).toLowerCase();
+
+ if (comparatorA < comparatorB) return -1;
+ else if (comparatorA > comparatorB) return +1;
+ else return 0;
+ }
+
+ // Arranges block in layout (Horizontal or Vertical).
+ function arrangeBlocks(layout) {
+ var SPACER = 25;
+ var topblocks = Blockly.mainWorkspace.getTopBlocks(false);
+ // If the blocks are arranged by Category, sort the array
+ if (Blockly.workspace_arranged_type === Blockly.BLKS_CATEGORY){
+ topblocks.sort(sortByCategory);
+ }
+ var metrics = Blockly.mainWorkspace.getMetrics();
+ var viewLeft = metrics.viewLeft + 5;
+ var viewTop = metrics.viewTop + 5;
+ var x = viewLeft;
+ var y = viewTop;
+ var wsRight = viewLeft + metrics.viewWidth;
+ var wsBottom = viewTop + metrics.viewHeight;
+ var maxHgt = 0;
+ var maxWidth = 0;
+ for (var i = 0, len = topblocks.length; i < len; i++) {
+ var blk = topblocks[i];
+ var blkXY = blk.getRelativeToSurfaceXY();
+ var blockHW = blk.getHeightWidth();
+ var blkHgt = blockHW.height;
+ var blkWidth = blockHW.width;
+ switch (layout) {
+ case Blockly.BLKS_HORIZONTAL:
+ if (x < wsRight) {
+ blk.moveBy(x - blkXY.x, y - blkXY.y);
+ blk.select();
+ x += blkWidth + SPACER;
+ if (blkHgt > maxHgt) // Remember highest block
+ maxHgt = blkHgt;
+ } else {
+ y += maxHgt + SPACER;
+ maxHgt = blkHgt;
+ x = viewLeft;
+ blk.moveBy(x - blkXY.x, y - blkXY.y);
+ blk.select();
+ x += blkWidth + SPACER;
+ }
+ break;
+ case Blockly.BLKS_VERTICAL:
+ if (y < wsBottom) {
+ blk.moveBy(x - blkXY.x, y - blkXY.y);
+ blk.select();
+ y += blkHgt + SPACER;
+ if (blkWidth > maxWidth) // Remember widest block
+ maxWidth = blkWidth;
+ } else {
+ x += maxWidth + SPACER;
+ maxWidth = blkWidth;
+ y = viewTop;
+ blk.moveBy(x - blkXY.x, y - blkXY.y);
+ blk.select();
+ y += blkHgt + SPACER;
+ }
+ break;
+ }
+ }
+ }
+
+ // Sort by Category.
+ var sortOptionCat = {enabled: (Blockly.workspace_arranged_type !== Blockly.BLKS_CATEGORY)};
+ sortOptionCat.text = Blockly.Msg.SORT_C;
+ sortOptionCat.callback = function() {
+ Blockly.workspace_arranged_type = Blockly.BLKS_CATEGORY;
+ rearrangeWorkspace();
+ };
+ options.push(sortOptionCat);
+
+ // Called after a sort or collapse/expand to redisplay blocks.
+ function rearrangeWorkspace() {
+ //default arrangement position set to Horizontal if it hasn't been set yet (is null)
+ if (Blockly.workspace_arranged_latest_position === null || Blockly.workspace_arranged_latest_position === Blockly.BLKS_HORIZONTAL)
+ arrangeOptionH.callback();
+ else if (Blockly.workspace_arranged_latest_position === Blockly.BLKS_VERTICAL)
+ arrangeOptionV.callback();
+ }
+
+ // Option to get help.
+ var helpOption = {enabled: false};
+ helpOption.text = Blockly.Msg.HELP;
+ helpOption.callback = function() {};
+ options.push(helpOption);
+
Blockly.ContextMenu.show(e, options);
};
+ /**
+ * reset arrangement state; to be called when blocks in the workspace change
+ */
+ Blockly.resetWorkspaceArrangements = function(){
+ // reset the variables used for menus, but keep the latest position, so the current horizontal or
+ // vertical state can be kept
+ Blockly.workspace_arranged_type = null;
+ Blockly.workspace_arranged_position = null;
+ };
/**
* Cancel the native context menu, unless the focus is on an HTML input widget.
***************
*** 404,414 ****
*/
Blockly.hideChaff = function(opt_allowToolbox) {
Blockly.Tooltip.hide();
Blockly.WidgetDiv.hide();
- if (!opt_allowToolbox &&
- Blockly.Toolbox.flyout_ && Blockly.Toolbox.flyout_.autoClose) {
- Blockly.Toolbox.clearSelection();
- }
};
/**
--- 635,643 ----
*/
Blockly.hideChaff = function(opt_allowToolbox) {
Blockly.Tooltip.hide();
+ Blockly.FieldFlydown && Blockly.FieldFlydown.hide(); // [lyn, 10/06/13] for handling parameter & procedure flydowns
Blockly.WidgetDiv.hide();
+ Blockly.TypeBlock && Blockly.TypeBlock.hide();
};
/**
***************
*** 556,562 ****
*/
Blockly.getMainWorkspaceMetrics_ = function() {
var svgSize = Blockly.svgSize();
- svgSize.width -= Blockly.Toolbox.width; // Zero if no Toolbox.
var viewWidth = svgSize.width - Blockly.Scrollbar.scrollbarThickness;
var viewHeight = svgSize.height - Blockly.Scrollbar.scrollbarThickness;
try {
--- 785,793 ----
*/
Blockly.getMainWorkspaceMetrics_ = function() {
var svgSize = Blockly.svgSize();
+ //We don't use Blockly.Toolbox in our version of Blockly instead we use drawer.js
+ //svgSize.width -= Blockly.Toolbox.width; // Zero if no Toolbox.
+ svgSize.width -= 0; // Zero if no Toolbox.
var viewWidth = svgSize.width - Blockly.Scrollbar.scrollbarThickness;
var viewHeight = svgSize.height - Blockly.Scrollbar.scrollbarThickness;
try {
***************
*** 582,588 ****
var topEdge = blockBox.y;
var bottomEdge = topEdge + blockBox.height;
}
- var absoluteLeft = Blockly.RTL ? 0 : Blockly.Toolbox.width;
var metrics = {
viewHeight: svgSize.height,
viewWidth: svgSize.width,
--- 813,821 ----
var topEdge = blockBox.y;
var bottomEdge = topEdge + blockBox.height;
}
+ //We don't use Blockly.Toolbox in our version of Blockly instead we use drawer.js
+ //var absoluteLeft = Blockly.RTL ? 0 : Blockly.Toolbox.width;
+ var absoluteLeft = Blockly.RTL ? 0 : 0;
var metrics = {
viewHeight: svgSize.height,
viewWidth: svgSize.width,

33
core/blocks.js.orig Normal file
View File

@@ -0,0 +1,33 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2013 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Empty name space for the Blocks singleton.
* @author spertus@google.com (Ellen Spertus)
*/
'use strict';
goog.provide('Blockly.Blocks');
/**
* Allow for switching between one and zero based indexing for lists and text,
* one based by default.
*/
Blockly.Blocks.ONE_BASED_INDEXING = true;

19
core/blocks.js.rej Normal file
View File

@@ -0,0 +1,19 @@
***************
*** 168,175 ****
* @this Blockly.Block
*/
block.mutationToDom = function() {
- var container = details.mutationToDomFunc ?
- details.mutatationToDomFunc() : document.createElement('mutation');
container.setAttribute('is_statement', this['isStatement'] || false);
return container;
};
--- 168,175 ----
* @this Blockly.Block
*/
block.mutationToDom = function() {
+ var container = details.mutationToDomFunc ? details.mutatationToDomFunc()
+ : document.createElement('mutation');
container.setAttribute('is_statement', this['isStatement'] || false);
return container;
};

View File

@@ -62,7 +62,12 @@ Blockly.Bubble = function(workspace, content, shape, anchorXY,
this.setAnchorLocation(anchorXY);
if (!bubbleWidth || !bubbleHeight) {
var bBox = /** @type {SVGLocatable} */ (this.content_).getBBox();
try {
var bBox = /** @type {SVGLocatable} */ (this.content_).getBBox();
} catch (e) {
// Firefox has trouble with hidden elements (Bug 528969).
var bBox = {height: 0, width: 0};
}
bubbleWidth = bBox.width + 2 * Blockly.Bubble.BORDER_WIDTH;
bubbleHeight = bBox.height + 2 * Blockly.Bubble.BORDER_WIDTH;
}

579
core/bubble.js.orig Normal file
View File

@@ -0,0 +1,579 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Object representing a UI bubble.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Bubble');
goog.require('Blockly.Workspace');
goog.require('goog.dom');
goog.require('goog.math');
goog.require('goog.math.Coordinate');
goog.require('goog.userAgent');
/**
* Class for UI bubble.
* @param {!Blockly.WorkspaceSvg} workspace The workspace on which to draw the
* bubble.
* @param {!Element} content SVG content for the bubble.
* @param {Element} shape SVG element to avoid eclipsing.
* @param {!goog.math.Coodinate} anchorXY Absolute position of bubble's anchor
* point.
* @param {?number} bubbleWidth Width of bubble, or null if not resizable.
* @param {?number} bubbleHeight Height of bubble, or null if not resizable.
* @constructor
*/
Blockly.Bubble = function(workspace, content, shape, anchorXY,
bubbleWidth, bubbleHeight) {
this.workspace_ = workspace;
this.content_ = content;
this.shape_ = shape;
var angle = Blockly.Bubble.ARROW_ANGLE;
if (this.workspace_.RTL) {
angle = -angle;
}
this.arrow_radians_ = goog.math.toRadians(angle);
var canvas = workspace.getBubbleCanvas();
canvas.appendChild(this.createDom_(content, !!(bubbleWidth && bubbleHeight)));
this.setAnchorLocation(anchorXY);
if (!bubbleWidth || !bubbleHeight) {
var bBox = /** @type {SVGLocatable} */ (this.content_).getBBox();
bubbleWidth = bBox.width + 2 * Blockly.Bubble.BORDER_WIDTH;
bubbleHeight = bBox.height + 2 * Blockly.Bubble.BORDER_WIDTH;
}
this.setBubbleSize(bubbleWidth, bubbleHeight);
// Render the bubble.
this.positionBubble_();
this.renderArrow_();
this.rendered_ = true;
if (!workspace.options.readOnly) {
Blockly.bindEvent_(this.bubbleBack_, 'mousedown', this,
this.bubbleMouseDown_);
if (this.resizeGroup_) {
Blockly.bindEvent_(this.resizeGroup_, 'mousedown', this,
this.resizeMouseDown_);
}
}
};
/**
* Width of the border around the bubble.
*/
Blockly.Bubble.BORDER_WIDTH = 6;
/**
* Determines the thickness of the base of the arrow in relation to the size
* of the bubble. Higher numbers result in thinner arrows.
*/
Blockly.Bubble.ARROW_THICKNESS = 10;
/**
* The number of degrees that the arrow bends counter-clockwise.
*/
Blockly.Bubble.ARROW_ANGLE = 20;
/**
* The sharpness of the arrow's bend. Higher numbers result in smoother arrows.
*/
Blockly.Bubble.ARROW_BEND = 4;
/**
* Distance between arrow point and anchor point.
*/
Blockly.Bubble.ANCHOR_RADIUS = 8;
/**
* Wrapper function called when a mouseUp occurs during a drag operation.
* @type {Array.<!Array>}
* @private
*/
Blockly.Bubble.onMouseUpWrapper_ = null;
/**
* Wrapper function called when a mouseMove occurs during a drag operation.
* @type {Array.<!Array>}
* @private
*/
Blockly.Bubble.onMouseMoveWrapper_ = null;
/**
* Function to call on resize of bubble.
* @type {Function}
*/
Blockly.Bubble.prototype.resizeCallback_ = null;
/**
* Stop binding to the global mouseup and mousemove events.
* @private
*/
Blockly.Bubble.unbindDragEvents_ = function() {
if (Blockly.Bubble.onMouseUpWrapper_) {
Blockly.unbindEvent_(Blockly.Bubble.onMouseUpWrapper_);
Blockly.Bubble.onMouseUpWrapper_ = null;
}
if (Blockly.Bubble.onMouseMoveWrapper_) {
Blockly.unbindEvent_(Blockly.Bubble.onMouseMoveWrapper_);
Blockly.Bubble.onMouseMoveWrapper_ = null;
}
};
/**
* Flag to stop incremental rendering during construction.
* @private
*/
Blockly.Bubble.prototype.rendered_ = false;
/**
* Absolute coordinate of anchor point.
* @type {goog.math.Coordinate}
* @private
*/
Blockly.Bubble.prototype.anchorXY_ = null;
/**
* Relative X coordinate of bubble with respect to the anchor's centre.
* In RTL mode the initial value is negated.
* @private
*/
Blockly.Bubble.prototype.relativeLeft_ = 0;
/**
* Relative Y coordinate of bubble with respect to the anchor's centre.
* @private
*/
Blockly.Bubble.prototype.relativeTop_ = 0;
/**
* Width of bubble.
* @private
*/
Blockly.Bubble.prototype.width_ = 0;
/**
* Height of bubble.
* @private
*/
Blockly.Bubble.prototype.height_ = 0;
/**
* Automatically position and reposition the bubble.
* @private
*/
Blockly.Bubble.prototype.autoLayout_ = true;
/**
* Create the bubble's DOM.
* @param {!Element} content SVG content for the bubble.
* @param {boolean} hasResize Add diagonal resize gripper if true.
* @return {!Element} The bubble's SVG group.
* @private
*/
Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
/* Create the bubble. Here's the markup that will be generated:
<g>
<g filter="url(#blocklyEmbossFilter837493)">
<path d="... Z" />
<rect class="blocklyDraggable" rx="8" ry="8" width="180" height="180"/>
</g>
<g transform="translate(165, 165)" class="blocklyResizeSE">
<polygon points="0,15 15,15 15,0"/>
<line class="blocklyResizeLine" x1="5" y1="14" x2="14" y2="5"/>
<line class="blocklyResizeLine" x1="10" y1="14" x2="14" y2="10"/>
</g>
[...content goes here...]
</g>
*/
this.bubbleGroup_ = Blockly.createSvgElement('g', {}, null);
var filter =
{'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'};
if (goog.userAgent.getUserAgentString().indexOf('JavaFX') != -1) {
// Multiple reports that JavaFX can't handle filters. UserAgent:
// Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.44
// (KHTML, like Gecko) JavaFX/8.0 Safari/537.44
// https://github.com/google/blockly/issues/99
filter = {};
}
var bubbleEmboss = Blockly.createSvgElement('g',
filter, this.bubbleGroup_);
this.bubbleArrow_ = Blockly.createSvgElement('path', {}, bubbleEmboss);
this.bubbleBack_ = Blockly.createSvgElement('rect',
{'class': 'blocklyDraggable', 'x': 0, 'y': 0,
'rx': Blockly.Bubble.BORDER_WIDTH, 'ry': Blockly.Bubble.BORDER_WIDTH},
bubbleEmboss);
if (hasResize) {
this.resizeGroup_ = Blockly.createSvgElement('g',
{'class': this.workspace_.RTL ?
'blocklyResizeSW' : 'blocklyResizeSE'},
this.bubbleGroup_);
var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH;
Blockly.createSvgElement('polygon',
{'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
this.resizeGroup_);
Blockly.createSvgElement('line',
{'class': 'blocklyResizeLine',
'x1': resizeSize / 3, 'y1': resizeSize - 1,
'x2': resizeSize - 1, 'y2': resizeSize / 3}, this.resizeGroup_);
Blockly.createSvgElement('line',
{'class': 'blocklyResizeLine',
'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1,
'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3}, this.resizeGroup_);
} else {
this.resizeGroup_ = null;
}
this.bubbleGroup_.appendChild(content);
return this.bubbleGroup_;
};
/**
* Handle a mouse-down on bubble's border.
* @param {!Event} e Mouse down event.
* @private
*/
Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
this.promote_();
Blockly.Bubble.unbindDragEvents_();
if (Blockly.isRightButton(e)) {
// No right-click.
e.stopPropagation();
return;
} else if (Blockly.isTargetInput_(e)) {
// When focused on an HTML text input widget, don't trap any events.
return;
}
// Left-click (or middle click)
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
this.workspace_.startDrag(e, new goog.math.Coordinate(
this.workspace_.RTL ? -this.relativeLeft_ : this.relativeLeft_,
this.relativeTop_));
Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document,
'mouseup', this, Blockly.Bubble.unbindDragEvents_);
Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
'mousemove', this, this.bubbleMouseMove_);
Blockly.hideChaff();
// This event has been handled. No need to bubble up to the document.
e.stopPropagation();
};
/**
* Drag this bubble to follow the mouse.
* @param {!Event} e Mouse move event.
* @private
*/
Blockly.Bubble.prototype.bubbleMouseMove_ = function(e) {
this.autoLayout_ = false;
var newXY = this.workspace_.moveDrag(e);
this.relativeLeft_ = this.workspace_.RTL ? -newXY.x : newXY.x;
this.relativeTop_ = newXY.y;
this.positionBubble_();
this.renderArrow_();
};
/**
* Handle a mouse-down on bubble's resize corner.
* @param {!Event} e Mouse down event.
* @private
*/
Blockly.Bubble.prototype.resizeMouseDown_ = function(e) {
this.promote_();
Blockly.Bubble.unbindDragEvents_();
if (Blockly.isRightButton(e)) {
// No right-click.
e.stopPropagation();
return;
}
// Left-click (or middle click)
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
this.workspace_.startDrag(e, new goog.math.Coordinate(
this.workspace_.RTL ? -this.width_ : this.width_, this.height_));
Blockly.Bubble.onMouseUpWrapper_ = Blockly.bindEvent_(document,
'mouseup', this, Blockly.Bubble.unbindDragEvents_);
Blockly.Bubble.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
'mousemove', this, this.resizeMouseMove_);
Blockly.hideChaff();
// This event has been handled. No need to bubble up to the document.
e.stopPropagation();
};
/**
* Resize this bubble to follow the mouse.
* @param {!Event} e Mouse move event.
* @private
*/
Blockly.Bubble.prototype.resizeMouseMove_ = function(e) {
this.autoLayout_ = false;
var newXY = this.workspace_.moveDrag(e);
this.setBubbleSize(this.workspace_.RTL ? -newXY.x : newXY.x, newXY.y);
if (this.workspace_.RTL) {
// RTL requires the bubble to move its left edge.
this.positionBubble_();
}
};
/**
* Register a function as a callback event for when the bubble is resized.
* @param {!Function} callback The function to call on resize.
*/
Blockly.Bubble.prototype.registerResizeEvent = function(callback) {
this.resizeCallback_ = callback;
};
/**
* Move this bubble to the top of the stack.
* @private
*/
Blockly.Bubble.prototype.promote_ = function() {
var svgGroup = this.bubbleGroup_.parentNode;
svgGroup.appendChild(this.bubbleGroup_);
};
/**
* Notification that the anchor has moved.
* Update the arrow and bubble accordingly.
* @param {!goog.math.Coordinate} xy Absolute location.
*/
Blockly.Bubble.prototype.setAnchorLocation = function(xy) {
this.anchorXY_ = xy;
if (this.rendered_) {
this.positionBubble_();
}
};
/**
* Position the bubble so that it does not fall off-screen.
* @private
*/
Blockly.Bubble.prototype.layoutBubble_ = function() {
// Compute the preferred bubble location.
var relativeLeft = -this.width_ / 4;
var relativeTop = -this.height_ - Blockly.BlockSvg.MIN_BLOCK_Y;
// Prevent the bubble from being off-screen.
var metrics = this.workspace_.getMetrics();
metrics.viewWidth /= this.workspace_.scale;
metrics.viewLeft /= this.workspace_.scale;
var anchorX = this.anchorXY_.x;
if (this.workspace_.RTL) {
if (anchorX - metrics.viewLeft - relativeLeft - this.width_ <
Blockly.Scrollbar.scrollbarThickness) {
// Slide the bubble right until it is onscreen.
relativeLeft = anchorX - metrics.viewLeft - this.width_ -
Blockly.Scrollbar.scrollbarThickness;
} else if (anchorX - metrics.viewLeft - relativeLeft >
metrics.viewWidth) {
// Slide the bubble left until it is onscreen.
relativeLeft = anchorX - metrics.viewLeft - metrics.viewWidth;
}
} else {
if (anchorX + relativeLeft < metrics.viewLeft) {
// Slide the bubble right until it is onscreen.
relativeLeft = metrics.viewLeft - anchorX;
} else if (metrics.viewLeft + metrics.viewWidth <
anchorX + relativeLeft + this.width_ +
Blockly.BlockSvg.SEP_SPACE_X +
Blockly.Scrollbar.scrollbarThickness) {
// Slide the bubble left until it is onscreen.
relativeLeft = metrics.viewLeft + metrics.viewWidth - anchorX -
this.width_ - Blockly.Scrollbar.scrollbarThickness;
}
}
if (this.anchorXY_.y + relativeTop < metrics.viewTop) {
// Slide the bubble below the block.
var bBox = /** @type {SVGLocatable} */ (this.shape_).getBBox();
relativeTop = bBox.height;
}
this.relativeLeft_ = relativeLeft;
this.relativeTop_ = relativeTop;
};
/**
* Move the bubble to a location relative to the anchor's centre.
* @private
*/
Blockly.Bubble.prototype.positionBubble_ = function() {
var left = this.anchorXY_.x;
if (this.workspace_.RTL) {
left -= this.relativeLeft_ + this.width_;
} else {
left += this.relativeLeft_;
}
var top = this.relativeTop_ + this.anchorXY_.y;
this.bubbleGroup_.setAttribute('transform',
'translate(' + left + ',' + top + ')');
};
/**
* Get the dimensions of this bubble.
* @return {!Object} Object with width and height properties.
*/
Blockly.Bubble.prototype.getBubbleSize = function() {
return {width: this.width_, height: this.height_};
};
/**
* Size this bubble.
* @param {number} width Width of the bubble.
* @param {number} height Height of the bubble.
*/
Blockly.Bubble.prototype.setBubbleSize = function(width, height) {
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
// Minimum size of a bubble.
width = Math.max(width, doubleBorderWidth + 45);
height = Math.max(height, doubleBorderWidth + 20);
this.width_ = width;
this.height_ = height;
this.bubbleBack_.setAttribute('width', width);
this.bubbleBack_.setAttribute('height', height);
if (this.resizeGroup_) {
if (this.workspace_.RTL) {
// Mirror the resize group.
var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH;
this.resizeGroup_.setAttribute('transform', 'translate(' +
resizeSize + ',' + (height - doubleBorderWidth) + ') scale(-1 1)');
} else {
this.resizeGroup_.setAttribute('transform', 'translate(' +
(width - doubleBorderWidth) + ',' +
(height - doubleBorderWidth) + ')');
}
}
if (this.rendered_) {
if (this.autoLayout_) {
this.layoutBubble_();
}
this.positionBubble_();
this.renderArrow_();
}
// Allow the contents to resize.
if (this.resizeCallback_) {
this.resizeCallback_();
}
};
/**
* Draw the arrow between the bubble and the origin.
* @private
*/
Blockly.Bubble.prototype.renderArrow_ = function() {
var steps = [];
// Find the relative coordinates of the center of the bubble.
var relBubbleX = this.width_ / 2;
var relBubbleY = this.height_ / 2;
// Find the relative coordinates of the center of the anchor.
var relAnchorX = -this.relativeLeft_;
var relAnchorY = -this.relativeTop_;
if (relBubbleX == relAnchorX && relBubbleY == relAnchorY) {
// Null case. Bubble is directly on top of the anchor.
// Short circuit this rather than wade through divide by zeros.
steps.push('M ' + relBubbleX + ',' + relBubbleY);
} else {
// Compute the angle of the arrow's line.
var rise = relAnchorY - relBubbleY;
var run = relAnchorX - relBubbleX;
if (this.workspace_.RTL) {
run *= -1;
}
var hypotenuse = Math.sqrt(rise * rise + run * run);
var angle = Math.acos(run / hypotenuse);
if (rise < 0) {
angle = 2 * Math.PI - angle;
}
// Compute a line perpendicular to the arrow.
var rightAngle = angle + Math.PI / 2;
if (rightAngle > Math.PI * 2) {
rightAngle -= Math.PI * 2;
}
var rightRise = Math.sin(rightAngle);
var rightRun = Math.cos(rightAngle);
// Calculate the thickness of the base of the arrow.
var bubbleSize = this.getBubbleSize();
var thickness = (bubbleSize.width + bubbleSize.height) /
Blockly.Bubble.ARROW_THICKNESS;
thickness = Math.min(thickness, bubbleSize.width, bubbleSize.height) / 2;
// Back the tip of the arrow off of the anchor.
var backoffRatio = 1 - Blockly.Bubble.ANCHOR_RADIUS / hypotenuse;
relAnchorX = relBubbleX + backoffRatio * run;
relAnchorY = relBubbleY + backoffRatio * rise;
// Coordinates for the base of the arrow.
var baseX1 = relBubbleX + thickness * rightRun;
var baseY1 = relBubbleY + thickness * rightRise;
var baseX2 = relBubbleX - thickness * rightRun;
var baseY2 = relBubbleY - thickness * rightRise;
// Distortion to curve the arrow.
var swirlAngle = angle + this.arrow_radians_;
if (swirlAngle > Math.PI * 2) {
swirlAngle -= Math.PI * 2;
}
var swirlRise = Math.sin(swirlAngle) *
hypotenuse / Blockly.Bubble.ARROW_BEND;
var swirlRun = Math.cos(swirlAngle) *
hypotenuse / Blockly.Bubble.ARROW_BEND;
steps.push('M' + baseX1 + ',' + baseY1);
steps.push('C' + (baseX1 + swirlRun) + ',' + (baseY1 + swirlRise) +
' ' + relAnchorX + ',' + relAnchorY +
' ' + relAnchorX + ',' + relAnchorY);
steps.push('C' + relAnchorX + ',' + relAnchorY +
' ' + (baseX2 + swirlRun) + ',' + (baseY2 + swirlRise) +
' ' + baseX2 + ',' + baseY2);
}
steps.push('z');
this.bubbleArrow_.setAttribute('d', steps.join(' '));
};
/**
* Change the colour of a bubble.
* @param {string} hexColour Hex code of colour.
*/
Blockly.Bubble.prototype.setColour = function(hexColour) {
this.bubbleBack_.setAttribute('fill', hexColour);
this.bubbleArrow_.setAttribute('fill', hexColour);
};
/**
* Dispose of this bubble.
*/
Blockly.Bubble.prototype.dispose = function() {
Blockly.Bubble.unbindDragEvents_();
// Dispose of and unlink the bubble.
goog.dom.removeNode(this.bubbleGroup_);
this.bubbleGroup_ = null;
this.bubbleArrow_ = null;
this.bubbleBack_ = null;
this.resizeGroup_ = null;
this.workspace_ = null;
this.content_ = null;
this.shape_ = null;
};

View File

@@ -212,6 +212,9 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
}
}, Blockly.BUMP_DELAY);
}
if (block.errorIcon) {
block.errorIcon.setVisible(false);
}
}
// Restore the shadow DOM.
parentConnection.setShadowDom(shadowDom);

615
core/connection.js.orig Normal file
View File

@@ -0,0 +1,615 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2011 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Components for creating connections between blocks.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Connection');
goog.require('goog.asserts');
goog.require('goog.dom');
/**
* Class for a connection between blocks.
* @param {!Blockly.Block} source The block establishing this connection.
* @param {number} type The type of the connection.
* @constructor
*/
Blockly.Connection = function(source, type) {
/**
* @type {!Blockly.Block}
* @private
*/
this.sourceBlock_ = source;
/** @type {number} */
this.type = type;
// Shortcut for the databases for this connection's workspace.
if (source.workspace.connectionDBList) {
this.db_ = source.workspace.connectionDBList[type];
this.dbOpposite_ =
source.workspace.connectionDBList[Blockly.OPPOSITE_TYPE[type]];
this.hidden_ = !this.db_;
}
};
/**
* Constants for checking whether two connections are compatible.
*/
Blockly.Connection.CAN_CONNECT = 0;
Blockly.Connection.REASON_SELF_CONNECTION = 1;
Blockly.Connection.REASON_WRONG_TYPE = 2;
Blockly.Connection.REASON_TARGET_NULL = 3;
Blockly.Connection.REASON_CHECKS_FAILED = 4;
Blockly.Connection.REASON_DIFFERENT_WORKSPACES = 5;
Blockly.Connection.REASON_SHADOW_PARENT = 6;
/**
* Connection this connection connects to. Null if not connected.
* @type {Blockly.Connection}
*/
Blockly.Connection.prototype.targetConnection = null;
/**
* List of compatible value types. Null if all types are compatible.
* @type {Array}
* @private
*/
Blockly.Connection.prototype.check_ = null;
/**
* DOM representation of a shadow block, or null if none.
* @type {Element}
* @private
*/
Blockly.Connection.prototype.shadowDom_ = null;
/**
* Horizontal location of this connection.
* @type {number}
* @private
*/
Blockly.Connection.prototype.x_ = 0;
/**
* Vertical location of this connection.
* @type {number}
* @private
*/
Blockly.Connection.prototype.y_ = 0;
/**
* Has this connection been added to the connection database?
* @type {boolean}
* @private
*/
Blockly.Connection.prototype.inDB_ = false;
/**
* Connection database for connections of this type on the current workspace.
* @type {Blockly.ConnectionDB}
* @private
*/
Blockly.Connection.prototype.db_ = null;
/**
* Connection database for connections compatible with this type on the
* current workspace.
* @type {Blockly.ConnectionDB}
* @private
*/
Blockly.Connection.prototype.dbOpposite_ = null;
/**
* Whether this connections is hidden (not tracked in a database) or not.
* @type {boolean}
* @private
*/
Blockly.Connection.prototype.hidden_ = null;
/**
* Connect two connections together. This is the connection on the superior
* block.
* @param {!Blockly.Connection} childConnection Connection on inferior block.
* @private
*/
Blockly.Connection.prototype.connect_ = function(childConnection) {
var parentConnection = this;
var parentBlock = parentConnection.getSourceBlock();
var childBlock = childConnection.getSourceBlock();
// Disconnect any existing parent on the child connection.
if (childConnection.isConnected()) {
childConnection.disconnect();
}
if (parentConnection.isConnected()) {
// Other connection is already connected to something.
// Disconnect it and reattach it or bump it as needed.
var orphanBlock = parentConnection.targetBlock();
var shadowDom = parentConnection.getShadowDom();
// Temporarily set the shadow DOM to null so it does not respawn.
parentConnection.setShadowDom(null);
// Displaced shadow blocks dissolve rather than reattaching or bumping.
if (orphanBlock.isShadow()) {
// Save the shadow block so that field values are preserved.
shadowDom = Blockly.Xml.blockToDom(orphanBlock);
orphanBlock.dispose();
orphanBlock = null;
} else if (parentConnection.type == Blockly.INPUT_VALUE) {
// Value connections.
// If female block is already connected, disconnect and bump the male.
if (!orphanBlock.outputConnection) {
throw 'Orphan block does not have an output connection.';
}
// Attempt to reattach the orphan at the end of the newly inserted
// block. Since this block may be a row, walk down to the end
// or to the first (and only) shadow block.
var connection = Blockly.Connection.lastConnectionInRow_(
childBlock, orphanBlock);
if (connection) {
orphanBlock.outputConnection.connect(connection);
orphanBlock = null;
}
} else if (parentConnection.type == Blockly.NEXT_STATEMENT) {
// Statement connections.
// Statement blocks may be inserted into the middle of a stack.
// Split the stack.
if (!orphanBlock.previousConnection) {
throw 'Orphan block does not have a previous connection.';
}
// Attempt to reattach the orphan at the bottom of the newly inserted
// block. Since this block may be a stack, walk down to the end.
var newBlock = childBlock;
while (newBlock.nextConnection) {
var nextBlock = newBlock.getNextBlock();
if (nextBlock && !nextBlock.isShadow()) {
newBlock = nextBlock;
} else {
if (orphanBlock.previousConnection.checkType_(
newBlock.nextConnection)) {
newBlock.nextConnection.connect(orphanBlock.previousConnection);
orphanBlock = null;
}
break;
}
}
}
if (orphanBlock) {
// Unable to reattach orphan.
parentConnection.disconnect();
if (Blockly.Events.recordUndo) {
// Bump it off to the side after a moment.
var group = Blockly.Events.getGroup();
setTimeout(function() {
// Verify orphan hasn't been deleted or reconnected (user on meth).
if (orphanBlock.workspace && !orphanBlock.getParent()) {
Blockly.Events.setGroup(group);
if (orphanBlock.outputConnection) {
orphanBlock.outputConnection.bumpAwayFrom_(parentConnection);
} else if (orphanBlock.previousConnection) {
orphanBlock.previousConnection.bumpAwayFrom_(parentConnection);
}
Blockly.Events.setGroup(false);
}
}, Blockly.BUMP_DELAY);
}
}
// Restore the shadow DOM.
parentConnection.setShadowDom(shadowDom);
}
var event;
if (Blockly.Events.isEnabled()) {
event = new Blockly.Events.Move(childBlock);
}
// Establish the connections.
Blockly.Connection.connectReciprocally_(parentConnection, childConnection);
// Demote the inferior block so that one is a child of the superior one.
childBlock.setParent(parentBlock);
if (event) {
event.recordNew();
Blockly.Events.fire(event);
}
};
/**
* Sever all links to this connection (not including from the source object).
*/
Blockly.Connection.prototype.dispose = function() {
if (this.isConnected()) {
throw 'Disconnect connection before disposing of it.';
}
if (this.inDB_) {
this.db_.removeConnection_(this);
}
if (Blockly.highlightedConnection_ == this) {
Blockly.highlightedConnection_ = null;
}
if (Blockly.localConnection_ == this) {
Blockly.localConnection_ = null;
}
this.db_ = null;
this.dbOpposite_ = null;
};
/**
* Get the source block for this connection.
* @return {Blockly.Block} The source block, or null if there is none.
*/
Blockly.Connection.prototype.getSourceBlock = function() {
return this.sourceBlock_;
};
/**
* Does the connection belong to a superior block (higher in the source stack)?
* @return {boolean} True if connection faces down or right.
*/
Blockly.Connection.prototype.isSuperior = function() {
return this.type == Blockly.INPUT_VALUE ||
this.type == Blockly.NEXT_STATEMENT;
};
/**
* Is the connection connected?
* @return {boolean} True if connection is connected to another connection.
*/
Blockly.Connection.prototype.isConnected = function() {
return !!this.targetConnection;
};
/**
* Checks whether the current connection can connect with the target
* connection.
* @param {Blockly.Connection} target Connection to check compatibility with.
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
* an error code otherwise.
* @private
*/
Blockly.Connection.prototype.canConnectWithReason_ = function(target) {
if (!target) {
return Blockly.Connection.REASON_TARGET_NULL;
}
if (this.isSuperior()) {
var blockA = this.sourceBlock_;
var blockB = target.getSourceBlock();
} else {
var blockB = this.sourceBlock_;
var blockA = target.getSourceBlock();
}
if (blockA && blockA == blockB) {
return Blockly.Connection.REASON_SELF_CONNECTION;
} else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) {
return Blockly.Connection.REASON_WRONG_TYPE;
} else if (blockA && blockB && blockA.workspace !== blockB.workspace) {
return Blockly.Connection.REASON_DIFFERENT_WORKSPACES;
} else if (!this.checkType_(target)) {
return Blockly.Connection.REASON_CHECKS_FAILED;
} else if (blockA.isShadow() && !blockB.isShadow()) {
return Blockly.Connection.REASON_SHADOW_PARENT;
}
return Blockly.Connection.CAN_CONNECT;
};
/**
* Checks whether the current connection and target connection are compatible
* and throws an exception if they are not.
* @param {Blockly.Connection} target The connection to check compatibility
* with.
* @private
*/
Blockly.Connection.prototype.checkConnection_ = function(target) {
switch (this.canConnectWithReason_(target)) {
case Blockly.Connection.CAN_CONNECT:
break;
case Blockly.Connection.REASON_SELF_CONNECTION:
throw 'Attempted to connect a block to itself.';
case Blockly.Connection.REASON_DIFFERENT_WORKSPACES:
// Usually this means one block has been deleted.
throw 'Blocks not on same workspace.';
case Blockly.Connection.REASON_WRONG_TYPE:
throw 'Attempt to connect incompatible types.';
case Blockly.Connection.REASON_TARGET_NULL:
throw 'Target connection is null.';
case Blockly.Connection.REASON_CHECKS_FAILED:
throw 'Connection checks failed.';
case Blockly.Connection.REASON_SHADOW_PARENT:
throw 'Connecting non-shadow to shadow block.';
default:
throw 'Unknown connection failure: this should never happen!';
}
};
/**
* Check if the two connections can be dragged to connect to each other.
* @param {!Blockly.Connection} candidate A nearby connection to check.
* @return {boolean} True if the connection is allowed, false otherwise.
*/
Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
// Type checking.
var canConnect = this.canConnectWithReason_(candidate);
if (canConnect != Blockly.Connection.CAN_CONNECT) {
return false;
}
// Don't offer to connect an already connected left (male) value plug to
// an available right (female) value plug. Don't offer to connect the
// bottom of a statement block to one that's already connected.
if (candidate.type == Blockly.OUTPUT_VALUE ||
candidate.type == Blockly.PREVIOUS_STATEMENT) {
if (candidate.isConnected() || this.isConnected()) {
return false;
}
}
// Offering to connect the left (male) of a value block to an already
// connected value pair is ok, we'll splice it in.
// However, don't offer to splice into an immovable block.
if (candidate.type == Blockly.INPUT_VALUE && candidate.isConnected() &&
!candidate.targetBlock().isMovable() &&
!candidate.targetBlock().isShadow()) {
return false;
}
// Don't let a block with no next connection bump other blocks out of the
// stack. But covering up a shadow block or stack of shadow blocks is fine.
// Similarly, replacing a terminal statement with another terminal statement
// is allowed.
if (this.type == Blockly.PREVIOUS_STATEMENT &&
candidate.isConnected() &&
!this.sourceBlock_.nextConnection &&
!candidate.targetBlock().isShadow() &&
candidate.targetBlock().nextConnection) {
return false;
}
// Don't let blocks try to connect to themselves or ones they nest.
if (Blockly.draggingConnections_.indexOf(candidate) != -1) {
return false;
}
return true;
};
/**
* Connect this connection to another connection.
* @param {!Blockly.Connection} otherConnection Connection to connect to.
*/
Blockly.Connection.prototype.connect = function(otherConnection) {
if (this.targetConnection == otherConnection) {
// Already connected together. NOP.
return;
}
this.checkConnection_(otherConnection);
// Determine which block is superior (higher in the source stack).
if (this.isSuperior()) {
// Superior block.
this.connect_(otherConnection);
} else {
// Inferior block.
otherConnection.connect_(this);
}
};
/**
* Update two connections to target each other.
* @param {Blockly.Connection} first The first connection to update.
* @param {Blockly.Connection} second The second conneciton to update.
* @private
*/
Blockly.Connection.connectReciprocally_ = function(first, second) {
goog.asserts.assert(first && second, 'Cannot connect null connections.');
first.targetConnection = second;
second.targetConnection = first;
};
/**
* Does the given block have one and only one connection point that will accept
* an orphaned block?
* @param {!Blockly.Block} block The superior block.
* @param {!Blockly.Block} orphanBlock The inferior block.
* @return {Blockly.Connection} The suitable connection point on 'block',
* or null.
* @private
*/
Blockly.Connection.singleConnection_ = function(block, orphanBlock) {
var connection = false;
for (var i = 0; i < block.inputList.length; i++) {
var thisConnection = block.inputList[i].connection;
if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE &&
orphanBlock.outputConnection.checkType_(thisConnection)) {
if (connection) {
return null; // More than one connection.
}
connection = thisConnection;
}
}
return connection;
};
/**
* Walks down a row a blocks, at each stage checking if there are any
* connections that will accept the orphaned block. If at any point there
* are zero or multiple eligible connections, returns null. Otherwise
* returns the only input on the last block in the chain.
* Terminates early for shadow blocks.
* @param {!Blockly.Block} startBlock The block on which to start the search.
* @param {!Blockly.Block} orphanBlock The block that is looking for a home.
* @return {Blockly.Connection} The suitable connection point on the chain
* of blocks, or null.
* @private
*/
Blockly.Connection.lastConnectionInRow_ = function(startBlock, orphanBlock) {
var newBlock = startBlock;
var connection;
while (connection = Blockly.Connection.singleConnection_(
/** @type {!Blockly.Block} */ (newBlock), orphanBlock)) {
// '=' is intentional in line above.
newBlock = connection.targetBlock();
if (!newBlock || newBlock.isShadow()) {
return connection;
}
}
return null;
};
/**
* Disconnect this connection.
*/
Blockly.Connection.prototype.disconnect = function() {
var otherConnection = this.targetConnection;
goog.asserts.assert(otherConnection, 'Source connection not connected.');
goog.asserts.assert(otherConnection.targetConnection == this,
'Target connection not connected to source connection.');
var parentBlock, childBlock, parentConnection;
if (this.isSuperior()) {
// Superior block.
parentBlock = this.sourceBlock_;
childBlock = otherConnection.getSourceBlock();
parentConnection = this;
} else {
// Inferior block.
parentBlock = otherConnection.getSourceBlock();
childBlock = this.sourceBlock_;
parentConnection = otherConnection;
}
this.disconnectInternal_(parentBlock, childBlock);
parentConnection.respawnShadow_();
};
/**
* Disconnect two blocks that are connected by this connection.
* @param {!Blockly.Block} parentBlock The superior block.
* @param {!Blockly.Block} childBlock The inferior block.
* @private
*/
Blockly.Connection.prototype.disconnectInternal_ = function(parentBlock,
childBlock) {
var event;
if (Blockly.Events.isEnabled()) {
event = new Blockly.Events.Move(childBlock);
}
var otherConnection = this.targetConnection;
otherConnection.targetConnection = null;
this.targetConnection = null;
childBlock.setParent(null);
if (event) {
event.recordNew();
Blockly.Events.fire(event);
}
};
/**
* Respawn the shadow block if there was one connected to the this connection.
* @private
*/
Blockly.Connection.prototype.respawnShadow_ = function() {
var parentBlock = this.getSourceBlock();
var shadow = this.getShadowDom();
if (parentBlock.workspace && shadow && Blockly.Events.recordUndo) {
var blockShadow =
Blockly.Xml.domToBlock(shadow, parentBlock.workspace);
if (blockShadow.outputConnection) {
this.connect(blockShadow.outputConnection);
} else if (blockShadow.previousConnection) {
this.connect(blockShadow.previousConnection);
} else {
throw 'Child block does not have output or previous statement.';
}
}
};
/**
* Returns the block that this connection connects to.
* @return {Blockly.Block} The connected block or null if none is connected.
*/
Blockly.Connection.prototype.targetBlock = function() {
if (this.isConnected()) {
return this.targetConnection.getSourceBlock();
}
return null;
};
/**
* Is this connection compatible with another connection with respect to the
* value type system. E.g. square_root("Hello") is not compatible.
* @param {!Blockly.Connection} otherConnection Connection to compare against.
* @return {boolean} True if the connections share a type.
* @private
*/
Blockly.Connection.prototype.checkType_ = function(otherConnection) {
if (!this.check_ || !otherConnection.check_) {
// One or both sides are promiscuous enough that anything will fit.
return true;
}
// Find any intersection in the check lists.
for (var i = 0; i < this.check_.length; i++) {
if (otherConnection.check_.indexOf(this.check_[i]) != -1) {
return true;
}
}
// No intersection.
return false;
};
/**
* Change a connection's compatibility.
* @param {*} check Compatible value type or list of value types.
* Null if all types are compatible.
* @return {!Blockly.Connection} The connection being modified
* (to allow chaining).
*/
Blockly.Connection.prototype.setCheck = function(check) {
if (check) {
// Ensure that check is in an array.
if (!goog.isArray(check)) {
check = [check];
}
this.check_ = check;
// The new value type may not be compatible with the existing connection.
if (this.isConnected() && !this.checkType_(this.targetConnection)) {
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
child.unplug();
// Bump away.
this.sourceBlock_.bumpNeighbours_();
}
} else {
this.check_ = null;
}
return this;
};
/**
* Change a connection's shadow block.
* @param {Element} shadow DOM representation of a block or null.
*/
Blockly.Connection.prototype.setShadowDom = function(shadow) {
this.shadowDom_ = shadow;
};
/**
* Return a connection's shadow block.
* @return {Element} shadow DOM representation of a block or null.
*/
Blockly.Connection.prototype.getShadowDom = function() {
return this.shadowDom_;
};

71
core/connection.js.rej Normal file
View File

@@ -0,0 +1,71 @@
***************
*** 39,45 ****
Blockly.Connection = function(source, type) {
this.sourceBlock_ = source;
this.targetConnection = null;
- this.type = type;
this.x_ = 0;
this.y_ = 0;
this.inDB_ = false;
--- 39,50 ----
Blockly.Connection = function(source, type) {
this.sourceBlock_ = source;
this.targetConnection = null;
+ if (type == Blockly.INDENTED_VALUE) {
+ this.type = Blockly.INPUT_VALUE;
+ this.subtype = Blockly.INDENTED_VALUE;
+ } else {
+ this.type = type;
+ }
this.x_ = 0;
this.y_ = 0;
this.inDB_ = false;
***************
*** 176,181 ****
// Demote the inferior block so that one is a child of the superior one.
childBlock.setParent(parentBlock);
if (parentBlock.rendered) {
parentBlock.svg_.updateDisabled();
--- 181,188 ----
// Demote the inferior block so that one is a child of the superior one.
childBlock.setParent(parentBlock);
+ // Rendering the child node will trigger a rendering of its parent.
+ // Rendering the parent node will move its connected children into position.
if (parentBlock.rendered) {
parentBlock.svg_.updateDisabled();
***************
*** 510,518 ****
// One or both sides are promiscuous enough that anything will fit.
return true;
}
- // Find any intersection in the check lists.
for (var x = 0; x < this.check_.length; x++) {
- if (otherConnection.check_.indexOf(this.check_[x]) != -1) {
return true;
}
}
--- 517,536 ----
// One or both sides are promiscuous enough that anything will fit.
return true;
}
+ // Find any intersection in the check lists,
+ // or if the check is a function, evaluate the function.
for (var x = 0; x < this.check_.length; x++) {
+ if ((otherConnection.check_.indexOf(this.check_[x]) != -1) ||
+ (typeof this.check_[x] == "function" && this.check_[x](this,otherConnection))) {
+ return true;
+ }
+ }
+
+ // If the check is a function on the other connection,
+ // evaluate the function to see if it evaluates to true.
+ for (var x = 0; x < otherConnection.check_.length; x++) {
+ if (typeof otherConnection.check_[x] == "function" &&
+ otherConnection.check_[x](otherConnection,this)) {
return true;
}
}

View File

@@ -24,6 +24,14 @@
*/
'use strict';
/**
* [lyn, 10/10/13]
* + Added CSS tags blocklyFieldParameter and blocklyFieldParameterFlydown
* to control parameter flydowns.
* + Added CSS tags blocklyFieldProcedure and blocklyFieldProcedureFlydown
* to control procedure flydowns.
*/
goog.provide('Blockly.Css');
@@ -148,6 +156,74 @@ Blockly.Css.CONTENT = [
'height: 100%;',
'position: relative;',
'}',
'/*',
' * [lyn, 10/08/13] Control parameter fields with flydown getter/setter blocks.',
' * Brightening factors for variable color rgb(208,95,45):',
' * 10%: rgb(212, 111, 66)',
' * 20%: rgb(217, 127, 87)',
' * 30%: rgb(222, 143, 108)',
' * 40%: rgb(226, 159, 129)',
' * 50%: rgb(231, 175, 150)',
' * 60%: rgb(236, 191, 171)',
' * 70%: rgb(240, 207, 192)',
' * 80%: rgb(245, 223, 213)',
' * 90%: rgb(250, 239, 234)',
' */',
'.blocklyFieldParameter>rect {',
' /* fill: rgb(231,175,150);*/ /* This looks too much like getter/setter var */',
' fill: rgb(222, 143, 108);',
' fill-opacity: 1.0;',
' stroke-width: 2;',
' stroke: rgb(231, 175, 150);',
'}',
'.blocklyFieldParameter>text {',
' /* fill: #000; */ /* Use white rather than black on dark orange */',
' stroke-width: 1;',
' fill: #000;',
'}',
'.blocklyFieldParameter:hover>rect {',
' stroke-width: 2;',
' stroke: rgb(231,175,150);',
' fill: rgb(231,175,150);',
' fill-opacity: 1.0;',
'}',
'/*',
' * [lyn, 10/08/13] Control flydown with the getter/setter blocks.',
' */',
'.blocklyFieldParameterFlydown {',
' fill: rgb(231,175,150);',
' fill-opacity: 0.8;',
'}',
'/*',
' * [lyn, 10/08/13] Control parameter fields with flydown procedure caller block.',
' */',
'.blocklyFieldProcedure>rect {',
' /* rgb(231,175,150) is procedure color rgb(124,83,133) brightened by 70% */',
' fill: rgb(215,203,218);',
' fill-opacity: 1.0;',
' stroke-width: 0;',
' stroke: #000;',
'}',
'.blocklyFieldProcedure>text {',
' fill: #000;',
'}',
'.blocklyFieldProcedure:hover>rect {',
' stroke-width: 2;',
' stroke: #fff;',
' fill: rgb(215,203,218);',
' fill-opacity: 1.0;',
'}',
'/*',
' * [lyn, 10/08/13] Control flydown with the procedure caller block.',
' */',
'.blocklyFieldProcedureFlydown {',
' fill: rgb(215,203,218);',
' fill-opacity: 0.8;',
'}',
'/*',
' * Don\'t allow users to select text. It gets annoying when trying to',
' * drag a block and selected text moves instead.',
' */',
'.blocklyNonSelectable {',
'user-select: none;',
@@ -280,7 +356,14 @@ Blockly.Css.CONTENT = [
'-webkit-user-select: none;',
'cursor: inherit;',
'}',
'/*',
' * Selecting text for Errors and Warnings is allowed though.',
' */',
'.blocklySvg text.blocklyErrorWarningText {',
' -moz-user-select: text;',
' -webkit-user-select: text;',
' user-select: text;',
'}',
'.blocklyHidden {',
'display: none;',
'}',

786
core/css.js.orig Normal file
View File

@@ -0,0 +1,786 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2013 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Inject Blockly's CSS synchronously.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Css');
/**
* List of cursors.
* @enum {string}
*/
Blockly.Css.Cursor = {
OPEN: 'handopen',
CLOSED: 'handclosed',
DELETE: 'handdelete'
};
/**
* Current cursor (cached value).
* @type {string}
* @private
*/
Blockly.Css.currentCursor_ = '';
/**
* Large stylesheet added by Blockly.Css.inject.
* @type {Element}
* @private
*/
Blockly.Css.styleSheet_ = null;
/**
* Path to media directory, with any trailing slash removed.
* @type {string}
* @private
*/
Blockly.Css.mediaPath_ = '';
/**
* Inject the CSS into the DOM. This is preferable over using a regular CSS
* file since:
* a) It loads synchronously and doesn't force a redraw later.
* b) It speeds up loading by not blocking on a separate HTTP transfer.
* c) The CSS content may be made dynamic depending on init options.
* @param {boolean} hasCss If false, don't inject CSS
* (providing CSS becomes the document's responsibility).
* @param {string} pathToMedia Path from page to the Blockly media directory.
*/
Blockly.Css.inject = function(hasCss, pathToMedia) {
// Only inject the CSS once.
if (Blockly.Css.styleSheet_) {
return;
}
// Placeholder for cursor rule. Must be first rule (index 0).
var text = '.blocklyDraggable {}\n';
if (hasCss) {
text += Blockly.Css.CONTENT.join('\n');
if (Blockly.FieldDate) {
text += Blockly.FieldDate.CSS.join('\n');
}
}
// Strip off any trailing slash (either Unix or Windows).
Blockly.Css.mediaPath_ = pathToMedia.replace(/[\\\/]$/, '');
text = text.replace(/<<<PATH>>>/g, Blockly.Css.mediaPath_);
// Inject CSS tag.
var cssNode = document.createElement('style');
document.head.appendChild(cssNode);
var cssTextNode = document.createTextNode(text);
cssNode.appendChild(cssTextNode);
Blockly.Css.styleSheet_ = cssNode.sheet;
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
};
/**
* Set the cursor to be displayed when over something draggable.
* @param {Blockly.Css.Cursor} cursor Enum.
*/
Blockly.Css.setCursor = function(cursor) {
if (Blockly.Css.currentCursor_ == cursor) {
return;
}
Blockly.Css.currentCursor_ = cursor;
var url = 'url(' + Blockly.Css.mediaPath_ + '/' + cursor + '.cur), auto';
// There are potentially hundreds of draggable objects. Changing their style
// properties individually is too slow, so change the CSS rule instead.
var rule = '.blocklyDraggable {\n cursor: ' + url + ';\n}\n';
Blockly.Css.styleSheet_.deleteRule(0);
Blockly.Css.styleSheet_.insertRule(rule, 0);
// There is probably only one toolbox, so just change its style property.
var toolboxen = document.getElementsByClassName('blocklyToolboxDiv');
for (var i = 0, toolbox; toolbox = toolboxen[i]; i++) {
if (cursor == Blockly.Css.Cursor.DELETE) {
toolbox.style.cursor = url;
} else {
toolbox.style.cursor = '';
}
}
// Set cursor on the whole document, so that rapid movements
// don't result in cursor changing to an arrow momentarily.
var html = document.body.parentNode;
if (cursor == Blockly.Css.Cursor.OPEN) {
html.style.cursor = '';
} else {
html.style.cursor = url;
}
};
/**
* Array making up the CSS content for Blockly.
*/
Blockly.Css.CONTENT = [
'.blocklySvg {',
'background-color: #fff;',
'outline: none;',
'overflow: hidden;', /* IE overflows by default. */
'display: block;',
'}',
'.blocklyWidgetDiv {',
'display: none;',
'position: absolute;',
'z-index: 99999;', /* big value for bootstrap3 compatibility */
'}',
'.injectionDiv {',
'height: 100%;',
'position: relative;',
'}',
'.blocklyNonSelectable {',
'user-select: none;',
'-moz-user-select: none;',
'-webkit-user-select: none;',
'-ms-user-select: none;',
'}',
'.blocklyTooltipDiv {',
'background-color: #ffffc7;',
'border: 1px solid #ddc;',
'box-shadow: 4px 4px 20px 1px rgba(0,0,0,.15);',
'color: #000;',
'display: none;',
'font-family: sans-serif;',
'font-size: 9pt;',
'opacity: 0.9;',
'padding: 2px;',
'position: absolute;',
'z-index: 100000;', /* big value for bootstrap3 compatibility */
'}',
'.blocklyResizeSE {',
'cursor: se-resize;',
'fill: #aaa;',
'}',
'.blocklyResizeSW {',
'cursor: sw-resize;',
'fill: #aaa;',
'}',
'.blocklyResizeLine {',
'stroke: #888;',
'stroke-width: 1;',
'}',
'.blocklyHighlightedConnectionPath {',
'fill: none;',
'stroke: #fc3;',
'stroke-width: 4px;',
'}',
'.blocklyPathLight {',
'fill: none;',
'stroke-linecap: round;',
'stroke-width: 1;',
'}',
'.blocklySelected>.blocklyPath {',
'stroke: #fc3;',
'stroke-width: 3px;',
'}',
'.blocklySelected>.blocklyPathLight {',
'display: none;',
'}',
'.blocklyDragging>.blocklyPath,',
'.blocklyDragging>.blocklyPathLight {',
'fill-opacity: .8;',
'stroke-opacity: .8;',
'}',
'.blocklyDragging>.blocklyPathDark {',
'display: none;',
'}',
'.blocklyDisabled>.blocklyPath {',
'fill-opacity: .5;',
'stroke-opacity: .5;',
'}',
'.blocklyDisabled>.blocklyPathLight,',
'.blocklyDisabled>.blocklyPathDark {',
'display: none;',
'}',
'.blocklyText {',
'cursor: default;',
'fill: #fff;',
'font-family: sans-serif;',
'font-size: 11pt;',
'}',
'.blocklyNonEditableText>text {',
'pointer-events: none;',
'}',
'.blocklyNonEditableText>rect,',
'.blocklyEditableText>rect {',
'fill: #fff;',
'fill-opacity: .6;',
'}',
'.blocklyNonEditableText>text,',
'.blocklyEditableText>text {',
'fill: #000;',
'}',
'.blocklyEditableText:hover>rect {',
'stroke: #fff;',
'stroke-width: 2;',
'}',
'.blocklyBubbleText {',
'fill: #000;',
'}',
'.blocklyFlyoutButton {',
'fill: #888;',
'cursor: default;',
'}',
'.blocklyFlyoutButtonShadow {',
'fill: #444;',
'}',
'.blocklyFlyoutButton:hover {',
'fill: #aaa;',
'}',
/*
Don't allow users to select text. It gets annoying when trying to
drag a block and selected text moves instead.
*/
'.blocklySvg text {',
'user-select: none;',
'-moz-user-select: none;',
'-webkit-user-select: none;',
'cursor: inherit;',
'}',
'.blocklyHidden {',
'display: none;',
'}',
'.blocklyFieldDropdown:not(.blocklyHidden) {',
'display: block;',
'}',
'.blocklyIconGroup {',
'cursor: default;',
'}',
'.blocklyIconGroup:not(:hover),',
'.blocklyIconGroupReadonly {',
'opacity: .6;',
'}',
'.blocklyIconShape {',
'fill: #00f;',
'stroke: #fff;',
'stroke-width: 1px;',
'}',
'.blocklyIconSymbol {',
'fill: #fff;',
'}',
'.blocklyMinimalBody {',
'margin: 0;',
'padding: 0;',
'}',
'.blocklyCommentTextarea {',
'background-color: #ffc;',
'border: 0;',
'margin: 0;',
'padding: 2px;',
'resize: none;',
'}',
'.blocklyHtmlInput {',
'border: none;',
'border-radius: 4px;',
'font-family: sans-serif;',
'height: 100%;',
'margin: 0;',
'outline: none;',
'padding: 0 1px;',
'width: 100%',
'}',
'.blocklyMainBackground {',
'stroke-width: 1;',
'stroke: #c6c6c6;', /* Equates to #ddd due to border being off-pixel. */
'}',
'.blocklyMutatorBackground {',
'fill: #fff;',
'stroke: #ddd;',
'stroke-width: 1;',
'}',
'.blocklyFlyoutBackground {',
'fill: #ddd;',
'fill-opacity: .8;',
'}',
'.blocklyScrollbarBackground {',
'opacity: 0;',
'}',
'.blocklyScrollbarHandle {',
'fill: #ccc;',
'}',
'.blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,',
'.blocklyScrollbarHandle:hover {',
'fill: #bbb;',
'}',
'.blocklyZoom>image {',
'opacity: .4;',
'}',
'.blocklyZoom>image:hover {',
'opacity: .6;',
'}',
'.blocklyZoom>image:active {',
'opacity: .8;',
'}',
/* Darken flyout scrollbars due to being on a grey background. */
/* By contrast, workspace scrollbars are on a white background. */
'.blocklyFlyout .blocklyScrollbarHandle {',
'fill: #bbb;',
'}',
'.blocklyFlyout .blocklyScrollbarBackground:hover+.blocklyScrollbarHandle,',
'.blocklyFlyout .blocklyScrollbarHandle:hover {',
'fill: #aaa;',
'}',
'.blocklyInvalidInput {',
'background: #faa;',
'}',
'.blocklyAngleCircle {',
'stroke: #444;',
'stroke-width: 1;',
'fill: #ddd;',
'fill-opacity: .8;',
'}',
'.blocklyAngleMarks {',
'stroke: #444;',
'stroke-width: 1;',
'}',
'.blocklyAngleGauge {',
'fill: #f88;',
'fill-opacity: .8;',
'}',
'.blocklyAngleLine {',
'stroke: #f00;',
'stroke-width: 2;',
'stroke-linecap: round;',
'}',
'.blocklyContextMenu {',
'border-radius: 4px;',
'}',
'.blocklyDropdownMenu {',
'padding: 0 !important;',
'}',
/* Override the default Closure URL. */
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {',
'background: url(<<<PATH>>>/sprites.png) no-repeat -48px -16px !important;',
'}',
/* Category tree in Toolbox. */
'.blocklyToolboxDiv {',
'background-color: #ddd;',
'overflow-x: visible;',
'overflow-y: auto;',
'position: absolute;',
'}',
'.blocklyTreeRoot {',
'padding: 4px 0;',
'}',
'.blocklyTreeRoot:focus {',
'outline: none;',
'}',
'.blocklyTreeRow {',
'height: 22px;',
'line-height: 22px;',
'margin-bottom: 3px;',
'padding-right: 8px;',
'white-space: nowrap;',
'}',
'.blocklyHorizontalTree {',
'float: left;',
'margin: 1px 5px 8px 0;',
'}',
'.blocklyHorizontalTreeRtl {',
'float: right;',
'margin: 1px 0 8px 5px;',
'}',
'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',
'margin-left: 8px;',
'}',
'.blocklyTreeRow:not(.blocklyTreeSelected):hover {',
'background-color: #e4e4e4;',
'}',
'.blocklyTreeSeparator {',
'border-bottom: solid #e5e5e5 1px;',
'height: 0;',
'margin: 5px 0;',
'}',
'.blocklyTreeSeparatorHorizontal {',
'border-right: solid #e5e5e5 1px;',
'width: 0;',
'padding: 5px 0;',
'margin: 0 5px;',
'}',
'.blocklyTreeIcon {',
'background-image: url(<<<PATH>>>/sprites.png);',
'height: 16px;',
'vertical-align: middle;',
'width: 16px;',
'}',
'.blocklyTreeIconClosedLtr {',
'background-position: -32px -1px;',
'}',
'.blocklyTreeIconClosedRtl {',
'background-position: 0px -1px;',
'}',
'.blocklyTreeIconOpen {',
'background-position: -16px -1px;',
'}',
'.blocklyTreeSelected>.blocklyTreeIconClosedLtr {',
'background-position: -32px -17px;',
'}',
'.blocklyTreeSelected>.blocklyTreeIconClosedRtl {',
'background-position: 0px -17px;',
'}',
'.blocklyTreeSelected>.blocklyTreeIconOpen {',
'background-position: -16px -17px;',
'}',
'.blocklyTreeIconNone,',
'.blocklyTreeSelected>.blocklyTreeIconNone {',
'background-position: -48px -1px;',
'}',
'.blocklyTreeLabel {',
'cursor: default;',
'font-family: sans-serif;',
'font-size: 16px;',
'padding: 0 3px;',
'vertical-align: middle;',
'}',
'.blocklyTreeSelected .blocklyTreeLabel {',
'color: #fff;',
'}',
/* Copied from: goog/css/colorpicker-simplegrid.css */
/*
* Copyright 2007 The Closure Library Authors. All Rights Reserved.
*
* Use of this source code is governed by the Apache License, Version 2.0.
* See the COPYING file for details.
*/
/* Author: pupius@google.com (Daniel Pupius) */
/*
Styles to make the colorpicker look like the old gmail color picker
NOTE: without CSS scoping this will override styles defined in palette.css
*/
'.blocklyWidgetDiv .goog-palette {',
'outline: none;',
'cursor: default;',
'}',
'.blocklyWidgetDiv .goog-palette-table {',
'border: 1px solid #666;',
'border-collapse: collapse;',
'}',
'.blocklyWidgetDiv .goog-palette-cell {',
'height: 13px;',
'width: 15px;',
'margin: 0;',
'border: 0;',
'text-align: center;',
'vertical-align: middle;',
'border-right: 1px solid #666;',
'font-size: 1px;',
'}',
'.blocklyWidgetDiv .goog-palette-colorswatch {',
'position: relative;',
'height: 13px;',
'width: 15px;',
'border: 1px solid #666;',
'}',
'.blocklyWidgetDiv .goog-palette-cell-hover .goog-palette-colorswatch {',
'border: 1px solid #FFF;',
'}',
'.blocklyWidgetDiv .goog-palette-cell-selected .goog-palette-colorswatch {',
'border: 1px solid #000;',
'color: #fff;',
'}',
/* Copied from: goog/css/menu.css */
/*
* Copyright 2009 The Closure Library Authors. All Rights Reserved.
*
* Use of this source code is governed by the Apache License, Version 2.0.
* See the COPYING file for details.
*/
/**
* Standard styling for menus created by goog.ui.MenuRenderer.
*
* @author attila@google.com (Attila Bodis)
*/
'.blocklyWidgetDiv .goog-menu {',
'background: #fff;',
'border-color: #ccc #666 #666 #ccc;',
'border-style: solid;',
'border-width: 1px;',
'cursor: default;',
'font: normal 13px Arial, sans-serif;',
'margin: 0;',
'outline: none;',
'padding: 4px 0;',
'position: absolute;',
'overflow-y: auto;',
'overflow-x: hidden;',
'max-height: 100%;',
'z-index: 20000;', /* Arbitrary, but some apps depend on it... */
'}',
/* Copied from: goog/css/menuitem.css */
/*
* Copyright 2009 The Closure Library Authors. All Rights Reserved.
*
* Use of this source code is governed by the Apache License, Version 2.0.
* See the COPYING file for details.
*/
/**
* Standard styling for menus created by goog.ui.MenuItemRenderer.
*
* @author attila@google.com (Attila Bodis)
*/
/**
* State: resting.
*
* NOTE(mleibman,chrishenry):
* The RTL support in Closure is provided via two mechanisms -- "rtl" CSS
* classes and BiDi flipping done by the CSS compiler. Closure supports RTL
* with or without the use of the CSS compiler. In order for them not
* to conflict with each other, the "rtl" CSS classes need to have the #noflip
* annotation. The non-rtl counterparts should ideally have them as well, but,
* since .goog-menuitem existed without .goog-menuitem-rtl for so long before
* being added, there is a risk of people having templates where they are not
* rendering the .goog-menuitem-rtl class when in RTL and instead rely solely
* on the BiDi flipping by the CSS compiler. That's why we're not adding the
* #noflip to .goog-menuitem.
*/
'.blocklyWidgetDiv .goog-menuitem {',
'color: #000;',
'font: normal 13px Arial, sans-serif;',
'list-style: none;',
'margin: 0;',
/* 28px on the left for icon or checkbox; 7em on the right for shortcut. */
'padding: 4px 7em 4px 28px;',
'white-space: nowrap;',
'}',
/* BiDi override for the resting state. */
/* #noflip */
'.blocklyWidgetDiv .goog-menuitem.goog-menuitem-rtl {',
/* Flip left/right padding for BiDi. */
'padding-left: 7em;',
'padding-right: 28px;',
'}',
/* If a menu doesn't have checkable items or items with icons, remove padding. */
'.blocklyWidgetDiv .goog-menu-nocheckbox .goog-menuitem,',
'.blocklyWidgetDiv .goog-menu-noicon .goog-menuitem {',
'padding-left: 12px;',
'}',
/*
* If a menu doesn't have items with shortcuts, leave just enough room for
* submenu arrows, if they are rendered.
*/
'.blocklyWidgetDiv .goog-menu-noaccel .goog-menuitem {',
'padding-right: 20px;',
'}',
'.blocklyWidgetDiv .goog-menuitem-content {',
'color: #000;',
'font: normal 13px Arial, sans-serif;',
'}',
/* State: disabled. */
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-accel,',
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-content {',
'color: #ccc !important;',
'}',
'.blocklyWidgetDiv .goog-menuitem-disabled .goog-menuitem-icon {',
'opacity: 0.3;',
'-moz-opacity: 0.3;',
'filter: alpha(opacity=30);',
'}',
/* State: hover. */
'.blocklyWidgetDiv .goog-menuitem-highlight,',
'.blocklyWidgetDiv .goog-menuitem-hover {',
'background-color: #d6e9f8;',
/* Use an explicit top and bottom border so that the selection is visible',
* in high contrast mode. */
'border-color: #d6e9f8;',
'border-style: dotted;',
'border-width: 1px 0;',
'padding-bottom: 3px;',
'padding-top: 3px;',
'}',
/* State: selected/checked. */
'.blocklyWidgetDiv .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-menuitem-icon {',
'background-repeat: no-repeat;',
'height: 16px;',
'left: 6px;',
'position: absolute;',
'right: auto;',
'vertical-align: middle;',
'width: 16px;',
'}',
/* BiDi override for the selected/checked state. */
/* #noflip */
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-icon {',
/* Flip left/right positioning. */
'left: auto;',
'right: 6px;',
'}',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-checkbox,',
'.blocklyWidgetDiv .goog-option-selected .goog-menuitem-icon {',
/* Client apps may override the URL at which they serve the sprite. */
'background: url(//ssl.gstatic.com/editor/editortoolbar.png) no-repeat -512px 0;',
'}',
/* Keyboard shortcut ("accelerator") style. */
'.blocklyWidgetDiv .goog-menuitem-accel {',
'color: #999;',
/* Keyboard shortcuts are untranslated; always left-to-right. */
/* #noflip */
'direction: ltr;',
'left: auto;',
'padding: 0 6px;',
'position: absolute;',
'right: 0;',
'text-align: right;',
'}',
/* BiDi override for shortcut style. */
/* #noflip */
'.blocklyWidgetDiv .goog-menuitem-rtl .goog-menuitem-accel {',
/* Flip left/right positioning and text alignment. */
'left: 0;',
'right: auto;',
'text-align: left;',
'}',
/* Mnemonic styles. */
'.blocklyWidgetDiv .goog-menuitem-mnemonic-hint {',
'text-decoration: underline;',
'}',
'.blocklyWidgetDiv .goog-menuitem-mnemonic-separator {',
'color: #999;',
'font-size: 12px;',
'padding-left: 4px;',
'}',
/* Copied from: goog/css/menuseparator.css */
/*
* Copyright 2009 The Closure Library Authors. All Rights Reserved.
*
* Use of this source code is governed by the Apache License, Version 2.0.
* See the COPYING file for details.
*/
/**
* Standard styling for menus created by goog.ui.MenuSeparatorRenderer.
*
* @author attila@google.com (Attila Bodis)
*/
'.blocklyWidgetDiv .goog-menuseparator {',
'border-top: 1px solid #ccc;',
'margin: 4px 0;',
'padding: 0;',
'}',
''
];

56
core/css.js.rej Normal file
View File

@@ -0,0 +1,56 @@
***************
*** 355,367 ****
' outline: none;',
' width: 100%',
'}',
-
'.blocklyMutatorBackground {',
' fill: #fff;',
' stroke-width: 1;',
' stroke: #ddd;',
'}',
-
'.blocklyFlyoutBackground {',
' fill: #ddd;',
' fill-opacity: .8;',
--- 438,477 ----
' outline: none;',
' width: 100%',
'}',
+ '.blocklyContextMenuBackground,',
'.blocklyMutatorBackground {',
' fill: #fff;',
' stroke-width: 1;',
' stroke: #ddd;',
'}',
+ '.blocklyContextMenuOptions>.blocklyMenuDiv,',
+ '.blocklyContextMenuOptions>.blocklyMenuDivDisabled,',
+ '.blocklyDropdownMenuOptions>.blocklyMenuDiv {',
+ ' fill: #fff;',
+ '}',
+ '.blocklyContextMenuOptions>.blocklyMenuDiv:hover>rect,',
+ '.blocklyDropdownMenuOptions>.blocklyMenuDiv:hover>rect {',
+ ' fill: #57e;',
+ '}',
+ '.blocklyMenuSelected>rect {',
+ ' fill: #57e;',
+ '}',
+ '.blocklyMenuText {',
+ ' cursor: default !important;',
+ ' font-family: sans-serif;',
+ ' font-size: 15px; /* All context menu sizes are based on pixels. */',
+ ' fill: #000;',
+ '}',
+ '.blocklyContextMenuOptions>.blocklyMenuDiv:hover>.blocklyMenuText,',
+ '.blocklyDropdownMenuOptions>.blocklyMenuDiv:hover>.blocklyMenuText {',
+ ' fill: #fff;',
+ '}',
+ '.blocklyMenuSelected>.blocklyMenuText {',
+ ' fill: #fff;',
+ '}',
+ '.blocklyMenuDivDisabled>.blocklyMenuText {',
+ ' fill: #ccc;',
+ '}',
'.blocklyFlyoutBackground {',
' fill: #ddd;',
' fill-opacity: .8;',

495
core/field.js.orig Normal file
View File

@@ -0,0 +1,495 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Field. Used for editable titles, variables, etc.
* This is an abstract class that defines the UI on the block. Actual
* instances would be Blockly.FieldTextInput, Blockly.FieldDropdown, etc.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Field');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.math.Size');
goog.require('goog.style');
goog.require('goog.userAgent');
/**
* Abstract class for an editable field.
* @param {string} text The initial content of the field.
* @param {Function=} opt_validator An optional function that is called
* to validate any constraints on what the user entered. Takes the new
* text as an argument and returns either the accepted text, a replacement
* text, or null to abort the change.
* @constructor
*/
Blockly.Field = function(text, opt_validator) {
this.size_ = new goog.math.Size(0, 25);
this.setValue(text);
this.setValidator(opt_validator);
};
/**
* Temporary cache of text widths.
* @type {Object}
* @private
*/
Blockly.Field.cacheWidths_ = null;
/**
* Number of current references to cache.
* @type {number}
* @private
*/
Blockly.Field.cacheReference_ = 0;
/**
* Name of field. Unique within each block.
* Static labels are usually unnamed.
* @type {string=}
*/
Blockly.Field.prototype.name = undefined;
/**
* Maximum characters of text to display before adding an ellipsis.
* @type {number}
*/
Blockly.Field.prototype.maxDisplayLength = 50;
/**
* Visible text to display.
* @type {string}
* @private
*/
Blockly.Field.prototype.text_ = '';
/**
* Block this field is attached to. Starts as null, then in set in init.
* @type {Blockly.Block}
* @private
*/
Blockly.Field.prototype.sourceBlock_ = null;
/**
* Is the field visible, or hidden due to the block being collapsed?
* @type {boolean}
* @private
*/
Blockly.Field.prototype.visible_ = true;
/**
* Validation function called when user edits an editable field.
* @type {Function}
* @private
*/
Blockly.Field.prototype.validator_ = null;
/**
* Non-breaking space.
* @const
*/
Blockly.Field.NBSP = '\u00A0';
/**
* Editable fields are saved by the XML renderer, non-editable fields are not.
*/
Blockly.Field.prototype.EDITABLE = true;
/**
* Attach this field to a block.
* @param {!Blockly.Block} block The block containing this field.
*/
Blockly.Field.prototype.setSourceBlock = function(block) {
goog.asserts.assert(!this.sourceBlock_, 'Field already bound to a block.');
this.sourceBlock_ = block;
};
/**
* Install this field on a block.
*/
Blockly.Field.prototype.init = function() {
if (this.fieldGroup_) {
// Field has already been initialized once.
return;
}
// Build the DOM.
this.fieldGroup_ = Blockly.createSvgElement('g', {}, null);
if (!this.visible_) {
this.fieldGroup_.style.display = 'none';
}
this.borderRect_ = Blockly.createSvgElement('rect',
{'rx': 4,
'ry': 4,
'x': -Blockly.BlockSvg.SEP_SPACE_X / 2,
'y': 0,
'height': 16}, this.fieldGroup_, this.sourceBlock_.workspace);
/** @type {!Element} */
this.textElement_ = Blockly.createSvgElement('text',
{'class': 'blocklyText', 'y': this.size_.height - 12.5},
this.fieldGroup_);
this.updateEditable();
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
this.mouseUpWrapper_ =
Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_);
// Force a render.
this.updateTextNode_();
};
/**
* Dispose of all DOM objects belonging to this editable field.
*/
Blockly.Field.prototype.dispose = function() {
if (this.mouseUpWrapper_) {
Blockly.unbindEvent_(this.mouseUpWrapper_);
this.mouseUpWrapper_ = null;
}
this.sourceBlock_ = null;
goog.dom.removeNode(this.fieldGroup_);
this.fieldGroup_ = null;
this.textElement_ = null;
this.borderRect_ = null;
this.validator_ = null;
};
/**
* Add or remove the UI indicating if this field is editable or not.
*/
Blockly.Field.prototype.updateEditable = function() {
var group = this.fieldGroup_;
if (!this.EDITABLE || !group) {
return;
}
if (this.sourceBlock_.isEditable()) {
Blockly.addClass_(group, 'blocklyEditableText');
Blockly.removeClass_(group, 'blocklyNonEditableText');
this.fieldGroup_.style.cursor = this.CURSOR;
} else {
Blockly.addClass_(group, 'blocklyNonEditableText');
Blockly.removeClass_(group, 'blocklyEditableText');
this.fieldGroup_.style.cursor = '';
}
};
/**
* Gets whether this editable field is visible or not.
* @return {boolean} True if visible.
*/
Blockly.Field.prototype.isVisible = function() {
return this.visible_;
};
/**
* Sets whether this editable field is visible or not.
* @param {boolean} visible True if visible.
*/
Blockly.Field.prototype.setVisible = function(visible) {
if (this.visible_ == visible) {
return;
}
this.visible_ = visible;
var root = this.getSvgRoot();
if (root) {
root.style.display = visible ? 'block' : 'none';
this.render_();
}
};
/**
* Sets a new validation function for editable fields.
* @param {Function} handler New validation function, or null.
*/
Blockly.Field.prototype.setValidator = function(handler) {
this.validator_ = handler;
};
/**
* Gets the validation function for editable fields.
* @return {Function} Validation function, or null.
*/
Blockly.Field.prototype.getValidator = function() {
return this.validator_;
};
/**
* Validates a change. Does nothing. Subclasses may override this.
* @param {string} text The user's text.
* @return {string} No change needed.
*/
Blockly.Field.prototype.classValidator = function(text) {
return text;
};
/**
* Calls the validation function for this field, as well as all the validation
* function for the field's class and its parents.
* @param {string} text Proposed text.
* @return {?string} Revised text, or null if invalid.
*/
Blockly.Field.prototype.callValidator = function(text) {
var classResult = this.classValidator(text);
if (classResult === null) {
// Class validator rejects value. Game over.
return null;
} else if (classResult !== undefined) {
text = classResult;
}
var userValidator = this.getValidator();
if (userValidator) {
var userResult = userValidator.call(this, text);
if (userResult === null) {
// User validator rejects value. Game over.
return null;
} else if (userResult !== undefined) {
text = userResult;
}
}
return text;
};
/**
* Gets the group element for this editable field.
* Used for measuring the size and for positioning.
* @return {!Element} The group element.
*/
Blockly.Field.prototype.getSvgRoot = function() {
return /** @type {!Element} */ (this.fieldGroup_);
};
/**
* Draws the border with the correct width.
* Saves the computed width in a property.
* @private
*/
Blockly.Field.prototype.render_ = function() {
if (this.visible_ && this.textElement_) {
var key = this.textElement_.textContent + '\n' +
this.textElement_.className.baseVal;
if (Blockly.Field.cacheWidths_ && Blockly.Field.cacheWidths_[key]) {
var width = Blockly.Field.cacheWidths_[key];
} else {
try {
var width = this.textElement_.getComputedTextLength();
} catch (e) {
// MSIE 11 is known to throw "Unexpected call to method or property
// access." if Blockly is hidden.
var width = this.textElement_.textContent.length * 8;
}
if (Blockly.Field.cacheWidths_) {
Blockly.Field.cacheWidths_[key] = width;
}
}
if (this.borderRect_) {
this.borderRect_.setAttribute('width',
width + Blockly.BlockSvg.SEP_SPACE_X);
}
} else {
var width = 0;
}
this.size_.width = width;
};
/**
* Start caching field widths. Every call to this function MUST also call
* stopCache. Caches must not survive between execution threads.
*/
Blockly.Field.startCache = function() {
Blockly.Field.cacheReference_++;
if (!Blockly.Field.cacheWidths_) {
Blockly.Field.cacheWidths_ = {};
}
};
/**
* Stop caching field widths. Unless caching was already on when the
* corresponding call to startCache was made.
*/
Blockly.Field.stopCache = function() {
Blockly.Field.cacheReference_--;
if (!Blockly.Field.cacheReference_) {
Blockly.Field.cacheWidths_ = null;
}
};
/**
* Returns the height and width of the field.
* @return {!goog.math.Size} Height and width.
*/
Blockly.Field.prototype.getSize = function() {
if (!this.size_.width) {
this.render_();
}
return this.size_;
};
/**
* Returns the height and width of the field,
* accounting for the workspace scaling.
* @return {!goog.math.Size} Height and width.
* @private
*/
Blockly.Field.prototype.getScaledBBox_ = function() {
var bBox = this.borderRect_.getBBox();
// Create new object, as getBBox can return an uneditable SVGRect in IE.
return new goog.math.Size(bBox.width * this.sourceBlock_.workspace.scale,
bBox.height * this.sourceBlock_.workspace.scale);
};
/**
* Get the text from this field.
* @return {string} Current text.
*/
Blockly.Field.prototype.getText = function() {
return this.text_;
};
/**
* Set the text in this field. Trigger a rerender of the source block.
* @param {*} text New text.
*/
Blockly.Field.prototype.setText = function(text) {
if (text === null) {
// No change if null.
return;
}
text = String(text);
if (text === this.text_) {
// No change.
return;
}
this.text_ = text;
this.updateTextNode_();
if (this.sourceBlock_ && this.sourceBlock_.rendered) {
this.sourceBlock_.render();
this.sourceBlock_.bumpNeighbours_();
}
};
/**
* Update the text node of this field to display the current text.
* @private
*/
Blockly.Field.prototype.updateTextNode_ = function() {
if (!this.textElement_) {
// Not rendered yet.
return;
}
var text = this.text_;
if (text.length > this.maxDisplayLength) {
// Truncate displayed string and add an ellipsis ('...').
text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
}
// Empty the text element.
goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_));
// Replace whitespace with non-breaking spaces so the text doesn't collapse.
text = text.replace(/\s/g, Blockly.Field.NBSP);
if (this.sourceBlock_.RTL && text) {
// The SVG is LTR, force text to be RTL.
text += '\u200F';
}
if (!text) {
// Prevent the field from disappearing if empty.
text = Blockly.Field.NBSP;
}
var textNode = document.createTextNode(text);
this.textElement_.appendChild(textNode);
// Cached width is obsolete. Clear it.
this.size_.width = 0;
};
/**
* By default there is no difference between the human-readable text and
* the language-neutral values. Subclasses (such as dropdown) may define this.
* @return {string} Current text.
*/
Blockly.Field.prototype.getValue = function() {
return this.getText();
};
/**
* By default there is no difference between the human-readable text and
* the language-neutral values. Subclasses (such as dropdown) may define this.
* @param {string} newText New text.
*/
Blockly.Field.prototype.setValue = function(newText) {
if (newText === null) {
// No change if null.
return;
}
var oldText = this.getValue();
if (oldText == newText) {
return;
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Change(
this.sourceBlock_, 'field', this.name, oldText, newText));
}
this.setText(newText);
};
/**
* Handle a mouse up event on an editable field.
* @param {!Event} e Mouse up event.
* @private
*/
Blockly.Field.prototype.onMouseUp_ = function(e) {
if ((goog.userAgent.IPHONE || goog.userAgent.IPAD) &&
!goog.userAgent.isVersionOrHigher('537.51.2') &&
e.layerX !== 0 && e.layerY !== 0) {
// Old iOS spawns a bogus event on the next touch after a 'prompt()' edit.
// Unlike the real events, these have a layerX and layerY set.
return;
} else if (Blockly.isRightButton(e)) {
// Right-click.
return;
} else if (this.sourceBlock_.workspace.isDragging()) {
// Drag operation is concluding. Don't open the editor.
return;
} else if (this.sourceBlock_.isEditable()) {
// Non-abstract sub-classes must define a showEditor_ method.
this.showEditor_();
}
};
/**
* Change the tooltip text for this field.
* @param {string|!Element} newTip Text for tooltip or a parent element to
* link to for its tooltip.
*/
Blockly.Field.prototype.setTooltip = function(newTip) {
// Non-abstract sub-classes may wish to implement this. See FieldLabel.
};
/**
* Return the absolute coordinates of the top-left corner of this field.
* The origin (0,0) is the top-left corner of the page body.
* @return {!goog.math.Coordinate} Object with .x and .y properties.
* @private
*/
Blockly.Field.prototype.getAbsoluteXY_ = function() {
return goog.style.getPageOffset(this.borderRect_);
};

37
core/field.js.rej Normal file
View File

@@ -0,0 +1,37 @@
***************
*** 87,93 ****
}
this.sourceBlock_ = block;
this.updateEditable();
block.getSvgRoot().appendChild(this.fieldGroup_);
this.mouseUpWrapper_ =
Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_);
// Bump to set the colours for dropdown arrows.
--- 87,113 ----
}
this.sourceBlock_ = block;
this.updateEditable();
+
+ // [lyn, 10/25/13] Handle the special case where adding a title to a collapsed block.
+ // This can happen if a mutator is forced open on a procedure declaration, which happens
+ // in the current AI implementation in Blockly.FieldProcedure.onChange and
+ // in Blockly.Language.procedures_callnoreturn.setProcedureParameters.
+ // Then when the mutator is closed, the compose method of a procedure declaration
+ // is called, and this can invoke the declarations updateParams_ method, which
+ // makes new title elements for the declaration (even a collapsed one).
+ //
+ // Note: even with this "fix", can still see a few white pixels added to top of
+ // collapsed procedure decl when svg is added. I can't explain why.
+ // So it's even better to avoid adding titles to collapsed blocks in the first place!
+ // E.g., I modified proc decl compose method to avoid calling updateParams_
+ // if arg names haven't changed from before.
+ // if (block.collapsed) {
+ // // this.fieldGroup_.style.display = 'none';
+ // this.getRootElement().style.display = 'none';
+ // }
+
block.getSvgRoot().appendChild(this.fieldGroup_);
+
this.mouseUpWrapper_ =
Blockly.bindEvent_(this.fieldGroup_, 'mouseup', this, this.onMouseUp_);
// Bump to set the colours for dropdown arrows.

View File

@@ -318,3 +318,19 @@ Blockly.FieldDropdown.prototype.dispose = function() {
Blockly.WidgetDiv.hideIfOwner(this);
Blockly.FieldDropdown.superClass_.dispose.call(this);
};
/**
* Lookup function to traverse OPERATORS structures (Array of Arrays) to return a human readable
* representation of a particular Operator
* @param {Array} a nested array with Operators and their 'readable' form
* @param {String} the value to look up
* @return {String} the value of the Operator to display to users
*/
Blockly.FieldDropdown.lookupOperator = function(operatorsArray, value){
var arrLength = operatorsArray.length;
for (var i = 0; i < arrLength; i++){
if (operatorsArray[i][1] === value)
return operatorsArray[i][0];
}
throw new Error('value: ' + value + ' not found in OPERATOR: ' + operatorsArray);
};

320
core/field_dropdown.js.orig Normal file
View File

@@ -0,0 +1,320 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Dropdown input field. Used for editable titles and variables.
* In the interests of a consistent UI, the toolbox shares some functions and
* properties with the context menu.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.FieldDropdown');
goog.require('Blockly.Field');
goog.require('goog.dom');
goog.require('goog.events');
goog.require('goog.style');
goog.require('goog.ui.Menu');
goog.require('goog.ui.MenuItem');
goog.require('goog.userAgent');
/**
* Class for an editable dropdown field.
* @param {(!Array.<!Array.<string>>|!Function)} menuGenerator An array of
* options for a dropdown list, or a function which generates these options.
* @param {Function=} opt_validator A function that is executed when a new
* option is selected, with the newly selected value as its sole argument.
* If it returns a value, that value (which must be one of the options) will
* become selected in place of the newly selected option, unless the return
* value is null, in which case the change is aborted.
* @extends {Blockly.Field}
* @constructor
*/
Blockly.FieldDropdown = function(menuGenerator, opt_validator) {
this.menuGenerator_ = menuGenerator;
this.trimOptions_();
var firstTuple = this.getOptions_()[0];
// Call parent's constructor.
Blockly.FieldDropdown.superClass_.constructor.call(this, firstTuple[1],
opt_validator);
};
goog.inherits(Blockly.FieldDropdown, Blockly.Field);
/**
* Horizontal distance that a checkmark ovehangs the dropdown.
*/
Blockly.FieldDropdown.CHECKMARK_OVERHANG = 25;
/**
* Android can't (in 2014) display "▾", so use "▼" instead.
*/
Blockly.FieldDropdown.ARROW_CHAR = goog.userAgent.ANDROID ? '\u25BC' : '\u25BE';
/**
* Mouse cursor style when over the hotspot that initiates the editor.
*/
Blockly.FieldDropdown.prototype.CURSOR = 'default';
/**
* Install this dropdown on a block.
*/
Blockly.FieldDropdown.prototype.init = function() {
if (this.fieldGroup_) {
// Dropdown has already been initialized once.
return;
}
// Add dropdown arrow: "option ▾" (LTR) or "▾ אופציה" (RTL)
this.arrow_ = Blockly.createSvgElement('tspan', {}, null);
this.arrow_.appendChild(document.createTextNode(
this.sourceBlock_.RTL ? Blockly.FieldDropdown.ARROW_CHAR + ' ' :
' ' + Blockly.FieldDropdown.ARROW_CHAR));
Blockly.FieldDropdown.superClass_.init.call(this);
// Force a reset of the text to add the arrow.
var text = this.text_;
this.text_ = null;
this.setText(text);
};
/**
* Create a dropdown menu under the text.
* @private
*/
Blockly.FieldDropdown.prototype.showEditor_ = function() {
Blockly.WidgetDiv.show(this, this.sourceBlock_.RTL, null);
var thisField = this;
function callback(e) {
var menuItem = e.target;
if (menuItem) {
var value = menuItem.getValue();
if (thisField.sourceBlock_) {
// Call any validation function, and allow it to override.
value = thisField.callValidator(value);
}
if (value !== null) {
thisField.setValue(value);
}
}
Blockly.WidgetDiv.hideIfOwner(thisField);
}
var menu = new goog.ui.Menu();
menu.setRightToLeft(this.sourceBlock_.RTL);
var options = this.getOptions_();
for (var i = 0; i < options.length; i++) {
var text = options[i][0]; // Human-readable text.
var value = options[i][1]; // Language-neutral value.
var menuItem = new goog.ui.MenuItem(text);
menuItem.setRightToLeft(this.sourceBlock_.RTL);
menuItem.setValue(value);
menuItem.setCheckable(true);
menu.addChild(menuItem, true);
menuItem.setChecked(value == this.value_);
}
// Listen for mouse/keyboard events.
goog.events.listen(menu, goog.ui.Component.EventType.ACTION, callback);
// Listen for touch events (why doesn't Closure handle this already?).
function callbackTouchStart(e) {
var control = this.getOwnerControl(/** @type {Node} */ (e.target));
// Highlight the menu item.
control.handleMouseDown(e);
}
function callbackTouchEnd(e) {
var control = this.getOwnerControl(/** @type {Node} */ (e.target));
// Activate the menu item.
control.performActionInternal(e);
}
menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHSTART,
callbackTouchStart);
menu.getHandler().listen(menu.getElement(), goog.events.EventType.TOUCHEND,
callbackTouchEnd);
// Record windowSize and scrollOffset before adding menu.
var windowSize = goog.dom.getViewportSize();
var scrollOffset = goog.style.getViewportPageOffset(document);
var xy = this.getAbsoluteXY_();
var borderBBox = this.getScaledBBox_();
var div = Blockly.WidgetDiv.DIV;
menu.render(div);
var menuDom = menu.getElement();
Blockly.addClass_(menuDom, 'blocklyDropdownMenu');
// Record menuSize after adding menu.
var menuSize = goog.style.getSize(menuDom);
// Recalculate height for the total content, not only box height.
menuSize.height = menuDom.scrollHeight;
// Position the menu.
// Flip menu vertically if off the bottom.
if (xy.y + menuSize.height + borderBBox.height >=
windowSize.height + scrollOffset.y) {
xy.y -= menuSize.height + 2;
} else {
xy.y += borderBBox.height;
}
if (this.sourceBlock_.RTL) {
xy.x += borderBBox.width;
xy.x += Blockly.FieldDropdown.CHECKMARK_OVERHANG;
// Don't go offscreen left.
if (xy.x < scrollOffset.x + menuSize.width) {
xy.x = scrollOffset.x + menuSize.width;
}
} else {
xy.x -= Blockly.FieldDropdown.CHECKMARK_OVERHANG;
// Don't go offscreen right.
if (xy.x > windowSize.width + scrollOffset.x - menuSize.width) {
xy.x = windowSize.width + scrollOffset.x - menuSize.width;
}
}
Blockly.WidgetDiv.position(xy.x, xy.y, windowSize, scrollOffset,
this.sourceBlock_.RTL);
menu.setAllowAutoFocus(true);
menuDom.focus();
};
/**
* Factor out common words in statically defined options.
* Create prefix and/or suffix labels.
* @private
*/
Blockly.FieldDropdown.prototype.trimOptions_ = function() {
this.prefixField = null;
this.suffixField = null;
var options = this.menuGenerator_;
if (!goog.isArray(options) || options.length < 2) {
return;
}
var strings = options.map(function(t) {return t[0];});
var shortest = Blockly.shortestStringLength(strings);
var prefixLength = Blockly.commonWordPrefix(strings, shortest);
var suffixLength = Blockly.commonWordSuffix(strings, shortest);
if (!prefixLength && !suffixLength) {
return;
}
if (shortest <= prefixLength + suffixLength) {
// One or more strings will entirely vanish if we proceed. Abort.
return;
}
if (prefixLength) {
this.prefixField = strings[0].substring(0, prefixLength - 1);
}
if (suffixLength) {
this.suffixField = strings[0].substr(1 - suffixLength);
}
// Remove the prefix and suffix from the options.
var newOptions = [];
for (var i = 0; i < options.length; i++) {
var text = options[i][0];
var value = options[i][1];
text = text.substring(prefixLength, text.length - suffixLength);
newOptions[i] = [text, value];
}
this.menuGenerator_ = newOptions;
};
/**
* Return a list of the options for this dropdown.
* @return {!Array.<!Array.<string>>} Array of option tuples:
* (human-readable text, language-neutral name).
* @private
*/
Blockly.FieldDropdown.prototype.getOptions_ = function() {
if (goog.isFunction(this.menuGenerator_)) {
return this.menuGenerator_.call(this);
}
return /** @type {!Array.<!Array.<string>>} */ (this.menuGenerator_);
};
/**
* Get the language-neutral value from this dropdown menu.
* @return {string} Current text.
*/
Blockly.FieldDropdown.prototype.getValue = function() {
return this.value_;
};
/**
* Set the language-neutral value for this dropdown menu.
* @param {string} newValue New value to set.
*/
Blockly.FieldDropdown.prototype.setValue = function(newValue) {
if (newValue === null || newValue === this.value_) {
return; // No change if null.
}
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Change(
this.sourceBlock_, 'field', this.name, this.value_, newValue));
}
this.value_ = newValue;
// Look up and display the human-readable text.
var options = this.getOptions_();
for (var i = 0; i < options.length; i++) {
// Options are tuples of human-readable text and language-neutral values.
if (options[i][1] == newValue) {
this.setText(options[i][0]);
return;
}
}
// Value not found. Add it, maybe it will become valid once set
// (like variable names).
this.setText(newValue);
};
/**
* Set the text in this field. Trigger a rerender of the source block.
* @param {?string} text New text.
*/
Blockly.FieldDropdown.prototype.setText = function(text) {
if (this.sourceBlock_ && this.arrow_) {
// Update arrow's colour.
this.arrow_.style.fill = this.sourceBlock_.getColour();
}
if (text === null || text === this.text_) {
// No change if null.
return;
}
this.text_ = text;
this.updateTextNode_();
if (this.textElement_) {
// Insert dropdown arrow.
if (this.sourceBlock_.RTL) {
this.textElement_.insertBefore(this.arrow_, this.textElement_.firstChild);
} else {
this.textElement_.appendChild(this.arrow_);
}
}
if (this.sourceBlock_ && this.sourceBlock_.rendered) {
this.sourceBlock_.render();
this.sourceBlock_.bumpNeighbours_();
}
};
/**
* Close the dropdown menu if this input is being deleted.
*/
Blockly.FieldDropdown.prototype.dispose = function() {
Blockly.WidgetDiv.hideIfOwner(this);
Blockly.FieldDropdown.superClass_.dispose.call(this);
};

View File

@@ -36,6 +36,20 @@ goog.require('goog.events');
goog.require('goog.math.Rect');
goog.require('goog.userAgent');
/**
* Factor by which margin is multiplied to vertically separate blocks in flyout
* [lyn, 10/06/13] introduced so can change in flydown subclass.)
* @type {number}
* @const
*/
Blockly.Flyout.prototype.VERTICAL_SEPARATION_FACTOR = 2;
/*
* Wrapper function called when a resize occurs.
* @type {Array.<!Array>}
* @private
*/
Blockly.Flyout.prototype.onResizeWrapper_ = null;
/**
* Class for a flyout.

1364
core/flyout.js.orig Normal file

File diff suppressed because it is too large Load Diff

17
core/flyout.js.rej Normal file
View File

@@ -0,0 +1,17 @@
***************
*** 304,310 ****
var block = Blockly.Xml.domToBlock(
/** @type {!Blockly.Workspace} */ (this.workspace_), xml);
blocks.push(block);
- gaps.push(margin * 3);
}
}
}
--- 318,324 ----
var block = Blockly.Xml.domToBlock(
/** @type {!Blockly.Workspace} */ (this.workspace_), xml);
blocks.push(block);
+ gaps.push(margin * this.VERTICAL_SEPARATION_FACTOR); // [lyn, 10/06/13] introduced VERTICAL_SEPARATION_FACTOR
}
}
}

378
core/inject.js.orig Normal file
View File

@@ -0,0 +1,378 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2011 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Functions for injecting Blockly into a web page.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.inject');
goog.require('Blockly.Css');
goog.require('Blockly.Options');
goog.require('Blockly.WorkspaceSvg');
goog.require('goog.dom');
goog.require('goog.ui.Component');
goog.require('goog.userAgent');
/**
* Inject a Blockly editor into the specified container element (usually a div).
* @param {!Element|string} container Containing element, or its ID,
* or a CSS selector.
* @param {Object=} opt_options Optional dictionary of options.
* @return {!Blockly.Workspace} Newly created main workspace.
*/
Blockly.inject = function(container, opt_options) {
if (goog.isString(container)) {
container = document.getElementById(container) ||
document.querySelector(container);
}
// Verify that the container is in document.
if (!goog.dom.contains(document, container)) {
throw 'Error: container is not in current document.';
}
var options = new Blockly.Options(opt_options || {});
var subContainer = goog.dom.createDom('div', 'injectionDiv');
container.appendChild(subContainer);
var svg = Blockly.createDom_(subContainer, options);
var workspace = Blockly.createMainWorkspace_(svg, options);
Blockly.init_(workspace);
workspace.markFocused();
Blockly.bindEvent_(svg, 'focus', workspace, workspace.markFocused);
Blockly.svgResize(workspace);
return workspace;
};
/**
* Create the SVG image.
* @param {!Element} container Containing element.
* @param {!Blockly.Options} options Dictionary of options.
* @return {!Element} Newly created SVG image.
* @private
*/
Blockly.createDom_ = function(container, options) {
// Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying
// out content in RTL mode. Therefore Blockly forces the use of LTR,
// then manually positions content in RTL as needed.
container.setAttribute('dir', 'LTR');
// Closure can be trusted to create HTML widgets with the proper direction.
goog.ui.Component.setDefaultRightToLeft(options.RTL);
// Load CSS.
Blockly.Css.inject(options.hasCss, options.pathToMedia);
// Build the SVG DOM.
/*
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xlink="http://www.w3.org/1999/xlink"
version="1.1"
class="blocklySvg">
...
</svg>
*/
var svg = Blockly.createSvgElement('svg', {
'xmlns': 'http://www.w3.org/2000/svg',
'xmlns:html': 'http://www.w3.org/1999/xhtml',
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
'version': '1.1',
'class': 'blocklySvg'
}, container);
/*
<defs>
... filters go here ...
</defs>
*/
var defs = Blockly.createSvgElement('defs', {}, svg);
var rnd = String(Math.random()).substring(2);
/*
<filter id="blocklyEmbossFilter837493">
<feGaussianBlur in="SourceAlpha" stdDeviation="1" result="blur"/>
<feSpecularLighting in="blur" surfaceScale="1" specularConstant="0.5"
specularExponent="10" lighting-color="white"
result="specOut">
<fePointLight x="-5000" y="-10000" z="20000"/>
</feSpecularLighting>
<feComposite in="specOut" in2="SourceAlpha" operator="in"
result="specOut"/>
<feComposite in="SourceGraphic" in2="specOut" operator="arithmetic"
k1="0" k2="1" k3="1" k4="0"/>
</filter>
*/
var embossFilter = Blockly.createSvgElement('filter',
{'id': 'blocklyEmbossFilter' + rnd}, defs);
Blockly.createSvgElement('feGaussianBlur',
{'in': 'SourceAlpha', 'stdDeviation': 1, 'result': 'blur'}, embossFilter);
var feSpecularLighting = Blockly.createSvgElement('feSpecularLighting',
{'in': 'blur', 'surfaceScale': 1, 'specularConstant': 0.5,
'specularExponent': 10, 'lighting-color': 'white', 'result': 'specOut'},
embossFilter);
Blockly.createSvgElement('fePointLight',
{'x': -5000, 'y': -10000, 'z': 20000}, feSpecularLighting);
Blockly.createSvgElement('feComposite',
{'in': 'specOut', 'in2': 'SourceAlpha', 'operator': 'in',
'result': 'specOut'}, embossFilter);
Blockly.createSvgElement('feComposite',
{'in': 'SourceGraphic', 'in2': 'specOut', 'operator': 'arithmetic',
'k1': 0, 'k2': 1, 'k3': 1, 'k4': 0}, embossFilter);
options.embossFilterId = embossFilter.id;
/*
<pattern id="blocklyDisabledPattern837493" patternUnits="userSpaceOnUse"
width="10" height="10">
<rect width="10" height="10" fill="#aaa" />
<path d="M 0 0 L 10 10 M 10 0 L 0 10" stroke="#cc0" />
</pattern>
*/
var disabledPattern = Blockly.createSvgElement('pattern',
{'id': 'blocklyDisabledPattern' + rnd,
'patternUnits': 'userSpaceOnUse',
'width': 10, 'height': 10}, defs);
Blockly.createSvgElement('rect',
{'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern);
Blockly.createSvgElement('path',
{'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern);
options.disabledPatternId = disabledPattern.id;
/*
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
<rect stroke="#888" />
<rect stroke="#888" />
</pattern>
*/
var gridPattern = Blockly.createSvgElement('pattern',
{'id': 'blocklyGridPattern' + rnd,
'patternUnits': 'userSpaceOnUse'}, defs);
if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) {
Blockly.createSvgElement('line',
{'stroke': options.gridOptions['colour']},
gridPattern);
if (options.gridOptions['length'] > 1) {
Blockly.createSvgElement('line',
{'stroke': options.gridOptions['colour']},
gridPattern);
}
// x1, y1, x1, x2 properties will be set later in updateGridPattern_.
}
options.gridPattern = gridPattern;
return svg;
};
/**
* Create a main workspace and add it to the SVG.
* @param {!Element} svg SVG element with pattern defined.
* @param {!Blockly.Options} options Dictionary of options.
* @return {!Blockly.Workspace} Newly created main workspace.
* @private
*/
Blockly.createMainWorkspace_ = function(svg, options) {
options.parentWorkspace = null;
var mainWorkspace = new Blockly.WorkspaceSvg(options);
mainWorkspace.scale = options.zoomOptions.startScale;
svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
// A null translation will also apply the correct initial scale.
mainWorkspace.translate(0, 0);
mainWorkspace.markFocused();
if (!options.readOnly && !options.hasScrollbars) {
var workspaceChanged = function() {
if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
var metrics = mainWorkspace.getMetrics();
var edgeLeft = metrics.viewLeft + metrics.absoluteLeft;
var edgeTop = metrics.viewTop + metrics.absoluteTop;
if (metrics.contentTop < edgeTop ||
metrics.contentTop + metrics.contentHeight >
metrics.viewHeight + edgeTop ||
metrics.contentLeft <
(options.RTL ? metrics.viewLeft : edgeLeft) ||
metrics.contentLeft + metrics.contentWidth > (options.RTL ?
metrics.viewWidth : metrics.viewWidth + edgeLeft)) {
// One or more blocks may be out of bounds. Bump them back in.
var MARGIN = 25;
var blocks = mainWorkspace.getTopBlocks(false);
for (var b = 0, block; block = blocks[b]; b++) {
var blockXY = block.getRelativeToSurfaceXY();
var blockHW = block.getHeightWidth();
// Bump any block that's above the top back inside.
var overflowTop = edgeTop + MARGIN - blockHW.height - blockXY.y;
if (overflowTop > 0) {
block.moveBy(0, overflowTop);
}
// Bump any block that's below the bottom back inside.
var overflowBottom =
edgeTop + metrics.viewHeight - MARGIN - blockXY.y;
if (overflowBottom < 0) {
block.moveBy(0, overflowBottom);
}
// Bump any block that's off the left back inside.
var overflowLeft = MARGIN + edgeLeft -
blockXY.x - (options.RTL ? 0 : blockHW.width);
if (overflowLeft > 0) {
block.moveBy(overflowLeft, 0);
}
// Bump any block that's off the right back inside.
var overflowRight = edgeLeft + metrics.viewWidth - MARGIN -
blockXY.x + (options.RTL ? blockHW.width : 0);
if (overflowRight < 0) {
block.moveBy(overflowRight, 0);
}
}
}
}
};
mainWorkspace.addChangeListener(workspaceChanged);
}
// The SVG is now fully assembled.
Blockly.svgResize(mainWorkspace);
Blockly.WidgetDiv.createDom();
Blockly.Tooltip.createDom();
return mainWorkspace;
};
/**
* Initialize Blockly with various handlers.
* @param {!Blockly.Workspace} mainWorkspace Newly created main workspace.
* @private
*/
Blockly.init_ = function(mainWorkspace) {
var options = mainWorkspace.options;
var svg = mainWorkspace.getParentSvg();
// Supress the browser's context menu.
Blockly.bindEvent_(svg, 'contextmenu', null,
function(e) {
if (!Blockly.isTargetInput_(e)) {
e.preventDefault();
}
});
var workspaceResizeHandler = Blockly.bindEvent_(window, 'resize', null,
function() {
Blockly.hideChaff(true);
Blockly.svgResize(mainWorkspace);
});
mainWorkspace.setResizeHandlerWrapper(workspaceResizeHandler);
Blockly.inject.bindDocumentEvents_();
if (options.languageTree) {
if (mainWorkspace.toolbox_) {
mainWorkspace.toolbox_.init(mainWorkspace);
} else if (mainWorkspace.flyout_) {
// Build a fixed flyout with the root blocks.
mainWorkspace.flyout_.init(mainWorkspace);
mainWorkspace.flyout_.show(options.languageTree.childNodes);
mainWorkspace.flyout_.scrollToStart();
// Translate the workspace sideways to avoid the fixed flyout.
mainWorkspace.scrollX = mainWorkspace.flyout_.width_;
if (options.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
mainWorkspace.scrollX *= -1;
}
mainWorkspace.translate(mainWorkspace.scrollX, 0);
}
}
if (options.hasScrollbars) {
mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace);
mainWorkspace.scrollbar.resize();
}
// Load the sounds.
if (options.hasSounds) {
Blockly.inject.loadSounds_(options.pathToMedia, mainWorkspace);
}
};
/**
* Bind document events, but only once. Destroying and reinjecting Blockly
* should not bind again.
* Bind events for scrolling the workspace.
* Most of these events should be bound to the SVG's surface.
* However, 'mouseup' has to be on the whole document so that a block dragged
* out of bounds and released will know that it has been released.
* Also, 'keydown' has to be on the whole document since the browser doesn't
* understand a concept of focus on the SVG image.
* @private
*/
Blockly.inject.bindDocumentEvents_ = function() {
if (!Blockly.documentEventsBound_) {
Blockly.bindEvent_(document, 'keydown', null, Blockly.onKeyDown_);
Blockly.bindEvent_(document, 'touchend', null, Blockly.longStop_);
Blockly.bindEvent_(document, 'touchcancel', null, Blockly.longStop_);
// Don't use bindEvent_ for document's mouseup since that would create a
// corresponding touch handler that would squeltch the ability to interact
// with non-Blockly elements.
document.addEventListener('mouseup', Blockly.onMouseUp_, false);
// Some iPad versions don't fire resize after portrait to landscape change.
if (goog.userAgent.IPAD) {
Blockly.bindEvent_(window, 'orientationchange', document, function() {
// TODO(#397): Fix for multiple blockly workspaces.
Blockly.svgResize(Blockly.getMainWorkspace());
});
}
}
Blockly.documentEventsBound_ = true;
};
/**
* Load sounds for the given workspace.
* @param {string} pathToMedia The path to the media directory.
* @param {!Blockly.Workspace} workspace The workspace to load sounds for.
* @private
*/
Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
workspace.loadAudio_(
[pathToMedia + 'click.mp3',
pathToMedia + 'click.wav',
pathToMedia + 'click.ogg'], 'click');
workspace.loadAudio_(
[pathToMedia + 'disconnect.wav',
pathToMedia + 'disconnect.mp3',
pathToMedia + 'disconnect.ogg'], 'disconnect');
workspace.loadAudio_(
[pathToMedia + 'delete.mp3',
pathToMedia + 'delete.ogg',
pathToMedia + 'delete.wav'], 'delete');
// Bind temporary hooks that preload the sounds.
var soundBinds = [];
var unbindSounds = function() {
while (soundBinds.length) {
Blockly.unbindEvent_(soundBinds.pop());
}
workspace.preloadAudio_();
};
// Android ignores any sound not loaded as a result of a user action.
soundBinds.push(
Blockly.bindEvent_(document, 'mousemove', null, unbindSounds));
soundBinds.push(
Blockly.bindEvent_(document, 'touchstart', null, unbindSounds));
};
/**
* Modify the block tree on the existing toolbox.
* @param {Node|string} tree DOM tree of blocks, or text representation of same.
*/
Blockly.updateToolbox = function(tree) {
console.warn('Deprecated call to Blockly.updateToolbox, ' +
'use workspace.updateToolbox instead.');
Blockly.getMainWorkspace().updateToolbox(tree);
};

129
core/inject.js.rej Normal file
View File

@@ -0,0 +1,129 @@
***************
*** 108,113 ****
if (hasCollapse === undefined) {
hasCollapse = hasCategories;
}
var hasComments = options['comments'];
if (hasComments === undefined) {
hasComments = hasCategories;
--- 108,114 ----
if (hasCollapse === undefined) {
hasCollapse = hasCategories;
}
+ var configForTypeBlock = options['typeblock_config'];
var hasComments = options['comments'];
if (hasComments === undefined) {
hasComments = hasCategories;
***************
*** 125,134 ****
if (hasScrollbars === undefined) {
hasScrollbars = true;
}
}
var enableRealtime = !!options['realtime'];
var realtimeOptions = enableRealtime ? options['realtimeOptions'] : undefined;
-
Blockly.RTL = !!options['rtl'];
Blockly.collapse = hasCollapse;
Blockly.comments = hasComments;
--- 126,135 ----
if (hasScrollbars === undefined) {
hasScrollbars = true;
}
+ var configForTypeBlock = null;
}
var enableRealtime = !!options['realtime'];
var realtimeOptions = enableRealtime ? options['realtimeOptions'] : undefined;
Blockly.RTL = !!options['rtl'];
Blockly.collapse = hasCollapse;
Blockly.comments = hasComments;
***************
*** 140,145 ****
Blockly.hasScrollbars = hasScrollbars;
Blockly.hasTrashcan = hasTrashcan;
Blockly.languageTree = tree;
Blockly.enableRealtime = enableRealtime;
Blockly.realtimeOptions = realtimeOptions;
};
--- 141,147 ----
Blockly.hasScrollbars = hasScrollbars;
Blockly.hasTrashcan = hasTrashcan;
Blockly.languageTree = tree;
+ Blockly.configForTypeBlock = configForTypeBlock;
Blockly.enableRealtime = enableRealtime;
Blockly.realtimeOptions = realtimeOptions;
};
***************
*** 271,276 ****
* @type {!Blockly.Flyout}
* @private
*/
Blockly.mainWorkspace.flyout_ = new Blockly.Flyout();
var flyout = Blockly.mainWorkspace.flyout_;
var flyoutSvg = flyout.createDom();
--- 273,280 ----
* @type {!Blockly.Flyout}
* @private
*/
+
+ /*
Blockly.mainWorkspace.flyout_ = new Blockly.Flyout();
var flyout = Blockly.mainWorkspace.flyout_;
var flyoutSvg = flyout.createDom();
***************
*** 329,334 ****
}
};
Blockly.addChangeListener(workspaceChanged);
}
}
--- 333,339 ----
}
};
Blockly.addChangeListener(workspaceChanged);
+ */
}
}
***************
*** 398,407 ****
Blockly.Toolbox.init();
} else {
// Build a fixed flyout with the root blocks.
- Blockly.mainWorkspace.flyout_.init(Blockly.mainWorkspace, true);
- Blockly.mainWorkspace.flyout_.show(Blockly.languageTree.childNodes);
- // Translate the workspace sideways to avoid the fixed flyout.
- Blockly.mainWorkspace.scrollX = Blockly.mainWorkspace.flyout_.width_;
if (Blockly.RTL) {
Blockly.mainWorkspace.scrollX *= -1;
}
--- 403,414 ----
Blockly.Toolbox.init();
} else {
// Build a fixed flyout with the root blocks.
+ //if (Blockly.mainWorkspace.flyout_) {
+ Blockly.mainWorkspace.flyout_.init(Blockly.mainWorkspace, true);
+ Blockly.mainWorkspace.flyout_.show(Blockly.languageTree.childNodes);
+ // Translate the workspace sideways to avoid the fixed flyout.
+ Blockly.mainWorkspace.scrollX = Blockly.mainWorkspace.flyout_.width_;
+ //}
if (Blockly.RTL) {
Blockly.mainWorkspace.scrollX *= -1;
}
***************
*** 418,423 ****
}
Blockly.mainWorkspace.addTrashcan();
// Load the sounds.
Blockly.loadAudio_(
--- 425,431 ----
}
Blockly.mainWorkspace.addTrashcan();
+ Blockly.mainWorkspace.addWarningIndicator(Blockly.mainWorkspace);
// Load the sounds.
Blockly.loadAudio_(

View File

@@ -102,6 +102,10 @@ Blockly.Input.prototype.appendField = function(field, opt_name) {
this.appendField(field.suffixField);
}
//If it's a COLLAPSE_TEXT input, hide it by default
if (opt_name === 'COLLAPSED_TEXT')
this.sourceBlock_.getTitle_(opt_name).getRootElement().style.display = 'none';
if (this.sourceBlock_.rendered) {
this.sourceBlock_.render();
// Adding a field will cause the block to change shape.

241
core/input.js.orig Normal file
View File

@@ -0,0 +1,241 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Object representing an input (value, statement, or dummy).
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Input');
goog.require('Blockly.Connection');
goog.require('Blockly.FieldLabel');
goog.require('goog.asserts');
/**
* Class for an input with an optional field.
* @param {number} type The type of the input.
* @param {string} name Language-neutral identifier which may used to find this
* input again.
* @param {!Blockly.Block} block The block containing this input.
* @param {Blockly.Connection} connection Optional connection for this input.
* @constructor
*/
Blockly.Input = function(type, name, block, connection) {
/** @type {number} */
this.type = type;
/** @type {string} */
this.name = name;
/**
* @type {!Blockly.Block}
* @private
*/
this.sourceBlock_ = block;
/** @type {Blockly.Connection} */
this.connection = connection;
/** @type {!Array.<!Blockly.Field>} */
this.fieldRow = [];
};
/**
* Alignment of input's fields (left, right or centre).
* @type {number}
*/
Blockly.Input.prototype.align = Blockly.ALIGN_LEFT;
/**
* Is the input visible?
* @type {boolean}
* @private
*/
Blockly.Input.prototype.visible_ = true;
/**
* Add an item to the end of the input's field row.
* @param {string|!Blockly.Field} field Something to add as a field.
* @param {string=} opt_name Language-neutral identifier which may used to find
* this field again. Should be unique to the host block.
* @return {!Blockly.Input} The input being append to (to allow chaining).
*/
Blockly.Input.prototype.appendField = function(field, opt_name) {
// Empty string, Null or undefined generates no field, unless field is named.
if (!field && !opt_name) {
return this;
}
// Generate a FieldLabel when given a plain text field.
if (goog.isString(field)) {
field = new Blockly.FieldLabel(/** @type {string} */ (field));
}
field.setSourceBlock(this.sourceBlock_);
if (this.sourceBlock_.rendered) {
field.init();
}
field.name = opt_name;
if (field.prefixField) {
// Add any prefix.
this.appendField(field.prefixField);
}
// Add the field to the field row.
this.fieldRow.push(field);
if (field.suffixField) {
// Add any suffix.
this.appendField(field.suffixField);
}
if (this.sourceBlock_.rendered) {
this.sourceBlock_.render();
// Adding a field will cause the block to change shape.
this.sourceBlock_.bumpNeighbours_();
}
return this;
};
/**
* Add an item to the end of the input's field row.
* @param {*} field Something to add as a field.
* @param {string=} opt_name Language-neutral identifier which may used to find
* this field again. Should be unique to the host block.
* @return {!Blockly.Input} The input being append to (to allow chaining).
* @deprecated December 2013
*/
Blockly.Input.prototype.appendTitle = function(field, opt_name) {
console.warn('Deprecated call to appendTitle, use appendField instead.');
return this.appendField(field, opt_name);
};
/**
* Remove a field from this input.
* @param {string} name The name of the field.
* @throws {goog.asserts.AssertionError} if the field is not present.
*/
Blockly.Input.prototype.removeField = function(name) {
for (var i = 0, field; field = this.fieldRow[i]; i++) {
if (field.name === name) {
field.dispose();
this.fieldRow.splice(i, 1);
if (this.sourceBlock_.rendered) {
this.sourceBlock_.render();
// Removing a field will cause the block to change shape.
this.sourceBlock_.bumpNeighbours_();
}
return;
}
}
goog.asserts.fail('Field "%s" not found.', name);
};
/**
* Gets whether this input is visible or not.
* @return {boolean} True if visible.
*/
Blockly.Input.prototype.isVisible = function() {
return this.visible_;
};
/**
* Sets whether this input is visible or not.
* Used to collapse/uncollapse a block.
* @param {boolean} visible True if visible.
* @return {!Array.<!Blockly.Block>} List of blocks to render.
*/
Blockly.Input.prototype.setVisible = function(visible) {
var renderList = [];
if (this.visible_ == visible) {
return renderList;
}
this.visible_ = visible;
var display = visible ? 'block' : 'none';
for (var y = 0, field; field = this.fieldRow[y]; y++) {
field.setVisible(visible);
}
if (this.connection) {
// Has a connection.
if (visible) {
renderList = this.connection.unhideAll();
} else {
this.connection.hideAll();
}
var child = this.connection.targetBlock();
if (child) {
child.getSvgRoot().style.display = display;
if (!visible) {
child.rendered = false;
}
}
}
return renderList;
};
/**
* Change a connection's compatibility.
* @param {string|Array.<string>|null} check Compatible value type or
* list of value types. Null if all types are compatible.
* @return {!Blockly.Input} The input being modified (to allow chaining).
*/
Blockly.Input.prototype.setCheck = function(check) {
if (!this.connection) {
throw 'This input does not have a connection.';
}
this.connection.setCheck(check);
return this;
};
/**
* Change the alignment of the connection's field(s).
* @param {number} align One of Blockly.ALIGN_LEFT, ALIGN_CENTRE, ALIGN_RIGHT.
* In RTL mode directions are reversed, and ALIGN_RIGHT aligns to the left.
* @return {!Blockly.Input} The input being modified (to allow chaining).
*/
Blockly.Input.prototype.setAlign = function(align) {
this.align = align;
if (this.sourceBlock_.rendered) {
this.sourceBlock_.render();
}
return this;
};
/**
* Initialize the fields on this input.
*/
Blockly.Input.prototype.init = function() {
if (!this.sourceBlock_.workspace.rendered) {
return; // Headless blocks don't need fields initialized.
}
for (var i = 0; i < this.fieldRow.length; i++) {
this.fieldRow[i].init();
}
};
/**
* Sever all links to this input.
*/
Blockly.Input.prototype.dispose = function() {
for (var i = 0, field; field = this.fieldRow[i]; i++) {
field.dispose();
}
if (this.connection) {
this.connection.dispose();
}
this.sourceBlock_ = null;
};

23
core/input.js.rej Normal file
View File

@@ -0,0 +1,23 @@
***************
*** 43,49 ****
* @constructor
*/
Blockly.Input = function(type, name, block, connection) {
- this.type = type;
this.name = name;
this.sourceBlock_ = block;
this.connection = connection;
--- 43,55 ----
* @constructor
*/
Blockly.Input = function(type, name, block, connection) {
+ if (type == Blockly.INDENTED_VALUE){
+ this.type = Blockly.INPUT_VALUE;
+ this.subtype = Blockly.INDENTED_VALUE;
+ }
+ else {
+ this.type = type;
+ }
this.name = name;
this.sourceBlock_ = block;
this.connection = connection;

389
core/mutator.js.orig Normal file
View File

@@ -0,0 +1,389 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Object representing a mutator dialog. A mutator allows the
* user to change the shape of a block using a nested blocks editor.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Mutator');
goog.require('Blockly.Bubble');
goog.require('Blockly.Icon');
goog.require('Blockly.WorkspaceSvg');
goog.require('goog.Timer');
goog.require('goog.dom');
/**
* Class for a mutator dialog.
* @param {!Array.<string>} quarkNames List of names of sub-blocks for flyout.
* @extends {Blockly.Icon}
* @constructor
*/
Blockly.Mutator = function(quarkNames) {
Blockly.Mutator.superClass_.constructor.call(this, null);
this.quarkNames_ = quarkNames;
};
goog.inherits(Blockly.Mutator, Blockly.Icon);
/**
* Width of workspace.
* @private
*/
Blockly.Mutator.prototype.workspaceWidth_ = 0;
/**
* Height of workspace.
* @private
*/
Blockly.Mutator.prototype.workspaceHeight_ = 0;
/**
* Draw the mutator icon.
* @param {!Element} group The icon group.
* @private
*/
Blockly.Mutator.prototype.drawIcon_ = function(group) {
// Square with rounded corners.
Blockly.createSvgElement('rect',
{'class': 'blocklyIconShape',
'rx': '4', 'ry': '4',
'height': '16', 'width': '16'},
group);
// Gear teeth.
Blockly.createSvgElement('path',
{'class': 'blocklyIconSymbol',
'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 0.41,0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 1.8,0 0.3,-0.299 0.127,-1.138 1.185,-0.682 1.046,0.458 0.409,-0.11 0.9,-1.559 -0.11,-0.41 -0.92,-0.677 0,-1.366 0.92,-0.677 0.11,-0.41 -0.9,-1.559 -0.409,-0.109 -1.046,0.458 -1.185,-0.682 -0.127,-1.138 -0.3,-0.299 -1.8,0 -0.3,0.3 -0.126,1.135 -1.187,0.682 -1.043,-0.457 -0.41,0.11 -0.899,1.559 0.108,0.409z'},
group);
// Axle hole.
Blockly.createSvgElement('circle',
{'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'},
group);
};
/**
* Clicking on the icon toggles if the mutator bubble is visible.
* Disable if block is uneditable.
* @param {!Event} e Mouse click event.
* @private
* @override
*/
Blockly.Mutator.prototype.iconClick_ = function(e) {
if (this.block_.isEditable()) {
Blockly.Icon.prototype.iconClick_.call(this, e);
}
};
/**
* Create the editor for the mutator's bubble.
* @return {!Element} The top-level node of the editor.
* @private
*/
Blockly.Mutator.prototype.createEditor_ = function() {
/* Create the editor. Here's the markup that will be generated:
<svg>
[Workspace]
</svg>
*/
this.svgDialog_ = Blockly.createSvgElement('svg',
{'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
null);
// Convert the list of names into a list of XML objects for the flyout.
if (this.quarkNames_.length) {
var quarkXml = goog.dom.createDom('xml');
for (var i = 0, quarkName; quarkName = this.quarkNames_[i]; i++) {
quarkXml.appendChild(goog.dom.createDom('block', {'type': quarkName}));
}
} else {
var quarkXml = null;
}
var workspaceOptions = {
languageTree: quarkXml,
parentWorkspace: this.block_.workspace,
pathToMedia: this.block_.workspace.options.pathToMedia,
RTL: this.block_.RTL,
toolboxPosition: this.block_.RTL ? Blockly.TOOLBOX_AT_RIGHT :
Blockly.TOOLBOX_AT_LEFT,
horizontalLayout: false,
getMetrics: this.getFlyoutMetrics_.bind(this),
setMetrics: null
};
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
this.workspace_.isMutator = true;
this.svgDialog_.appendChild(
this.workspace_.createDom('blocklyMutatorBackground'));
return this.svgDialog_;
};
/**
* Add or remove the UI indicating if this icon may be clicked or not.
*/
Blockly.Mutator.prototype.updateEditable = function() {
if (!this.block_.isInFlyout) {
if (this.block_.isEditable()) {
if (this.iconGroup_) {
Blockly.removeClass_(/** @type {!Element} */ (this.iconGroup_),
'blocklyIconGroupReadonly');
}
} else {
// Close any mutator bubble. Icon is not clickable.
this.setVisible(false);
if (this.iconGroup_) {
Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_),
'blocklyIconGroupReadonly');
}
}
}
// Default behaviour for an icon.
Blockly.Icon.prototype.updateEditable.call(this);
};
/**
* Callback function triggered when the bubble has resized.
* Resize the workspace accordingly.
* @private
*/
Blockly.Mutator.prototype.resizeBubble_ = function() {
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
var workspaceSize = this.workspace_.getCanvas().getBBox();
var width;
if (this.block_.RTL) {
width = -workspaceSize.x;
} else {
width = workspaceSize.width + workspaceSize.x;
}
var height = workspaceSize.height + doubleBorderWidth * 3;
if (this.workspace_.flyout_) {
var flyoutMetrics = this.workspace_.flyout_.getMetrics_();
height = Math.max(height, flyoutMetrics.contentHeight + 20);
}
width += doubleBorderWidth * 3;
// Only resize if the size difference is significant. Eliminates shuddering.
if (Math.abs(this.workspaceWidth_ - width) > doubleBorderWidth ||
Math.abs(this.workspaceHeight_ - height) > doubleBorderWidth) {
// Record some layout information for getFlyoutMetrics_.
this.workspaceWidth_ = width;
this.workspaceHeight_ = height;
// Resize the bubble.
this.bubble_.setBubbleSize(width + doubleBorderWidth,
height + doubleBorderWidth);
this.svgDialog_.setAttribute('width', this.workspaceWidth_);
this.svgDialog_.setAttribute('height', this.workspaceHeight_);
}
if (this.block_.RTL) {
// Scroll the workspace to always left-align.
var translation = 'translate(' + this.workspaceWidth_ + ',0)';
this.workspace_.getCanvas().setAttribute('transform', translation);
}
this.workspace_.resize();
};
/**
* Show or hide the mutator bubble.
* @param {boolean} visible True if the bubble should be visible.
*/
Blockly.Mutator.prototype.setVisible = function(visible) {
if (visible == this.isVisible()) {
// No change.
return;
}
Blockly.Events.fire(
new Blockly.Events.Ui(this.block_, 'mutatorOpen', !visible, visible));
if (visible) {
// Create the bubble.
this.bubble_ = new Blockly.Bubble(
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
this.createEditor_(), this.block_.svgPath_, this.iconXY_, null, null);
var tree = this.workspace_.options.languageTree;
if (tree) {
this.workspace_.flyout_.init(this.workspace_);
this.workspace_.flyout_.show(tree.childNodes);
}
this.rootBlock_ = this.block_.decompose(this.workspace_);
var blocks = this.rootBlock_.getDescendants();
for (var i = 0, child; child = blocks[i]; i++) {
child.render();
}
// The root block should not be dragable or deletable.
this.rootBlock_.setMovable(false);
this.rootBlock_.setDeletable(false);
if (this.workspace_.flyout_) {
var margin = this.workspace_.flyout_.CORNER_RADIUS * 2;
var x = this.workspace_.flyout_.width_ + margin;
} else {
var margin = 16;
var x = margin;
}
if (this.block_.RTL) {
x = -x;
}
this.rootBlock_.moveBy(x, margin);
// Save the initial connections, then listen for further changes.
if (this.block_.saveConnections) {
var thisMutator = this;
this.block_.saveConnections(this.rootBlock_);
this.sourceListener_ = function() {
thisMutator.block_.saveConnections(thisMutator.rootBlock_);
};
this.block_.workspace.addChangeListener(this.sourceListener_);
}
this.resizeBubble_();
// When the mutator's workspace changes, update the source block.
this.workspace_.addChangeListener(this.workspaceChanged_.bind(this));
this.updateColour();
} else {
// Dispose of the bubble.
this.svgDialog_ = null;
this.workspace_.dispose();
this.workspace_ = null;
this.rootBlock_ = null;
this.bubble_.dispose();
this.bubble_ = null;
this.workspaceWidth_ = 0;
this.workspaceHeight_ = 0;
if (this.sourceListener_) {
this.block_.workspace.removeChangeListener(this.sourceListener_);
this.sourceListener_ = null;
}
}
};
/**
* Update the source block when the mutator's blocks are changed.
* Bump down any block that's too high.
* Fired whenever a change is made to the mutator's workspace.
* @private
*/
Blockly.Mutator.prototype.workspaceChanged_ = function() {
if (Blockly.dragMode_ == Blockly.DRAG_NONE) {
var blocks = this.workspace_.getTopBlocks(false);
var MARGIN = 20;
for (var b = 0, block; block = blocks[b]; b++) {
var blockXY = block.getRelativeToSurfaceXY();
var blockHW = block.getHeightWidth();
if (blockXY.y + blockHW.height < MARGIN) {
// Bump any block that's above the top back inside.
block.moveBy(0, MARGIN - blockHW.height - blockXY.y);
}
}
}
// When the mutator's workspace changes, update the source block.
if (this.rootBlock_.workspace == this.workspace_) {
Blockly.Events.setGroup(true);
var block = this.block_;
var oldMutationDom = block.mutationToDom();
var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
// Switch off rendering while the source block is rebuilt.
var savedRendered = block.rendered;
block.rendered = false;
// Allow the source block to rebuild itself.
block.compose(this.rootBlock_);
// Restore rendering and show the changes.
block.rendered = savedRendered;
// Mutation may have added some elements that need initalizing.
block.initSvg();
var newMutationDom = block.mutationToDom();
var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
if (oldMutation != newMutation) {
Blockly.Events.fire(new Blockly.Events.Change(
block, 'mutation', null, oldMutation, newMutation));
// Ensure that any bump is part of this mutation's event group.
var group = Blockly.Events.getGroup();
setTimeout(function() {
Blockly.Events.setGroup(group);
block.bumpNeighbours_();
Blockly.Events.setGroup(false);
}, Blockly.BUMP_DELAY);
}
if (block.rendered) {
block.render();
}
this.resizeBubble_();
Blockly.Events.setGroup(false);
}
};
/**
* Return an object with all the metrics required to size scrollbars for the
* mutator flyout. The following properties are computed:
* .viewHeight: Height of the visible rectangle,
* .viewWidth: Width of the visible rectangle,
* .absoluteTop: Top-edge of view.
* .absoluteLeft: Left-edge of view.
* @return {!Object} Contains size and position metrics of mutator dialog's
* workspace.
* @private
*/
Blockly.Mutator.prototype.getFlyoutMetrics_ = function() {
return {
viewHeight: this.workspaceHeight_,
viewWidth: this.workspaceWidth_,
absoluteTop: 0,
absoluteLeft: 0
};
};
/**
* Dispose of this mutator.
*/
Blockly.Mutator.prototype.dispose = function() {
this.block_.mutator = null;
Blockly.Icon.prototype.dispose.call(this);
};
/**
* Reconnect an block to a mutated input.
* @param {Blockly.Connection} connectionChild Connection on child block.
* @param {!Blockly.Block} block Parent block.
* @param {string} inputName Name of input on parent block.
* @return {boolean} True iff a reconnection was made, false otherwise.
*/
Blockly.Mutator.reconnect = function(connectionChild, block, inputName) {
if (!connectionChild || !connectionChild.getSourceBlock().workspace) {
return false; // No connection or block has been deleted.
}
var connectionParent = block.getInput(inputName).connection;
var currentParent = connectionChild.targetBlock();
if ((!currentParent || currentParent == block) &&
connectionParent.targetConnection != connectionChild) {
if (connectionParent.isConnected()) {
// There's already something connected here. Get rid of it.
connectionParent.disconnect();
}
connectionParent.connect(connectionChild);
return true;
}
return false;
};
// Export symbols that would otherwise be renamed by Closure compiler.
if (!goog.global['Blockly']) {
goog.global['Blockly'] = {};
}
if (!goog.global['Blockly']['Mutator']) {
goog.global['Blockly']['Mutator'] = {};
}
goog.global['Blockly']['Mutator']['reconnect'] = Blockly.Mutator.reconnect;

63
core/mutator.js.rej Normal file
View File

@@ -0,0 +1,63 @@
***************
*** 80,86 ****
{'class': 'blocklyIconMark',
'x': Blockly.Icon.RADIUS,
'y': 2 * Blockly.Icon.RADIUS - 4}, this.iconGroup_);
- this.iconMark_.appendChild(document.createTextNode('\u2605'));
};
/**
--- 80,87 ----
{'class': 'blocklyIconMark',
'x': Blockly.Icon.RADIUS,
'y': 2 * Blockly.Icon.RADIUS - 4}, this.iconGroup_);
+ this.iconMark_.appendChild(document.createTextNode('\u2699'));
+ //this.iconMark_.appendChild(document.createTextNode('\u2605'));
};
/**
***************
*** 122,127 ****
this.flyout_.autoClose = false;
this.svgDialog_.appendChild(this.flyout_.createDom());
this.svgDialog_.appendChild(this.workspace_.createDom());
return this.svgDialog_;
};
--- 123,136 ----
this.flyout_.autoClose = false;
this.svgDialog_.appendChild(this.flyout_.createDom());
this.svgDialog_.appendChild(this.workspace_.createDom());
+
+ //when mutator bubble is clicked, do not close mutator
+ Blockly.bindEvent_(this.svgDialog_, 'mousedown', this.svgDialog_,
+ function(e) {
+ e.preventDefault();
+ e.stopPropagation();
+ });
+
return this.svgDialog_;
};
***************
*** 147,153 ****
*/
Blockly.Mutator.prototype.resizeBubble_ = function() {
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
- var workspaceSize = this.workspace_.getCanvas().getBBox();
var flyoutMetrics = this.flyout_.getMetrics_();
var width;
if (Blockly.RTL) {
--- 156,167 ----
*/
Blockly.Mutator.prototype.resizeBubble_ = function() {
var doubleBorderWidth = 2 * Blockly.Bubble.BORDER_WIDTH;
+ try {
+ var workspaceSize = this.workspace_.getCanvas().getBBox();
+ } catch (e) {
+ // Firefox has trouble with hidden elements (Bug 528969).
+ return;
+ }
var flyoutMetrics = this.flyout_.getMetrics_();
var width;
if (Blockly.RTL) {

287
core/procedures.js.orig Normal file
View File

@@ -0,0 +1,287 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Utility functions for handling procedures.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Procedures');
goog.require('Blockly.Blocks');
goog.require('Blockly.Field');
goog.require('Blockly.Names');
goog.require('Blockly.Workspace');
/**
* Category to separate procedure names from variables and generated functions.
*/
Blockly.Procedures.NAME_TYPE = 'PROCEDURE';
/**
* Find all user-created procedure definitions in a workspace.
* @param {!Blockly.Workspace} root Root workspace.
* @return {!Array.<!Array.<!Array>>} Pair of arrays, the
* first contains procedures without return variables, the second with.
* Each procedure is defined by a three-element list of name, parameter
* list, and return value boolean.
*/
Blockly.Procedures.allProcedures = function(root) {
var blocks = root.getAllBlocks();
var proceduresReturn = [];
var proceduresNoReturn = [];
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].getProcedureDef) {
var tuple = blocks[i].getProcedureDef();
if (tuple) {
if (tuple[2]) {
proceduresReturn.push(tuple);
} else {
proceduresNoReturn.push(tuple);
}
}
}
}
proceduresNoReturn.sort(Blockly.Procedures.procTupleComparator_);
proceduresReturn.sort(Blockly.Procedures.procTupleComparator_);
return [proceduresNoReturn, proceduresReturn];
};
/**
* Comparison function for case-insensitive sorting of the first element of
* a tuple.
* @param {!Array} ta First tuple.
* @param {!Array} tb Second tuple.
* @return {number} -1, 0, or 1 to signify greater than, equality, or less than.
* @private
*/
Blockly.Procedures.procTupleComparator_ = function(ta, tb) {
return ta[0].toLowerCase().localeCompare(tb[0].toLowerCase());
};
/**
* Ensure two identically-named procedures don't exist.
* @param {string} name Proposed procedure name.
* @param {!Blockly.Block} block Block to disambiguate.
* @return {string} Non-colliding name.
*/
Blockly.Procedures.findLegalName = function(name, block) {
if (block.isInFlyout) {
// Flyouts can have multiple procedures called 'do something'.
return name;
}
while (!Blockly.Procedures.isLegalName_(name, block.workspace, block)) {
// Collision with another procedure.
var r = name.match(/^(.*?)(\d+)$/);
if (!r) {
name += '2';
} else {
name = r[1] + (parseInt(r[2], 10) + 1);
}
}
return name;
};
/**
* Does this procedure have a legal name? Illegal names include names of
* procedures already defined.
* @param {string} name The questionable name.
* @param {!Blockly.Workspace} workspace The workspace to scan for collisions.
* @param {Blockly.Block=} opt_exclude Optional block to exclude from
* comparisons (one doesn't want to collide with oneself).
* @return {boolean} True if the name is legal.
* @private
*/
Blockly.Procedures.isLegalName_ = function(name, workspace, opt_exclude) {
var blocks = workspace.getAllBlocks();
// Iterate through every block and check the name.
for (var i = 0; i < blocks.length; i++) {
if (blocks[i] == opt_exclude) {
continue;
}
if (blocks[i].getProcedureDef) {
var procName = blocks[i].getProcedureDef();
if (Blockly.Names.equals(procName[0], name)) {
return false;
}
}
}
return true;
};
/**
* Rename a procedure. Called by the editable field.
* @param {string} name The proposed new name.
* @return {string} The accepted name.
* @this {!Blockly.Field}
*/
Blockly.Procedures.rename = function(name) {
// Strip leading and trailing whitespace. Beyond this, all names are legal.
name = name.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
// Ensure two identically-named procedures don't exist.
var legalName = Blockly.Procedures.findLegalName(name, this.sourceBlock_);
var oldName = this.text_;
if (oldName != name && oldName != legalName) {
// Rename any callers.
var blocks = this.sourceBlock_.workspace.getAllBlocks();
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].renameProcedure) {
blocks[i].renameProcedure(oldName, legalName);
}
}
}
return legalName;
};
/**
* Construct the blocks required by the flyout for the procedure category.
* @param {!Blockly.Workspace} workspace The workspace contianing procedures.
* @return {!Array.<!Element>} Array of XML block elements.
*/
Blockly.Procedures.flyoutCategory = function(workspace) {
var xmlList = [];
if (Blockly.Blocks['procedures_defnoreturn']) {
// <block type="procedures_defnoreturn" gap="16"></block>
var block = goog.dom.createDom('block');
block.setAttribute('type', 'procedures_defnoreturn');
block.setAttribute('gap', 16);
xmlList.push(block);
}
if (Blockly.Blocks['procedures_defreturn']) {
// <block type="procedures_defreturn" gap="16"></block>
var block = goog.dom.createDom('block');
block.setAttribute('type', 'procedures_defreturn');
block.setAttribute('gap', 16);
xmlList.push(block);
}
if (Blockly.Blocks['procedures_ifreturn']) {
// <block type="procedures_ifreturn" gap="16"></block>
var block = goog.dom.createDom('block');
block.setAttribute('type', 'procedures_ifreturn');
block.setAttribute('gap', 16);
xmlList.push(block);
}
if (xmlList.length) {
// Add slightly larger gap between system blocks and user calls.
xmlList[xmlList.length - 1].setAttribute('gap', 24);
}
function populateProcedures(procedureList, templateName) {
for (var i = 0; i < procedureList.length; i++) {
var name = procedureList[i][0];
var args = procedureList[i][1];
// <block type="procedures_callnoreturn" gap="16">
// <mutation name="do something">
// <arg name="x"></arg>
// </mutation>
// </block>
var block = goog.dom.createDom('block');
block.setAttribute('type', templateName);
block.setAttribute('gap', 16);
var mutation = goog.dom.createDom('mutation');
mutation.setAttribute('name', name);
block.appendChild(mutation);
for (var j = 0; j < args.length; j++) {
var arg = goog.dom.createDom('arg');
arg.setAttribute('name', args[j]);
mutation.appendChild(arg);
}
xmlList.push(block);
}
}
var tuple = Blockly.Procedures.allProcedures(workspace);
populateProcedures(tuple[0], 'procedures_callnoreturn');
populateProcedures(tuple[1], 'procedures_callreturn');
return xmlList;
};
/**
* Find all the callers of a named procedure.
* @param {string} name Name of procedure.
* @param {!Blockly.Workspace} workspace The workspace to find callers in.
* @return {!Array.<!Blockly.Block>} Array of caller blocks.
*/
Blockly.Procedures.getCallers = function(name, workspace) {
var callers = [];
var blocks = workspace.getAllBlocks();
// Iterate through every block and check the name.
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].getProcedureCall) {
var procName = blocks[i].getProcedureCall();
// Procedure name may be null if the block is only half-built.
if (procName && Blockly.Names.equals(procName, name)) {
callers.push(blocks[i]);
}
}
}
return callers;
};
/**
* When a procedure definition changes its parameters, find and edit all its
* callers.
* @param {!Blockly.Block} defBlock Procedure definition block.
*/
Blockly.Procedures.mutateCallers = function(defBlock) {
var oldRecordUndo = Blockly.Events.recordUndo;
var name = defBlock.getProcedureDef()[0];
var xmlElement = defBlock.mutationToDom(true);
var callers = Blockly.Procedures.getCallers(name, defBlock.workspace);
for (var i = 0, caller; caller = callers[i]; i++) {
var oldMutationDom = caller.mutationToDom();
var oldMutation = oldMutationDom && Blockly.Xml.domToText(oldMutationDom);
caller.domToMutation(xmlElement);
var newMutationDom = caller.mutationToDom();
var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
if (oldMutation != newMutation) {
// Fire a mutation on every caller block. But don't record this as an
// undo action since it is deterministically tied to the procedure's
// definition mutation.
Blockly.Events.recordUndo = false;
Blockly.Events.fire(new Blockly.Events.Change(
caller, 'mutation', null, oldMutation, newMutation));
Blockly.Events.recordUndo = oldRecordUndo;
}
}
};
/**
* Find the definition block for the named procedure.
* @param {string} name Name of procedure.
* @param {!Blockly.Workspace} workspace The workspace to search.
* @return {Blockly.Block} The procedure definition block, or null not found.
*/
Blockly.Procedures.getDefinition = function(name, workspace) {
// Assume that a procedure definition is a top block.
var blocks = workspace.getTopBlocks(false);
for (var i = 0; i < blocks.length; i++) {
if (blocks[i].getProcedureDef) {
var tuple = blocks[i].getProcedureDef();
if (tuple && Blockly.Names.equals(tuple[0], name)) {
return blocks[i];
}
}
}
return null;
};

46
core/procedures.js.rej Normal file
View File

@@ -0,0 +1,46 @@
***************
*** 99,104 ****
// Flyouts can have multiple procedures called 'procedure'.
return name;
}
while (!Blockly.Procedures.isLegalName(name, block.workspace, block)) {
// Collision with another procedure.
var r = name.match(/^(.*?)(\d+)$/);
--- 99,105 ----
// Flyouts can have multiple procedures called 'procedure'.
return name;
}
+ name = name.replace(/\s+/g, '');
while (!Blockly.Procedures.isLegalName(name, block.workspace, block)) {
// Collision with another procedure.
var r = name.match(/^(.*?)(\d+)$/);
***************
*** 255,266 ****
* @param {!Blockly.Workspace} workspace The workspace to delete callers from.
* @param {!Array.<string>} paramNames Array of new parameter names.
* @param {!Array.<string>} paramIds Array of unique parameter IDs.
*/
Blockly.Procedures.mutateCallers = function(name, workspace,
- paramNames, paramIds) {
var callers = Blockly.Procedures.getCallers(name, workspace);
for (var x = 0; x < callers.length; x++) {
- callers[x].setProcedureParameters(paramNames, paramIds);
}
};
--- 256,270 ----
* @param {!Blockly.Workspace} workspace The workspace to delete callers from.
* @param {!Array.<string>} paramNames Array of new parameter names.
* @param {!Array.<string>} paramIds Array of unique parameter IDs.
+ * @param {boolean} opt_intializeTracking indicate whether paramId tracking should start
+ * Is undefined (falsey) as default. // [lyn, 10/26/13] added this.
*/
Blockly.Procedures.mutateCallers = function(name, workspace,
+ paramNames, paramIds,
+ opt_initializeTracking) {
var callers = Blockly.Procedures.getCallers(name, workspace);
for (var x = 0; x < callers.length; x++) {
+ callers[x].setProcedureParameters(paramNames, paramIds, opt_initializeTracking);
}
};

View File

@@ -74,6 +74,7 @@ Blockly.ScrollbarPair.prototype.dispose = function() {
* Also reposition the corner rectangle.
*/
Blockly.ScrollbarPair.prototype.resize = function() {
var start = new Date().getTime();
// Look up the host metrics once, and use for both scrollbars.
var hostMetrics = this.workspace_.getMetrics();
if (!hostMetrics) {
@@ -125,9 +126,12 @@ Blockly.ScrollbarPair.prototype.resize = function() {
this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop) {
this.corner_.setAttribute('y', this.hScroll.position_.y);
}
// Cache the current metrics to potentially short-cut the next resize event.
this.oldHostMetrics_ = hostMetrics;
var stop = new Date().getTime();
var timeDiff = stop - start;
Blockly.Instrument.stats.scrollBarResizeCalls++; //***lyn
Blockly.Instrument.stats.scrollBarResizeTime += timeDiff; //***lyn
};
/**

750
core/scrollbar.js.orig Normal file
View File

@@ -0,0 +1,750 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2011 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Library for creating scrollbars.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Scrollbar');
goog.provide('Blockly.ScrollbarPair');
goog.require('goog.dom');
goog.require('goog.events');
/**
* Class for a pair of scrollbars. Horizontal and vertical.
* @param {!Blockly.Workspace} workspace Workspace to bind the scrollbars to.
* @constructor
*/
Blockly.ScrollbarPair = function(workspace) {
this.workspace_ = workspace;
this.hScroll = new Blockly.Scrollbar(workspace, true, true);
this.vScroll = new Blockly.Scrollbar(workspace, false, true);
this.corner_ = Blockly.createSvgElement('rect',
{'height': Blockly.Scrollbar.scrollbarThickness,
'width': Blockly.Scrollbar.scrollbarThickness,
'class': 'blocklyScrollbarBackground'}, null);
Blockly.Scrollbar.insertAfter_(this.corner_, workspace.getBubbleCanvas());
};
/**
* Previously recorded metrics from the workspace.
* @type {Object}
* @private
*/
Blockly.ScrollbarPair.prototype.oldHostMetrics_ = null;
/**
* Dispose of this pair of scrollbars.
* Unlink from all DOM elements to prevent memory leaks.
*/
Blockly.ScrollbarPair.prototype.dispose = function() {
goog.dom.removeNode(this.corner_);
this.corner_ = null;
this.workspace_ = null;
this.oldHostMetrics_ = null;
this.hScroll.dispose();
this.hScroll = null;
this.vScroll.dispose();
this.vScroll = null;
};
/**
* Recalculate both of the scrollbars' locations and lengths.
* Also reposition the corner rectangle.
*/
Blockly.ScrollbarPair.prototype.resize = function() {
// Look up the host metrics once, and use for both scrollbars.
var hostMetrics = this.workspace_.getMetrics();
if (!hostMetrics) {
// Host element is likely not visible.
return;
}
// Only change the scrollbars if there has been a change in metrics.
var resizeH = false;
var resizeV = false;
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth ||
this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight ||
this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop ||
this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) {
// The window has been resized or repositioned.
resizeH = true;
resizeV = true;
} else {
// Has the content been resized or moved?
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.contentWidth != hostMetrics.contentWidth ||
this.oldHostMetrics_.viewLeft != hostMetrics.viewLeft ||
this.oldHostMetrics_.contentLeft != hostMetrics.contentLeft) {
resizeH = true;
}
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.contentHeight != hostMetrics.contentHeight ||
this.oldHostMetrics_.viewTop != hostMetrics.viewTop ||
this.oldHostMetrics_.contentTop != hostMetrics.contentTop) {
resizeV = true;
}
}
if (resizeH) {
this.hScroll.resize(hostMetrics);
}
if (resizeV) {
this.vScroll.resize(hostMetrics);
}
// Reposition the corner square.
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.viewWidth != hostMetrics.viewWidth ||
this.oldHostMetrics_.absoluteLeft != hostMetrics.absoluteLeft) {
this.corner_.setAttribute('x', this.vScroll.position_.x);
}
if (!this.oldHostMetrics_ ||
this.oldHostMetrics_.viewHeight != hostMetrics.viewHeight ||
this.oldHostMetrics_.absoluteTop != hostMetrics.absoluteTop) {
this.corner_.setAttribute('y', this.hScroll.position_.y);
}
// Cache the current metrics to potentially short-cut the next resize event.
this.oldHostMetrics_ = hostMetrics;
};
/**
* Set the sliders of both scrollbars to be at a certain position.
* @param {number} x Horizontal scroll value.
* @param {number} y Vertical scroll value.
*/
Blockly.ScrollbarPair.prototype.set = function(x, y) {
// This function is equivalent to:
// this.hScroll.set(x);
// this.vScroll.set(y);
// However, that calls setMetrics twice which causes a chain of
// getAttribute->setAttribute->getAttribute resulting in an extra layout pass.
// Combining them speeds up rendering.
var xyRatio = {};
var hHandlePosition = x * this.hScroll.ratio_;
var vHandlePosition = y * this.vScroll.ratio_;
var hBarLength = this.hScroll.scrollViewSize_;
var vBarLength = this.vScroll.scrollViewSize_;
xyRatio.x = this.getRatio_(hHandlePosition, hBarLength);
xyRatio.y = this.getRatio_(vHandlePosition, vBarLength);
this.workspace_.setMetrics(xyRatio);
this.hScroll.setHandlePosition(hHandlePosition);
this.vScroll.setHandlePosition(vHandlePosition);
};
/**
* Helper to calculate the ratio of handle position to scrollbar view size.
* @param {number} handlePosition The value of the handle.
* @param {number} viewSize The total size of the scrollbar's view.
* @return {number} Ratio.
* @private
*/
Blockly.ScrollbarPair.prototype.getRatio_ = function(handlePosition, viewSize) {
var ratio = handlePosition / viewSize;
if (isNaN(ratio)) {
return 0;
}
return ratio;
};
// --------------------------------------------------------------------
/**
* Class for a pure SVG scrollbar.
* This technique offers a scrollbar that is guaranteed to work, but may not
* look or behave like the system's scrollbars.
* @param {!Blockly.Workspace} workspace Workspace to bind the scrollbar to.
* @param {boolean} horizontal True if horizontal, false if vertical.
* @param {boolean=} opt_pair True if scrollbar is part of a horiz/vert pair.
* @constructor
*/
Blockly.Scrollbar = function(workspace, horizontal, opt_pair) {
this.workspace_ = workspace;
this.pair_ = opt_pair || false;
this.horizontal_ = horizontal;
this.oldHostMetrics_ = null;
this.createDom_();
/**
* The upper left corner of the scrollbar's svg group.
* @type {goog.math.Coordinate}
* @private
*/
this.position_ = new goog.math.Coordinate(0, 0);
if (horizontal) {
this.svgBackground_.setAttribute('height',
Blockly.Scrollbar.scrollbarThickness);
this.svgHandle_.setAttribute('height',
Blockly.Scrollbar.scrollbarThickness - 5);
this.svgHandle_.setAttribute('y', 2.5);
this.lengthAttribute_ = 'width';
this.positionAttribute_ = 'x';
} else {
this.svgBackground_.setAttribute('width',
Blockly.Scrollbar.scrollbarThickness);
this.svgHandle_.setAttribute('width',
Blockly.Scrollbar.scrollbarThickness - 5);
this.svgHandle_.setAttribute('x', 2.5);
this.lengthAttribute_ = 'height';
this.positionAttribute_ = 'y';
}
var scrollbar = this;
this.onMouseDownBarWrapper_ = Blockly.bindEvent_(this.svgBackground_,
'mousedown', scrollbar, scrollbar.onMouseDownBar_);
this.onMouseDownHandleWrapper_ = Blockly.bindEvent_(this.svgHandle_,
'mousedown', scrollbar, scrollbar.onMouseDownHandle_);
};
/**
* The size of the area within which the scrollbar handle can move.
* @type {number}
* @private
*/
Blockly.Scrollbar.prototype.scrollViewSize_ = 0;
/**
* The length of the scrollbar handle.
* @type {number}
* @private
*/
Blockly.Scrollbar.prototype.handleLength_ = 0;
/**
* The offset of the start of the handle from the start of the scrollbar range.
* @type {number}
* @private
*/
Blockly.Scrollbar.prototype.handlePosition_ = 0;
/**
* Whether the scrollbar handle is visible.
* @type {boolean}
* @private
*/
Blockly.Scrollbar.prototype.isVisible_ = true;
/**
* Width of vertical scrollbar or height of horizontal scrollbar.
* Increase the size of scrollbars on touch devices.
* Don't define if there is no document object (e.g. node.js).
*/
Blockly.Scrollbar.scrollbarThickness = 15;
if (goog.events.BrowserFeature.TOUCH_ENABLED) {
Blockly.Scrollbar.scrollbarThickness = 25;
}
/**
* @param {!Object} first An object containing computed measurements of a
* workspace.
* @param {!Object} second Another object containing computed measurements of a
* workspace.
* @return {boolean} Whether the two sets of metrics are equivalent.
* @private
*/
Blockly.Scrollbar.metricsAreEquivalent_ = function(first, second) {
if (!(first && second)) {
return false;
}
if (first.viewWidth != second.viewWidth ||
first.viewHeight != second.viewHeight ||
first.viewLeft != second.viewLeft ||
first.viewTop != second.viewTop ||
first.absoluteTop != second.absoluteTop ||
first.absoluteLeft != second.absoluteLeft ||
first.contentWidth != second.contentWidth ||
first.contentHeight != second.contentHeight ||
first.contentLeft != second.contentLeft ||
first.contentTop != second.contentTop) {
return false;
}
return true;
};
/**
* Dispose of this scrollbar.
* Unlink from all DOM elements to prevent memory leaks.
*/
Blockly.Scrollbar.prototype.dispose = function() {
this.onMouseUpHandle_();
Blockly.unbindEvent_(this.onMouseDownBarWrapper_);
this.onMouseDownBarWrapper_ = null;
Blockly.unbindEvent_(this.onMouseDownHandleWrapper_);
this.onMouseDownHandleWrapper_ = null;
goog.dom.removeNode(this.svgGroup_);
this.svgGroup_ = null;
this.svgBackground_ = null;
this.svgHandle_ = null;
this.workspace_ = null;
};
/**
* Set the length of the scrollbar's handle and change the SVG attribute
* accordingly.
* @param {number} newLength The new scrollbar handle length.
*/
Blockly.Scrollbar.prototype.setHandleLength_ = function(newLength) {
this.handleLength_ = newLength;
this.svgHandle_.setAttribute(this.lengthAttribute_, this.handleLength_);
};
/**
* Set the offset of the scrollbar's handle and change the SVG attribute
* accordingly.
* @param {number} newPosition The new scrollbar handle offset.
*/
Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) {
this.handlePosition_ = newPosition;
this.svgHandle_.setAttribute(this.positionAttribute_, this.handlePosition_);
};
/**
* Set the size of the scrollbar's background and change the SVG attribute
* accordingly.
* @param {number} newSize The new scrollbar background length.
* @private
*/
Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) {
this.scrollViewSize_ = newSize;
this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_);
};
/**
* Set the position of the scrollbar's svg group.
* @param {number} x The new x coordinate.
* @param {number} y The new y coordinate.
*/
Blockly.Scrollbar.prototype.setPosition = function(x, y) {
this.position_.x = x;
this.position_.y = y;
this.svgGroup_.setAttribute('transform',
'translate(' + this.position_.x + ',' + this.position_.y + ')');
};
/**
* Recalculate the scrollbar's location and its length.
* @param {Object=} opt_metrics A data structure of from the describing all the
* required dimensions. If not provided, it will be fetched from the host
* object.
*/
Blockly.Scrollbar.prototype.resize = function(opt_metrics) {
// Determine the location, height and width of the host element.
var hostMetrics = opt_metrics;
if (!hostMetrics) {
hostMetrics = this.workspace_.getMetrics();
if (!hostMetrics) {
// Host element is likely not visible.
return;
}
}
if (Blockly.Scrollbar.metricsAreEquivalent_(hostMetrics,
this.oldHostMetrics_)) {
return;
}
this.oldHostMetrics_ = hostMetrics;
/* hostMetrics is an object with the following properties.
* .viewHeight: Height of the visible rectangle,
* .viewWidth: Width of the visible rectangle,
* .contentHeight: Height of the contents,
* .contentWidth: Width of the content,
* .viewTop: Offset of top edge of visible rectangle from parent,
* .viewLeft: Offset of left edge of visible rectangle from parent,
* .contentTop: Offset of the top-most content from the y=0 coordinate,
* .contentLeft: Offset of the left-most content from the x=0 coordinate,
* .absoluteTop: Top-edge of view.
* .absoluteLeft: Left-edge of view.
*/
if (this.horizontal_) {
this.resizeHorizontal_(hostMetrics);
} else {
this.resizeVertical_(hostMetrics);
}
// Resizing may have caused some scrolling.
this.onScroll_();
};
/**
* Recalculate a horizontal scrollbar's location and length.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
* @private
*/
Blockly.Scrollbar.prototype.resizeHorizontal_ = function(hostMetrics) {
// TODO: Inspect metrics to determine if we can get away with just a content
// resize.
this.resizeViewHorizontal(hostMetrics);
};
/**
* Recalculate a horizontal scrollbar's location on the screen and path length.
* This should be called when the layout or size of the window has changed.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeViewHorizontal = function(hostMetrics) {
var viewSize = hostMetrics.viewWidth - 1;
if (this.pair_) {
// Shorten the scrollbar to make room for the corner square.
viewSize -= Blockly.Scrollbar.scrollbarThickness;
}
this.setScrollViewSize_(Math.max(0, viewSize));
var xCoordinate = hostMetrics.absoluteLeft + 0.5;
if (this.pair_ && this.workspace_.RTL) {
xCoordinate += Blockly.Scrollbar.scrollbarThickness;
}
// Horizontal toolbar should always be just above the bottom of the workspace.
var yCoordinate = hostMetrics.absoluteTop + hostMetrics.viewHeight -
Blockly.Scrollbar.scrollbarThickness - 0.5;
this.setPosition(xCoordinate, yCoordinate);
// If the view has been resized, a content resize will also be necessary. The
// reverse is not true.
this.resizeContentHorizontal(hostMetrics);
};
/**
* Recalculate a horizontal scrollbar's location within its path and length.
* This should be called when the contents of the workspace have changed.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeContentHorizontal = function(hostMetrics) {
if (!this.pair_) {
// Only show the scrollbar if needed.
// Ideally this would also apply to scrollbar pairs, but that's a bigger
// headache (due to interactions with the corner square).
this.setVisible(this.scrollViewSize_ < hostMetrics.contentWidth);
}
this.ratio_ = this.scrollViewSize_ / hostMetrics.contentWidth;
if (this.ratio_ == -Infinity || this.ratio_ == Infinity ||
isNaN(this.ratio_)) {
this.ratio_ = 0;
}
var handleLength = hostMetrics.viewWidth * this.ratio_;
this.setHandleLength_(Math.max(0, handleLength));
var handlePosition = (hostMetrics.viewLeft - hostMetrics.contentLeft) *
this.ratio_;
this.setHandlePosition(this.constrainHandle_(handlePosition));
};
/**
* Recalculate a vertical scrollbar's location and length.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
* @private
*/
Blockly.Scrollbar.prototype.resizeVertical_ = function(hostMetrics) {
// TODO: Inspect metrics to determine if we can get away with just a content
// resize.
this.resizeViewVertical(hostMetrics);
};
/**
* Recalculate a vertical scrollbar's location on the screen and path length.
* This should be called when the layout or size of the window has changed.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeViewVertical = function(hostMetrics) {
var viewSize = hostMetrics.viewHeight - 1;
if (this.pair_) {
// Shorten the scrollbar to make room for the corner square.
viewSize -= Blockly.Scrollbar.scrollbarThickness;
}
this.setScrollViewSize_(Math.max(0, viewSize));
var xCoordinate = hostMetrics.absoluteLeft + 0.5;
if (!this.workspace_.RTL) {
xCoordinate += hostMetrics.viewWidth -
Blockly.Scrollbar.scrollbarThickness - 1;
}
var yCoordinate = hostMetrics.absoluteTop + 0.5;
this.setPosition(xCoordinate, yCoordinate);
// If the view has been resized, a content resize will also be necessary. The
// reverse is not true.
this.resizeContentVertical(hostMetrics);
};
/**
* Recalculate a vertical scrollbar's location within its path and length.
* This should be called when the contents of the workspace have changed.
* @param {!Object} hostMetrics A data structure describing all the
* required dimensions, possibly fetched from the host object.
*/
Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) {
if (!this.pair_) {
// Only show the scrollbar if needed.
this.setVisible(this.scrollViewSize_ < hostMetrics.contentHeight);
}
this.ratio_ = this.scrollViewSize_ / hostMetrics.contentHeight;
if (this.ratio_ == -Infinity || this.ratio_ == Infinity ||
isNaN(this.ratio_)) {
this.ratio_ = 0;
}
var handleLength = hostMetrics.viewHeight * this.ratio_;
this.setHandleLength_(Math.max(0, handleLength));
var handlePosition = (hostMetrics.viewTop - hostMetrics.contentTop) *
this.ratio_;
this.setHandlePosition(this.constrainHandle_(handlePosition));
};
/**
* Create all the DOM elements required for a scrollbar.
* The resulting widget is not sized.
* @private
*/
Blockly.Scrollbar.prototype.createDom_ = function() {
/* Create the following DOM:
<g class="blocklyScrollbarHorizontal">
<rect class="blocklyScrollbarBackground" />
<rect class="blocklyScrollbarHandle" rx="8" ry="8" />
</g>
*/
var className = 'blocklyScrollbar' +
(this.horizontal_ ? 'Horizontal' : 'Vertical');
this.svgGroup_ = Blockly.createSvgElement('g', {'class': className}, null);
this.svgBackground_ = Blockly.createSvgElement('rect',
{'class': 'blocklyScrollbarBackground'}, this.svgGroup_);
var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2);
this.svgHandle_ = Blockly.createSvgElement('rect',
{'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius},
this.svgGroup_);
Blockly.Scrollbar.insertAfter_(this.svgGroup_,
this.workspace_.getBubbleCanvas());
};
/**
* Is the scrollbar visible. Non-paired scrollbars disappear when they aren't
* needed.
* @return {boolean} True if visible.
*/
Blockly.Scrollbar.prototype.isVisible = function() {
return this.isVisible_;
};
/**
* Set whether the scrollbar is visible.
* Only applies to non-paired scrollbars.
* @param {boolean} visible True if visible.
*/
Blockly.Scrollbar.prototype.setVisible = function(visible) {
if (visible == this.isVisible()) {
return;
}
// Ideally this would also apply to scrollbar pairs, but that's a bigger
// headache (due to interactions with the corner square).
if (this.pair_) {
throw 'Unable to toggle visibility of paired scrollbars.';
}
this.isVisible_ = visible;
if (visible) {
this.svgGroup_.setAttribute('display', 'block');
} else {
// Hide the scrollbar.
this.workspace_.setMetrics({x: 0, y: 0});
this.svgGroup_.setAttribute('display', 'none');
}
};
/**
* Scroll by one pageful.
* Called when scrollbar background is clicked.
* @param {!Event} e Mouse down event.
* @private
*/
Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) {
this.onMouseUpHandle_();
if (Blockly.isRightButton(e)) {
// Right-click.
// Scrollbars have no context menu.
e.stopPropagation();
return;
}
var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg(),
this.workspace_.getInverseScreenCTM());
var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y;
var handleXY = Blockly.getSvgXY_(this.svgHandle_, this.workspace_);
var handleStart = this.horizontal_ ? handleXY.x : handleXY.y;
var handlePosition = this.handlePosition_;
var pageLength = this.handleLength_ * 0.95;
if (mouseLocation <= handleStart) {
// Decrease the scrollbar's value by a page.
handlePosition -= pageLength;
} else if (mouseLocation >= handleStart + this.handleLength_) {
// Increase the scrollbar's value by a page.
handlePosition += pageLength;
}
this.setHandlePosition(this.constrainHandle_(handlePosition));
this.onScroll_();
e.stopPropagation();
e.preventDefault();
};
/**
* Start a dragging operation.
* Called when scrollbar handle is clicked.
* @param {!Event} e Mouse down event.
* @private
*/
Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) {
this.onMouseUpHandle_();
if (Blockly.isRightButton(e)) {
// Right-click.
// Scrollbars have no context menu.
e.stopPropagation();
return;
}
// Look up the current translation and record it.
this.startDragHandle = this.handlePosition_;
// Record the current mouse position.
this.startDragMouse = this.horizontal_ ? e.clientX : e.clientY;
Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEvent_(document,
'mouseup', this, this.onMouseUpHandle_);
Blockly.Scrollbar.onMouseMoveWrapper_ = Blockly.bindEvent_(document,
'mousemove', this, this.onMouseMoveHandle_);
e.stopPropagation();
e.preventDefault();
};
/**
* Drag the scrollbar's handle.
* @param {!Event} e Mouse up event.
* @private
*/
Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) {
var currentMouse = this.horizontal_ ? e.clientX : e.clientY;
var mouseDelta = currentMouse - this.startDragMouse;
var handlePosition = this.startDragHandle + mouseDelta;
// Position the bar.
this.setHandlePosition(this.constrainHandle_(handlePosition));
this.onScroll_();
};
/**
* Stop binding to the global mouseup and mousemove events.
* @private
*/
Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() {
Blockly.hideChaff(true);
if (Blockly.Scrollbar.onMouseUpWrapper_) {
Blockly.unbindEvent_(Blockly.Scrollbar.onMouseUpWrapper_);
Blockly.Scrollbar.onMouseUpWrapper_ = null;
}
if (Blockly.Scrollbar.onMouseMoveWrapper_) {
Blockly.unbindEvent_(Blockly.Scrollbar.onMouseMoveWrapper_);
Blockly.Scrollbar.onMouseMoveWrapper_ = null;
}
};
/**
* Constrain the handle's position within the minimum (0) and maximum
* (length of scrollbar) values allowed for the scrollbar.
* @param {number} value Value that is potentially out of bounds.
* @return {number} Constrained value.
* @private
*/
Blockly.Scrollbar.prototype.constrainHandle_ = function(value) {
if (value <= 0 || isNaN(value) || this.scrollViewSize_ < this.handleLength_) {
value = 0;
} else {
value = Math.min(value, this.scrollViewSize_ - this.handleLength_);
}
return value;
};
/**
* Called when scrollbar is moved.
* @private
*/
Blockly.Scrollbar.prototype.onScroll_ = function() {
var ratio = this.handlePosition_ / this.scrollViewSize_;
if (isNaN(ratio)) {
ratio = 0;
}
var xyRatio = {};
if (this.horizontal_) {
xyRatio.x = ratio;
} else {
xyRatio.y = ratio;
}
this.workspace_.setMetrics(xyRatio);
};
/**
* Set the scrollbar slider's position.
* @param {number} value The distance from the top/left end of the bar.
*/
Blockly.Scrollbar.prototype.set = function(value) {
this.setHandlePosition(this.constrainHandle_(value * this.ratio_));
this.onScroll_();
};
/**
* Insert a node after a reference node.
* Contrast with node.insertBefore function.
* @param {!Element} newNode New element to insert.
* @param {!Element} refNode Existing element to precede new node.
* @private
*/
Blockly.Scrollbar.insertAfter_ = function(newNode, refNode) {
var siblingNode = refNode.nextSibling;
var parentNode = refNode.parentNode;
if (!parentNode) {
throw 'Reference node has no parent.';
}
if (siblingNode) {
parentNode.insertBefore(newNode, siblingNode);
} else {
parentNode.appendChild(newNode);
}
};

16
core/scrollbar.js.rej Normal file
View File

@@ -0,0 +1,16 @@
***************
*** 27,32 ****
goog.provide('Blockly.Scrollbar');
goog.provide('Blockly.ScrollbarPair');
goog.require('goog.userAgent');
--- 27,33 ----
goog.provide('Blockly.Scrollbar');
goog.provide('Blockly.ScrollbarPair');
+ goog.require('Blockly.Instrument'); // lyn's instrumentation code
goog.require('goog.userAgent');

View File

@@ -26,6 +26,7 @@
goog.provide('Blockly.Trashcan');
goog.require('Blockly.Instrument'); // lyn's instrumentation code
goog.require('goog.Timer');
goog.require('goog.dom');
goog.require('goog.math');
@@ -258,6 +259,10 @@ Blockly.Trashcan.prototype.position = function() {
}
this.svgGroup_.setAttribute('transform',
'translate(' + this.left_ + ',' + this.top_ + ')');
var stop = new Date().getTime();
var timeDiff = stop - start;
Blockly.Instrument.stats.trashCanPositionCalls++; //***lyn
Blockly.Instrument.stats.trashCanPositionTime += timeDiff; //***lyn
};
/**

332
core/trashcan.js.orig Normal file
View File

@@ -0,0 +1,332 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2011 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Object representing a trash can icon.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Trashcan');
goog.require('goog.Timer');
goog.require('goog.dom');
goog.require('goog.math');
goog.require('goog.math.Rect');
/**
* Class for a trash can.
* @param {!Blockly.Workspace} workspace The workspace to sit in.
* @constructor
*/
Blockly.Trashcan = function(workspace) {
this.workspace_ = workspace;
};
/**
* Width of both the trash can and lid images.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.WIDTH_ = 47;
/**
* Height of the trashcan image (minus lid).
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.BODY_HEIGHT_ = 44;
/**
* Height of the lid image.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.LID_HEIGHT_ = 16;
/**
* Distance between trashcan and bottom edge of workspace.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.MARGIN_BOTTOM_ = 20;
/**
* Distance between trashcan and right edge of workspace.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.MARGIN_SIDE_ = 20;
/**
* Extent of hotspot on all sides beyond the size of the image.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.MARGIN_HOTSPOT_ = 10;
/**
* Location of trashcan in sprite image.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.SPRITE_LEFT_ = 0;
/**
* Location of trashcan in sprite image.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.SPRITE_TOP_ = 32;
/**
* Current open/close state of the lid.
* @type {boolean}
*/
Blockly.Trashcan.prototype.isOpen = false;
/**
* The SVG group containing the trash can.
* @type {Element}
* @private
*/
Blockly.Trashcan.prototype.svgGroup_ = null;
/**
* The SVG image element of the trash can lid.
* @type {Element}
* @private
*/
Blockly.Trashcan.prototype.svgLid_ = null;
/**
* Task ID of opening/closing animation.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.lidTask_ = 0;
/**
* Current state of lid opening (0.0 = closed, 1.0 = open).
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.lidOpen_ = 0;
/**
* Left coordinate of the trash can.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.left_ = 0;
/**
* Top coordinate of the trash can.
* @type {number}
* @private
*/
Blockly.Trashcan.prototype.top_ = 0;
/**
* Create the trash can elements.
* @return {!Element} The trash can's SVG group.
*/
Blockly.Trashcan.prototype.createDom = function() {
/* Here's the markup that will be generated:
<g class="blocklyTrash">
<clippath id="blocklyTrashBodyClipPath837493">
<rect width="47" height="45" y="15"></rect>
</clippath>
<image width="64" height="92" y="-32" xlink:href="media/sprites.png"
clip-path="url(#blocklyTrashBodyClipPath837493)"></image>
<clippath id="blocklyTrashLidClipPath837493">
<rect width="47" height="15"></rect>
</clippath>
<image width="84" height="92" y="-32" xlink:href="media/sprites.png"
clip-path="url(#blocklyTrashLidClipPath837493)"></image>
</g>
*/
this.svgGroup_ = Blockly.createSvgElement('g',
{'class': 'blocklyTrash'}, null);
var rnd = String(Math.random()).substring(2);
var clip = Blockly.createSvgElement('clipPath',
{'id': 'blocklyTrashBodyClipPath' + rnd},
this.svgGroup_);
Blockly.createSvgElement('rect',
{'width': this.WIDTH_, 'height': this.BODY_HEIGHT_,
'y': this.LID_HEIGHT_},
clip);
var body = Blockly.createSvgElement('image',
{'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_,
'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_,
'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')'},
this.svgGroup_);
body.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
this.workspace_.options.pathToMedia + Blockly.SPRITE.url);
var clip = Blockly.createSvgElement('clipPath',
{'id': 'blocklyTrashLidClipPath' + rnd},
this.svgGroup_);
Blockly.createSvgElement('rect',
{'width': this.WIDTH_, 'height': this.LID_HEIGHT_}, clip);
this.svgLid_ = Blockly.createSvgElement('image',
{'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_,
'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_,
'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')'},
this.svgGroup_);
this.svgLid_.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
this.workspace_.options.pathToMedia + Blockly.SPRITE.url);
Blockly.bindEvent_(this.svgGroup_, 'mouseup', this, this.click);
this.animateLid_();
return this.svgGroup_;
};
/**
* Initialize the trash can.
* @param {number} bottom Distance from workspace bottom to bottom of trashcan.
* @return {number} Distance from workspace bottom to the top of trashcan.
*/
Blockly.Trashcan.prototype.init = function(bottom) {
this.bottom_ = this.MARGIN_BOTTOM_ + bottom;
this.setOpen_(false);
return this.bottom_ + this.BODY_HEIGHT_ + this.LID_HEIGHT_;
};
/**
* Dispose of this trash can.
* Unlink from all DOM elements to prevent memory leaks.
*/
Blockly.Trashcan.prototype.dispose = function() {
if (this.svgGroup_) {
goog.dom.removeNode(this.svgGroup_);
this.svgGroup_ = null;
}
this.svgLid_ = null;
this.workspace_ = null;
goog.Timer.clear(this.lidTask_);
};
/**
* Move the trash can to the bottom-right corner.
*/
Blockly.Trashcan.prototype.position = function() {
var metrics = this.workspace_.getMetrics();
if (!metrics) {
// There are no metrics available (workspace is probably not visible).
return;
}
if (this.workspace_.RTL) {
this.left_ = this.MARGIN_SIDE_ + Blockly.Scrollbar.scrollbarThickness;
if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
this.left_ += metrics.flyoutWidth;
if (this.workspace_.toolbox_) {
this.left_ += metrics.absoluteLeft;
}
}
} else {
this.left_ = metrics.viewWidth + metrics.absoluteLeft -
this.WIDTH_ - this.MARGIN_SIDE_ - Blockly.Scrollbar.scrollbarThickness;
if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) {
this.left_ -= metrics.flyoutWidth;
}
}
this.top_ = metrics.viewHeight + metrics.absoluteTop -
(this.BODY_HEIGHT_ + this.LID_HEIGHT_) - this.bottom_;
if (metrics.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
this.top_ -= metrics.flyoutHeight;
}
this.svgGroup_.setAttribute('transform',
'translate(' + this.left_ + ',' + this.top_ + ')');
};
/**
* Return the deletion rectangle for this trash can.
* @return {goog.math.Rect} Rectangle in which to delete.
*/
Blockly.Trashcan.prototype.getClientRect = function() {
if (!this.svgGroup_) {
return null;
}
var trashRect = this.svgGroup_.getBoundingClientRect();
var left = trashRect.left + this.SPRITE_LEFT_ - this.MARGIN_HOTSPOT_;
var top = trashRect.top + this.SPRITE_TOP_ - this.MARGIN_HOTSPOT_;
var width = this.WIDTH_ + 2 * this.MARGIN_HOTSPOT_;
var height = this.LID_HEIGHT_ + this.BODY_HEIGHT_ + 2 * this.MARGIN_HOTSPOT_;
return new goog.math.Rect(left, top, width, height);
};
/**
* Flip the lid open or shut.
* @param {boolean} state True if open.
* @private
*/
Blockly.Trashcan.prototype.setOpen_ = function(state) {
if (this.isOpen == state) {
return;
}
goog.Timer.clear(this.lidTask_);
this.isOpen = state;
this.animateLid_();
};
/**
* Rotate the lid open or closed by one step. Then wait and recurse.
* @private
*/
Blockly.Trashcan.prototype.animateLid_ = function() {
this.lidOpen_ += this.isOpen ? 0.2 : -0.2;
this.lidOpen_ = goog.math.clamp(this.lidOpen_, 0, 1);
var lidAngle = this.lidOpen_ * 45;
this.svgLid_.setAttribute('transform', 'rotate(' +
(this.workspace_.RTL ? -lidAngle : lidAngle) + ',' +
(this.workspace_.RTL ? 4 : this.WIDTH_ - 4) + ',' +
(this.LID_HEIGHT_ - 2) + ')');
var opacity = goog.math.lerp(0.4, 0.8, this.lidOpen_);
this.svgGroup_.style.opacity = opacity;
if (this.lidOpen_ > 0 && this.lidOpen_ < 1) {
this.lidTask_ = goog.Timer.callOnce(this.animateLid_, 20, this);
}
};
/**
* Flip the lid shut.
* Called externally after a drag.
*/
Blockly.Trashcan.prototype.close = function() {
this.setOpen_(false);
};
/**
* Inspect the contents of the trash.
*/
Blockly.Trashcan.prototype.click = function() {
var dx = this.workspace_.startScrollX - this.workspace_.scrollX;
var dy = this.workspace_.startScrollY - this.workspace_.scrollY;
if (Math.sqrt(dx * dx + dy * dy) > Blockly.DRAG_RADIUS) {
return;
}
console.log('TODO: Inspect trash.');
};

16
core/trashcan.js.rej Normal file
View File

@@ -0,0 +1,16 @@
***************
*** 218,223 ****
* @private
*/
Blockly.Trashcan.prototype.position_ = function() {
var metrics = this.workspace_.getMetrics();
if (!metrics) {
// There are no metrics available (workspace is probably not visible).
--- 219,225 ----
* @private
*/
Blockly.Trashcan.prototype.position_ = function() {
+ var start = new Date().getTime();
var metrics = this.workspace_.getMetrics();
if (!metrics) {
// There are no metrics available (workspace is probably not visible).

644
core/typeblock.js Normal file
View File

@@ -0,0 +1,644 @@
//Copyright 2013 Massachusetts Institute of Technology. All rights reserved.
/**
* @fileoverview File to handle 'Type Blocking'. When the user starts typing the
* name of a Block in the workspace, a series of suggestions will appear. Upon
* selecting one (enter key), the chosen block will be created in the workspace
* This file needs additional configuration through the inject method.
* @author josmasflores@gmail.com (Jose Dominguez)
*/
'use strict';
goog.provide('Blockly.TypeBlock');
goog.require('Blockly.Xml');
goog.require('goog.events');
goog.require('goog.events.KeyCodes');
goog.require('goog.events.KeyHandler');
goog.require('goog.ui.ac');
goog.require('goog.style');
goog.require('goog.ui.ac.ArrayMatcher');
goog.require('goog.ui.ac.AutoComplete');
goog.require('goog.ui.ac.InputHandler');
goog.require('goog.ui.ac.Renderer');
/**
* Main Type Block function for configuration.
* @param {Object} htmlConfig an object of the type:
{
frame: 'ai_frame',
typeBlockDiv: 'ai_type_block',
inputText: 'ac_input_text'
}
* stating the ids of the attributes to be used in the html enclosing page
* create a new block
*/
Blockly.TypeBlock = function( htmlConfig ){
var frame = htmlConfig['frame'];
Blockly.TypeBlock.typeBlockDiv_ = htmlConfig['typeBlockDiv'];
Blockly.TypeBlock.inputText_ = htmlConfig['inputText'];
Blockly.TypeBlock.docKh_ = new goog.events.KeyHandler(goog.dom.getElement(frame));
Blockly.TypeBlock.inputKh_ = new goog.events.KeyHandler(goog.dom.getElement(Blockly.TypeBlock.inputText_));
Blockly.TypeBlock.handleKey = function(e){
if (e.altKey || e.ctrlKey || e.metaKey || e.keycode === 9) return; // 9 is tab
//We need to duplicate delete handling here from blockly.js
if (e.keyCode === 8 || e.keyCode === 46) {
// Delete or backspace.
// If the panel is showing the panel, just return to allow deletion in the panel itself
if (goog.style.isElementShown(goog.dom.getElement(Blockly.TypeBlock.typeBlockDiv_))) return;
// if id is empty, it is deleting inside a block title
if (e.target.id === '') return;
// only when selected and deletable, actually delete the block
if (Blockly.selected && Blockly.selected.deletable) {
Blockly.hideChaff();
Blockly.selected.dispose(true, true);
}
// Stop the browser from going back to the previous page.
e.preventDefault();
return;
}
if (e.keyCode === 27){ //Dismiss the panel with esc
Blockly.TypeBlock.hide();
return;
}
// A way to know if the user is editing a block or trying to type a new one
if (e.target.id === '') return;
if (goog.style.isElementShown(goog.dom.getElement(Blockly.TypeBlock.typeBlockDiv_))) {
// Enter in the panel makes it select an option
if (e.keyCode === 13) Blockly.TypeBlock.hide();
}
else {
Blockly.TypeBlock.show();
// Can't seem to make Firefox display first character, so keep all browsers from automatically
// displaying the first character and add it manually.
e.preventDefault();
goog.dom.getElement(Blockly.TypeBlock.inputText_).value =
String.fromCharCode(e.charCode != null ? e.charCode : e.keycode);
}
};
goog.events.listen(Blockly.TypeBlock.docKh_, 'key', Blockly.TypeBlock.handleKey);
// Create the auto-complete panel
Blockly.TypeBlock.createAutoComplete_(Blockly.TypeBlock.inputText_);
};
/**
* Div where the type block panel will be rendered
* @private
*/
Blockly.TypeBlock.typeBlockDiv_ = null;
/**
* input text contained in the type block panel used as input
* @private
*/
Blockly.TypeBlock.inputText_ = null;
/**
* Document key handler applied to the frame area, and used to catch keyboard
* events. It is detached when the Type Block panel is shown, and
* re-attached when the Panel is dismissed.
* @private
*/
Blockly.TypeBlock.docKh_ = null;
/**
* Input key handler applied to the Type Block Panel, and used to catch
* keyboard events. It is attached when the Type Block panel is shown, and
* dettached when the Panel is dismissed.
* @private
*/
Blockly.TypeBlock.inputKh_ = null;
/**
* Is the Type Block panel currently showing?
*/
Blockly.TypeBlock.visible = false;
/**
* Mapping of options to show in the auto-complete panel. This maps the
* canonical name of the block, needed to create a new Blockly.Block, with the
* internationalised word or sentence used in typeblocks. Certain blocks do not only need the
* canonical block representation, but also values for dropdowns (name and value)
* - No dropdowns: this.typeblock: [{ translatedName: Blockly.LANG_VAR }]
* - With dropdowns: this.typeblock: [{ translatedName: Blockly.LANG_VAR },
* dropdown: {
* titleName: 'TITLE', value: 'value'
* }]
* - Additional types can be used to mark a block as isProcedure or isGlobalVar. These are only
* used to manage the loading of options in the auto-complete matcher.
* @private
*/
Blockly.TypeBlock.TBOptions_ = {};
/**
* This array contains only the Keys of Blockly.TypeBlock.TBOptions_ to be used
* as options in the autocomplete widget.
* @private
*/
Blockly.TypeBlock.TBOptionsNames_ = [];
/**
* pointer to the automcomplete widget to be able to change its contents when
* the Language tree is modified (additions, renaming, or deletions)
* @private
*/
Blockly.TypeBlock.ac_ = null;
/**
* We keep a listener pointer in case of needing to unlisten to it. We only want
* one listener at a time, and a reload could create a second one, so we
* unlisten first and then listen back
* @private
*/
Blockly.TypeBlock.currentListener_ = null;
/**
* function to hide the autocomplete panel. Also used from hideChaff in
* Blockly.js
*/
Blockly.TypeBlock.hide = function(){
// if (Blockly.TypeBlock.typeBlockDiv_ == null)
// return;
goog.style.showElement(goog.dom.getElement(Blockly.TypeBlock.typeBlockDiv_), false);
goog.events.unlisten(Blockly.TypeBlock.inputKh_, 'key', Blockly.TypeBlock.handleKey);
goog.events.listen(Blockly.TypeBlock.docKh_, 'key', Blockly.TypeBlock.handleKey);
Blockly.TypeBlock.visible = false;
};
/**
* function to show the auto-complete panel to start typing block names
*/
Blockly.TypeBlock.show = function(){
this.lazyLoadOfOptions_();
var panel = goog.dom.getElement(Blockly.TypeBlock.typeBlockDiv_);
goog.style.setStyle(panel, 'top', Blockly.latestClick.y);
goog.style.setStyle(panel, 'left', Blockly.latestClick.x);
goog.style.showElement(panel, true);
goog.dom.getElement(Blockly.TypeBlock.inputText_).focus();
// If the input gets cleaned before adding the handler, all keys are read
// correctly (at times it was missing the first char)
goog.dom.getElement(Blockly.TypeBlock.inputText_).value = '';
goog.events.unlisten(Blockly.TypeBlock.docKh_, 'key', Blockly.TypeBlock.handleKey);
goog.events.listen(Blockly.TypeBlock.inputKh_, 'key', Blockly.TypeBlock.handleKey);
Blockly.TypeBlock.visible = true;
};
/**
* Used as an optimisation trick to avoid reloading components and built-ins unless there is a real
* need to do so. needsReload.components can be set to true when a component changes.
* Defaults to true so that it loads the first time (set to null after loading in lazyLoadOfOptions_())
* @type {{components: boolean}}
*/
Blockly.TypeBlock.needsReload = {
components: true
};
/**
* Lazily loading options because some of them are not available during bootstrapping, and some
* users will never use this functionality, so we avoid having to deal with changes such as handling
* renaming of variables and procedures (leaving it until the moment they are used, if ever).
* @private
*/
Blockly.TypeBlock.lazyLoadOfOptions_ = function () {
// Optimisation to avoid reloading all components and built-in objects unless it is needed.
// needsReload.components is setup when adding/renaming/removing a component in components.js
if (this.needsReload.components){
Blockly.TypeBlock.generateOptions();
this.needsReload.components = null;
}
Blockly.TypeBlock.loadGlobalVariables_();
Blockly.TypeBlock.loadProcedures_();
this.reloadOptionsAfterChanges_();
};
/**
* This function traverses the Language tree and re-creates all the options
* available for type blocking. It's needed in the case of modifying the
* Language tree after its creation (adding or renaming components, for instance).
* It also loads all the built-in blocks.
*
* call 'reloadOptionsAfterChanges_' after calling this. The function lazyLoadOfOptions_ is an
* example of how to call this function.
*/
Blockly.TypeBlock.generateOptions = function() {
var buildListOfOptions = function() {
var listOfOptions = {};
var typeblockArray;
for (var name in Blockly.Blocks) {
var block = Blockly.Blocks[name];
if(block.typeblock){
typeblockArray = block.typeblock;
if(typeof block.typeblock == "function") {
typeblockArray = block.typeblock();
}
createOption(typeblockArray, name);
}
}
function createOption(tb, canonicName){
if (tb){
goog.array.forEach(tb, function(dd){
var dropDownValues = {};
var mutatorAttributes = {};
if (dd.dropDown){
if (dd.dropDown.titleName && dd.dropDown.value){
dropDownValues.titleName = dd.dropDown.titleName;
dropDownValues.value = dd.dropDown.value;
}
else {
throw new Error('TypeBlock not correctly set up for ' + canonicName);
}
}
if(dd.mutatorAttributes) {
mutatorAttributes = dd.mutatorAttributes;
}
listOfOptions[dd.translatedName] = {
canonicName: canonicName,
dropDown: dropDownValues,
mutatorAttributes: mutatorAttributes
};
});
}
}
return listOfOptions;
};
// This is called once on startup, and it will contain all built-in blocks. After that, it can
// be called on demand (for instance in the function lazyLoadOfOptions_)
Blockly.TypeBlock.TBOptions_ = buildListOfOptions();
};
/**
* This function reloads all the latest changes that might have occurred in the language tree or
* the structures containing procedures and variables. It only needs to be called once even if
* different sources are being updated at the same time (call on load proc, load vars, and generate
* options, only needs one call of this function; and example of that is lazyLoadOfOptions_
* @private
*/
Blockly.TypeBlock.reloadOptionsAfterChanges_ = function () {
Blockly.TypeBlock.TBOptionsNames_ = goog.object.getKeys(Blockly.TypeBlock.TBOptions_);
goog.array.sort(Blockly.TypeBlock.TBOptionsNames_);
Blockly.TypeBlock.ac_.matcher_.setRows(Blockly.TypeBlock.TBOptionsNames_);
};
/**
* Loads all procedure names as options for TypeBlocking. It is used lazily from show().
* Call 'reloadOptionsAfterChanges_' after calling this one. The function lazyLoadOfOptions_ is an
* example of how to call this function.
* @private
*/
Blockly.TypeBlock.loadProcedures_ = function(){
// Clean up any previous procedures in the list.
Blockly.TypeBlock.TBOptions_ = goog.object.filter(Blockly.TypeBlock.TBOptions_,
function(opti){ return !opti.isProcedure;});
var procsNoReturn = createTypeBlockForProcedures_(false);
goog.array.forEach(procsNoReturn, function(pro){
Blockly.TypeBlock.TBOptions_[pro.translatedName] = {
canonicName: 'procedures_callnoreturn',
dropDown: pro.dropDown,
isProcedure: true // this attribute is used to clean up before reloading
};
});
var procsReturn = createTypeBlockForProcedures_(true);
goog.array.forEach(procsReturn, function(pro){
Blockly.TypeBlock.TBOptions_[pro.translatedName] = {
canonicName: 'procedures_callreturn',
dropDown: pro.dropDown,
isProcedure: true
};
});
/**
* Procedure names can be collected for both 'with return' and 'no return' varieties from
* getProcedureNames()
* @param {boolean} withReturn indicates if the query us for 'with':true or 'no':false return
* @returns {Array} array of the procedures requested
* @private
*/
function createTypeBlockForProcedures_(withReturn) {
var options = [];
var procNames = Blockly.AIProcedure.getProcedureNames(withReturn);
goog.array.forEach(procNames, function(proc){
options.push(
{
translatedName: Blockly.LANG_PROCEDURES_CALLNORETURN_CALL + ' ' + proc[0],
dropDown: {
titleName: 'PROCNAME',
value: proc[0]
}
}
);
});
return options;
}
};
/**
* Loads all global variable names as options for TypeBlocking. It is used lazily from show().
* Call 'reloadOptionsAfterChanges_' after calling this one. The function lazyLoadOfOptions_ is an
* example of how to call this function.
*/
Blockly.TypeBlock.loadGlobalVariables_ = function () {
//clean up any previous procedures in the list
Blockly.TypeBlock.TBOptions_ = goog.object.filter(Blockly.TypeBlock.TBOptions_,
function(opti){ return !opti.isGlobalvar;});
var globalVarNames = createTypeBlockForVariables_();
goog.array.forEach(globalVarNames, function(varName){
var canonicalN;
if (varName.translatedName.substring(0,3) === 'get')
canonicalN = 'lexical_variable_get';
else
canonicalN = 'lexical_variable_set';
Blockly.TypeBlock.TBOptions_[varName.translatedName] = {
canonicName: canonicalN,
dropDown: varName.dropDown,
isGlobalvar: true
};
});
/**
* Create TypeBlock options for global variables (a setter and a getter for each).
* @returns {Array} array of global var options
*/
function createTypeBlockForVariables_() {
var options = [];
var varNames = Blockly.FieldLexicalVariable.getGlobalNames();
// Make a setter and a getter for each of the names
goog.array.forEach(varNames, function(varName){
options.push(
{
translatedName: 'get global ' + varName,
dropDown: {
titleName: 'VAR',
value: 'global ' + varName
}
}
);
options.push(
{
translatedName: 'set global ' + varName,
dropDown: {
titleName: 'VAR',
value: 'global ' + varName
}
}
);
});
return options;
}
};
/**
* Creates the auto-complete panel, powered by Google Closure's ac widget
* @private
*/
Blockly.TypeBlock.createAutoComplete_ = function(inputText){
Blockly.TypeBlock.TBOptionsNames_ = goog.object.getKeys( Blockly.TypeBlock.TBOptions_ );
goog.array.sort(Blockly.TypeBlock.TBOptionsNames_);
goog.events.unlistenByKey(Blockly.TypeBlock.currentListener_); //if there is a key, unlisten
if (Blockly.TypeBlock.ac_)
Blockly.TypeBlock.ac_.dispose(); //Make sure we only have 1 at a time
// 3 objects needed to create a goog.ui.ac.AutoComplete instance
var matcher = new Blockly.TypeBlock.ac.AIArrayMatcher(Blockly.TypeBlock.TBOptionsNames_, false);
var renderer = new goog.ui.ac.Renderer();
var inputHandler = new goog.ui.ac.InputHandler(null, null, false);
Blockly.TypeBlock.ac_ = new goog.ui.ac.AutoComplete(matcher, renderer, inputHandler);
Blockly.TypeBlock.ac_.setMaxMatches(100); //Renderer has a set height of 294px and a scroll bar.
inputHandler.attachAutoComplete(Blockly.TypeBlock.ac_);
inputHandler.attachInputs(goog.dom.getElement(inputText));
Blockly.TypeBlock.currentListener_ = goog.events.listen(Blockly.TypeBlock.ac_,
goog.ui.ac.AutoComplete.EventType.UPDATE,
function() {
var blockName = goog.dom.getElement(inputText).value;
var blockToCreate = goog.object.get(Blockly.TypeBlock.TBOptions_, blockName);
if (!blockToCreate) {
//If the input passed is not a block, check if it is a number or a pre-populated text block
var numberReg = new RegExp('^-?[0-9]\\d*(\.\\d+)?$', 'g');
var numberMatch = numberReg.exec(blockName);
var textReg = new RegExp('^[\"|\']+', 'g');
var textMatch = textReg.exec(blockName);
if (numberMatch && numberMatch.length > 0){
blockToCreate = {
canonicName: 'math_number',
dropDown: {
titleName: 'NUM',
value: blockName
}
};
}
else if (textMatch && textMatch.length === 1){
blockToCreate = {
canonicName: 'text',
dropDown: {
titleName: 'TEXT',
value: blockName.substring(1)
}
};
}
else
return; // block does not exist: return
}
var blockToCreateName = '';
var block;
if (blockToCreate.dropDown){ //All blocks should have a dropDown property, even if empty
blockToCreateName = blockToCreate.canonicName;
// components have mutator attributes we need to deal with. We can also add these for special blocks
// e.g., this is done for create empty list
if(!goog.object.isEmpty(blockToCreate.mutatorAttributes)) {
//construct xml
var xmlString = '<xml><block type="' + blockToCreateName + '"><mutation ';
for(var attributeName in blockToCreate.mutatorAttributes) {
xmlString += attributeName + '="' + blockToCreate.mutatorAttributes[attributeName] + '" ';
}
xmlString += '>';
xmlString += '</mutation></block></xml>';
var xml = Blockly.Xml.textToDom(xmlString);
block = Blockly.Xml.domToBlock(Blockly.mainWorkspace, xml.firstChild);
} else {
block = new Blockly.Block.obtain(Blockly.mainWorkspace, blockToCreateName);
block.initSvg(); //Need to init the block before doing anything else
if (block.type && (block.type == "procedures_callnoreturn" || block.type == "procedures_callreturn")) {
//Need to make sure Procedure Block inputs are updated
Blockly.FieldProcedure.onChange.call(block.getField_("PROCNAME"), blockToCreate.dropDown.value);
}
}
if (blockToCreate.dropDown.titleName && blockToCreate.dropDown.value){
block.setTitleValue(blockToCreate.dropDown.value, blockToCreate.dropDown.titleName);
// change type checking for split blocks
if(blockToCreate.dropDown.value == 'SPLITATFIRST' || blockToCreate.dropDown.value == 'SPLIT') {
block.getInput("AT").setCheck(Blockly.Blocks.Utilities.YailTypeToBlocklyType("text",Blockly.Blocks.Utilities.INPUT));
} else if(blockToCreate.dropDown.value == 'SPLITATFIRSTOFANY' || blockToCreate.dropDown.value == 'SPLITATANY') {
block.getInput("AT").setCheck(Blockly.Blocks.Utilities.YailTypeToBlocklyType("list",Blockly.Blocks.Utilities.INPUT));
}
}
} else {
throw new Error('Type Block not correctly set up for: ' + blockToCreateName);
}
Blockly.WarningHandler.checkAllBlocksForWarningsAndErrors();
block.render();
var blockSelected = Blockly.selected;
var selectedX, selectedY, selectedXY;
if (blockSelected) {
selectedXY = blockSelected.getRelativeToSurfaceXY();
selectedX = selectedXY.x;
selectedY = selectedXY.y;
Blockly.TypeBlock.connectIfPossible(blockSelected, block);
if(!block.parentBlock_){
//Place it close but a bit out of the way from the one we created.
block.moveBy(Blockly.selected.getRelativeToSurfaceXY().x + 110,
Blockly.selected.getRelativeToSurfaceXY().y + 50);
}
block.select();
}
else {
//calculate positions relative to the view and the latest click
var left = Blockly.mainWorkspace.getMetrics().viewLeft +
Blockly.latestClick.x;
var top = Blockly.mainWorkspace.getMetrics().viewTop +
Blockly.latestClick.y;
block.moveBy(left, top);
block.select();
}
Blockly.TypeBlock.hide();
}
);
};
/**
* Blocks connect in different ways; a block with an outputConnection such as
* a number will connect in one of its parent's input connection (inputLis). .
* A block with no outputConnection could be connected to its parent's next
* connection.
*/
Blockly.TypeBlock.connectIfPossible = function(blockSelected, createdBlock) {
var i = 0,
inputList = blockSelected.inputList,
ilLength = inputList.length;
//If createdBlock has an output connection, we need to:
// connect to parent (eg: connect equals into if)
//else we need to:
// connect its previousConnection to parent (eg: connect if to if)
for (i = 0; i < ilLength; i++){
try {
if (createdBlock.outputConnection != null){
//Check for type validity (connect does not do it)
if ( inputList[i].connection.checkType_(createdBlock.outputConnection) ){
if (!inputList[i].connection.targetConnection){ // is connection empty?
createdBlock.outputConnection.connect(inputList[i].connection);
break;
}
}
}
else {
createdBlock.previousConnection.connect(inputList[i].connection);
}
} catch(e) {
//We can ignore these exceptions; they happen when connecting two blocks
//that should not be connected.
}
}
if (createdBlock.parentBlock_ !== null) return; //Already connected --> return
// Are both blocks statement blocks? If so, connect created block below the selected block
if (blockSelected.outputConnection == null && createdBlock.outputConnection == null) {
createdBlock.previousConnection.connect(blockSelected.nextConnection);
return;
}
// No connections? Try the parent (if it exists)
if (blockSelected.parentBlock_) {
//Is the parent block a statement?
if (blockSelected.parentBlock_.outputConnection == null) {
//Is the created block a statment? If so, connect it below the parent block,
// which is a statement
if(createdBlock.outputConnection == null) {
blockSelected.parentBlock_.nextConnection.connect(createdBlock.previousConnection);
return;
//If it's not, no connections should be made
} else return;
}
else {
//try the parent for other connections
Blockly.TypeBlock.connectIfPossible(blockSelected.parentBlock_, createdBlock);
//recursive call: creates the inner functions again, but should not be much
//overhead; if it is, optimise!
}
}
};
//--------------------------------------
// A custom matcher for the auto-complete widget that can handle numbers as well as the default
// functionality of goog.ui.ac.ArrayMatcher
goog.provide('Blockly.TypeBlock.ac.AIArrayMatcher');
goog.require('goog.iter');
goog.require('goog.string');
/**
* Extension of goog.ui.ac.ArrayMatcher so that it can handle any number typed in.
* @constructor
* @param {Array} rows Dictionary of items to match. Can be objects if they
* have a toString method that returns the value to match against.
* @param {boolean=} opt_noSimilar if true, do not do similarity matches for the
* input token against the dictionary.
* @extends {goog.ui.ac.ArrayMatcher}
*/
Blockly.TypeBlock.ac.AIArrayMatcher = function(rows, opt_noSimilar) {
goog.ui.ac.ArrayMatcher.call(rows, opt_noSimilar);
this.rows_ = rows;
this.useSimilar_ = !opt_noSimilar;
};
goog.inherits(Blockly.TypeBlock.ac.AIArrayMatcher, goog.ui.ac.ArrayMatcher);
/**
* @inheritDoc
*/
Blockly.TypeBlock.ac.AIArrayMatcher.prototype.requestMatchingRows = function(token, maxMatches,
matchHandler, opt_fullString) {
var matches = this.getPrefixMatches(token, maxMatches);
//Because we allow for similar matches, Button.Text will always appear before Text
//So we handle the 'text' case as a special case here
if (token === 'text' || token === 'Text'){
goog.array.remove(matches, 'Text');
goog.array.insertAt(matches, 'Text', 0);
}
// Added code to handle any number typed in the widget (including negatives and decimals)
var reg = new RegExp('^-?[0-9]\\d*(\.\\d+)?$', 'g');
var match = reg.exec(token);
if (match && match.length > 0){
matches.push(token);
}
// Added code to handle default values for text fields (they start with " or ')
var textReg = new RegExp('^[\"|\']+', 'g');
var textMatch = textReg.exec(token);
if (textMatch && textMatch.length === 1){
matches.push(token);
}
if (matches.length === 0 && this.useSimilar_) {
matches = this.getSimilarRows(token, maxMatches);
}
matchHandler(token, matches);
};

273
core/variables.js.orig Normal file
View File

@@ -0,0 +1,273 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Utility functions for handling variables.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Variables');
goog.require('Blockly.Blocks');
goog.require('Blockly.Workspace');
goog.require('goog.string');
/**
* Category to separate variable names from procedures and generated functions.
*/
Blockly.Variables.NAME_TYPE = 'VARIABLE';
/**
* Find all user-created variables that are in use in the workspace.
* For use by generators.
* @param {!Blockly.Block|!Blockly.Workspace} root Root block or workspace.
* @return {!Array.<string>} Array of variable names.
*/
Blockly.Variables.allUsedVariables = function(root) {
var blocks;
if (root instanceof Blockly.Block) {
// Root is Block.
blocks = root.getDescendants();
} else if (root.getAllBlocks) {
// Root is Workspace.
blocks = root.getAllBlocks();
} else {
throw 'Not Block or Workspace: ' + root;
}
var variableHash = Object.create(null);
// Iterate through every block and add each variable to the hash.
for (var x = 0; x < blocks.length; x++) {
var blockVariables = blocks[x].getVars();
if (blockVariables) {
for (var y = 0; y < blockVariables.length; y++) {
var varName = blockVariables[y];
// Variable name may be null if the block is only half-built.
if (varName) {
variableHash[varName.toLowerCase()] = varName;
}
}
}
}
// Flatten the hash into a list.
var variableList = [];
for (var name in variableHash) {
variableList.push(variableHash[name]);
}
return variableList;
};
/**
* Find all variables that the user has created through the workspace or
* toolbox. For use by generators.
* @param {!Blockly.Workspace} root The workspace to inspect.
* @return {!Array.<string>} Array of variable names.
*/
Blockly.Variables.allVariables = function(root) {
if (root instanceof Blockly.Block) {
// Root is Block.
console.warn('Deprecated call to Blockly.Variables.allVariables ' +
'with a block instead of a workspace. You may want ' +
'Blockly.Variables.allUsedVariables');
}
return root.variableList;
};
/**
* Construct the blocks required by the flyout for the variable category.
* @param {!Blockly.Workspace} workspace The workspace contianing variables.
* @return {!Array.<!Element>} Array of XML block elements.
*/
Blockly.Variables.flyoutCategory = function(workspace) {
var variableList = workspace.variableList;
variableList.sort(goog.string.caseInsensitiveCompare);
var xmlList = [];
var button = goog.dom.createDom('button');
button.setAttribute('text', Blockly.Msg.NEW_VARIABLE);
xmlList.push(button);
if (variableList.length > 0) {
if (Blockly.Blocks['variables_set']) {
// <block type="variables_set" gap="20">
// <field name="VAR">item</field>
// </block>
var block = goog.dom.createDom('block');
block.setAttribute('type', 'variables_set');
if (Blockly.Blocks['math_change']) {
block.setAttribute('gap', 8);
} else {
block.setAttribute('gap', 24);
}
var field = goog.dom.createDom('field', null, variableList[0]);
field.setAttribute('name', 'VAR');
block.appendChild(field);
xmlList.push(block);
}
if (Blockly.Blocks['math_change']) {
// <block type="math_change">
// <value name="DELTA">
// <shadow type="math_number">
// <field name="NUM">1</field>
// </shadow>
// </value>
// </block>
var block = goog.dom.createDom('block');
block.setAttribute('type', 'math_change');
if (Blockly.Blocks['variables_get']) {
block.setAttribute('gap', 20);
}
var value = goog.dom.createDom('value');
value.setAttribute('name', 'DELTA');
block.appendChild(value);
var field = goog.dom.createDom('field', null, variableList[0]);
field.setAttribute('name', 'VAR');
block.appendChild(field);
var shadowBlock = goog.dom.createDom('shadow');
shadowBlock.setAttribute('type', 'math_number');
value.appendChild(shadowBlock);
var numberField = goog.dom.createDom('field', null, '1');
numberField.setAttribute('name', 'NUM');
shadowBlock.appendChild(numberField);
xmlList.push(block);
}
for (var i = 0; i < variableList.length; i++) {
if (Blockly.Blocks['variables_get']) {
// <block type="variables_get" gap="8">
// <field name="VAR">item</field>
// </block>
var block = goog.dom.createDom('block');
block.setAttribute('type', 'variables_get');
if (Blockly.Blocks['variables_set']) {
block.setAttribute('gap', 8);
}
var field = goog.dom.createDom('field', null, variableList[i]);
field.setAttribute('name', 'VAR');
block.appendChild(field);
xmlList.push(block);
}
}
}
return xmlList;
};
/**
* Return a new variable name that is not yet being used. This will try to
* generate single letter variable names in the range 'i' to 'z' to start with.
* If no unique name is located it will try 'i' to 'z', 'a' to 'h',
* then 'i2' to 'z2' etc. Skip 'l'.
* @param {!Blockly.Workspace} workspace The workspace to be unique in.
* @return {string} New variable name.
*/
Blockly.Variables.generateUniqueName = function(workspace) {
var variableList = workspace.variableList;
var newName = '';
if (variableList.length) {
var nameSuffix = 1;
var letters = 'ijkmnopqrstuvwxyzabcdefgh'; // No 'l'.
var letterIndex = 0;
var potName = letters.charAt(letterIndex);
while (!newName) {
var inUse = false;
for (var i = 0; i < variableList.length; i++) {
if (variableList[i].toLowerCase() == potName) {
// This potential name is already used.
inUse = true;
break;
}
}
if (inUse) {
// Try the next potential name.
letterIndex++;
if (letterIndex == letters.length) {
// Reached the end of the character sequence so back to 'i'.
// a new suffix.
letterIndex = 0;
nameSuffix++;
}
potName = letters.charAt(letterIndex);
if (nameSuffix > 1) {
potName += nameSuffix;
}
} else {
// We can use the current potential name.
newName = potName;
}
}
} else {
newName = 'i';
}
return newName;
};
/**
* Create a new variable on the given workspace.
* @param {!Blockly.Workspace} workspace The workspace on which to create the
* variable.
* @return {null|undefined|string} An acceptable new variable name, or null if
* change is to be aborted (cancel button), or undefined if an existing
* variable was chosen.
*/
Blockly.Variables.createVariable = function(workspace) {
while (true) {
var text = Blockly.Variables.promptName(Blockly.Msg.NEW_VARIABLE_TITLE, '');
if (text) {
if (workspace.variableIndexOf(text) != -1) {
window.alert(Blockly.Msg.VARIABLE_ALREADY_EXISTS.replace('%1',
text.toLowerCase()));
} else {
workspace.createVariable(text);
break;
}
} else {
text = null;
break;
}
}
return text;
};
/**
* Prompt the user for a new variable name.
* @param {string} promptText The string of the prompt.
* @param {string} defaultText The default value to show in the prompt's field.
* @return {?string} The new variable name, or null if the user picked
* something illegal.
*/
Blockly.Variables.promptName = function(promptText, defaultText) {
var newVar = window.prompt(promptText, defaultText);
// Merge runs of whitespace. Strip leading and trailing whitespace.
// Beyond this, all names are legal.
if (newVar) {
newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
if (newVar == Blockly.Msg.RENAME_VARIABLE ||
newVar == Blockly.Msg.NEW_VARIABLE) {
// Ok, not ALL names are legal...
newVar = null;
}
}
return newVar;
};

17
core/variables.js.rej Normal file
View File

@@ -0,0 +1,17 @@
***************
*** 30,36 ****
// TODO(scr): Fix circular dependencies
// goog.require('Blockly.Block');
- goog.require('Blockly.Toolbox');
goog.require('Blockly.Workspace');
--- 30,36 ----
// TODO(scr): Fix circular dependencies
// goog.require('Blockly.Block');
+ //goog.require('Blockly.Toolbox');
goog.require('Blockly.Workspace');

185
core/warning.js.orig Normal file
View File

@@ -0,0 +1,185 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Object representing a warning.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Warning');
goog.require('Blockly.Bubble');
goog.require('Blockly.Icon');
/**
* Class for a warning.
* @param {!Blockly.Block} block The block associated with this warning.
* @extends {Blockly.Icon}
* @constructor
*/
Blockly.Warning = function(block) {
Blockly.Warning.superClass_.constructor.call(this, block);
this.createIcon();
// The text_ object can contain multiple warnings.
this.text_ = {};
};
goog.inherits(Blockly.Warning, Blockly.Icon);
/**
* Does this icon get hidden when the block is collapsed.
*/
Blockly.Warning.prototype.collapseHidden = false;
/**
* Draw the warning icon.
* @param {!Element} group The icon group.
* @private
*/
Blockly.Warning.prototype.drawIcon_ = function(group) {
// Triangle with rounded corners.
Blockly.createSvgElement('path',
{'class': 'blocklyIconShape',
'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z'},
group);
// Can't use a real '!' text character since different browsers and operating
// systems render it differently.
// Body of exclamation point.
Blockly.createSvgElement('path',
{'class': 'blocklyIconSymbol',
'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z'},
group);
// Dot of exclamation point.
Blockly.createSvgElement('rect',
{'class': 'blocklyIconSymbol',
'x': '7', 'y': '11', 'height': '2', 'width': '2'},
group);
};
/**
* Create the text for the warning's bubble.
* @param {string} text The text to display.
* @return {!SVGTextElement} The top-level node of the text.
* @private
*/
Blockly.Warning.textToDom_ = function(text) {
var paragraph = /** @type {!SVGTextElement} */ (
Blockly.createSvgElement('text',
{'class': 'blocklyText blocklyBubbleText',
'y': Blockly.Bubble.BORDER_WIDTH},
null));
var lines = text.split('\n');
for (var i = 0; i < lines.length; i++) {
var tspanElement = Blockly.createSvgElement('tspan',
{'dy': '1em', 'x': Blockly.Bubble.BORDER_WIDTH}, paragraph);
var textNode = document.createTextNode(lines[i]);
tspanElement.appendChild(textNode);
}
return paragraph;
};
/**
* Show or hide the warning bubble.
* @param {boolean} visible True if the bubble should be visible.
*/
Blockly.Warning.prototype.setVisible = function(visible) {
if (visible == this.isVisible()) {
// No change.
return;
}
Blockly.Events.fire(
new Blockly.Events.Ui(this.block_, 'warningOpen', !visible, visible));
if (visible) {
// Create the bubble to display all warnings.
var paragraph = Blockly.Warning.textToDom_(this.getText());
this.bubble_ = new Blockly.Bubble(
/** @type {!Blockly.WorkspaceSvg} */ (this.block_.workspace),
paragraph, this.block_.svgPath_, this.iconXY_, null, null);
if (this.block_.RTL) {
// Right-align the paragraph.
// This cannot be done until the bubble is rendered on screen.
var maxWidth = paragraph.getBBox().width;
for (var i = 0, textElement; textElement = paragraph.childNodes[i]; i++) {
textElement.setAttribute('text-anchor', 'end');
textElement.setAttribute('x', maxWidth + Blockly.Bubble.BORDER_WIDTH);
}
}
this.updateColour();
// Bump the warning into the right location.
var size = this.bubble_.getBubbleSize();
this.bubble_.setBubbleSize(size.width, size.height);
} else {
// Dispose of the bubble.
this.bubble_.dispose();
this.bubble_ = null;
this.body_ = null;
}
};
/**
* Bring the warning to the top of the stack when clicked on.
* @param {!Event} e Mouse up event.
* @private
*/
Blockly.Warning.prototype.bodyFocus_ = function(e) {
this.bubble_.promote_();
};
/**
* Set this warning's text.
* @param {string} text Warning text (or '' to delete).
* @param {string} id An ID for this text entry to be able to maintain
* multiple warnings.
*/
Blockly.Warning.prototype.setText = function(text, id) {
if (this.text_[id] == text) {
return;
}
if (text) {
this.text_[id] = text;
} else {
delete this.text_[id];
}
if (this.isVisible()) {
this.setVisible(false);
this.setVisible(true);
}
};
/**
* Get this warning's texts.
* @return {string} All texts concatenated into one string.
*/
Blockly.Warning.prototype.getText = function() {
var allWarnings = [];
for (var id in this.text_) {
allWarnings.push(this.text_[id]);
}
return allWarnings.join('\n');
};
/**
* Dispose of this warning.
*/
Blockly.Warning.prototype.dispose = function() {
this.block_.warning = null;
Blockly.Icon.prototype.dispose.call(this);
};

60
core/warning.js.rej Normal file
View File

@@ -0,0 +1,60 @@
***************
*** 82,99 ****
<text class="blocklyIconMark" x="8" y="13">!</text>
*/
var iconShield = Blockly.createSvgElement('path',
- {'class': 'blocklyIconShield',
'd': 'M 2,15 Q -1,15 0.5,12 L 6.5,1.7 Q 8,-1 9.5,1.7 L 15.5,12 ' +
'Q 17,15 14,15 z'},
this.iconGroup_);
this.iconMark_ = Blockly.createSvgElement('text',
- {'class': 'blocklyIconMark',
'x': Blockly.Icon.RADIUS,
'y': 2 * Blockly.Icon.RADIUS - 3}, this.iconGroup_);
this.iconMark_.appendChild(document.createTextNode('!'));
};
/**
* Show or hide the warning bubble.
* @param {boolean} visible True if the bubble should be visible.
*/
--- 82,120 ----
<text class="blocklyIconMark" x="8" y="13">!</text>
*/
var iconShield = Blockly.createSvgElement('path',
+ {'class': 'blocklyWarningIconShield',
'd': 'M 2,15 Q -1,15 0.5,12 L 6.5,1.7 Q 8,-1 9.5,1.7 L 15.5,12 ' +
'Q 17,15 14,15 z'},
this.iconGroup_);
this.iconMark_ = Blockly.createSvgElement('text',
+ {'class': 'blocklyWarningIconMark',
'x': Blockly.Icon.RADIUS,
'y': 2 * Blockly.Icon.RADIUS - 3}, this.iconGroup_);
this.iconMark_.appendChild(document.createTextNode('!'));
};
/**
+ * Create the text for the warning's bubble.
+ * @param {string} text The text to display.
+ * @return {!SVGTextElement} The top-level node of the text.
+ * @private
+ */
+ Blockly.Warning.prototype.textToDom_ = function(text) {
+ var paragraph = /** @type {!SVGTextElement} */ (
+ Blockly.createSvgElement(
+ 'text', {'class': 'blocklyText blocklyErrorWarningText', 'y': Blockly.Bubble.BORDER_WIDTH},
+ null));
+ var lines = text.split('\n');
+ for (var i = 0; i < lines.length; i++) {
+ var tspanElement = Blockly.createSvgElement('tspan',
+ {'dy': '1em', 'x': Blockly.Bubble.BORDER_WIDTH}, paragraph);
+ var textNode = document.createTextNode(lines[i]);
+ tspanElement.appendChild(textNode);
+ }
+ return paragraph;
+ };
+
+ /**
* Show or hide the warning bubble.
* @param {boolean} visible True if the bubble should be visible.
*/

View File

@@ -129,6 +129,10 @@ Blockly.Workspace.prototype.addTopBlock = function(block) {
}
}
}
if (this.warningIndicator) {
this.warningIndicator.dispose();
this.warningIndicator = null;
}
};
/**
@@ -136,6 +140,8 @@ Blockly.Workspace.prototype.addTopBlock = function(block) {
* @param {!Blockly.Block} block Block to remove.
*/
Blockly.Workspace.prototype.removeTopBlock = function(block) {
if (block.workspace == Blockly.mainWorkspace) //Do not reset arrangements for the flyout
Blockly.resetWorkspaceArrangements();
var found = false;
for (var child, i = 0; child = this.topBlocks_[i]; i++) {
if (child == block) {
@@ -156,6 +162,7 @@ Blockly.Workspace.prototype.removeTopBlock = function(block) {
* @return {!Array.<!Blockly.Block>} The top-level block objects.
*/
Blockly.Workspace.prototype.getTopBlocks = function(ordered) {
var start = new Date().getTime(); //*** lyn instrumentation
// Copy the topBlocks_ list.
var blocks = [].concat(this.topBlocks_);
if (ordered && blocks.length > 1) {
@@ -169,6 +176,10 @@ Blockly.Workspace.prototype.getTopBlocks = function(ordered) {
return (aXY.y + offset * aXY.x) - (bXY.y + offset * bXY.x);
});
}
var stop = new Date().getTime(); //*** lyn instrumentation
var timeDiff = stop - start; //*** lyn instrumentation
Blockly.Instrument.stats.getTopBlocksCalls++;
Blockly.Instrument.stats.getTopBlocksTime += timeDiff;
return blocks;
};

501
core/workspace.js.orig Normal file
View File

@@ -0,0 +1,501 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Object representing a workspace.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Workspace');
goog.require('goog.math');
/**
* Class for a workspace. This is a data structure that contains blocks.
* There is no UI, and can be created headlessly.
* @param {Blockly.Options} opt_options Dictionary of options.
* @constructor
*/
Blockly.Workspace = function(opt_options) {
/** @type {string} */
this.id = Blockly.genUid();
Blockly.Workspace.WorkspaceDB_[this.id] = this;
/** @type {!Blockly.Options} */
this.options = opt_options || {};
/** @type {boolean} */
this.RTL = !!this.options.RTL;
/** @type {boolean} */
this.horizontalLayout = !!this.options.horizontalLayout;
/** @type {number} */
this.toolboxPosition = this.options.toolboxPosition;
/**
* @type {!Array.<!Blockly.Block>}
* @private
*/
this.topBlocks_ = [];
/**
* @type {!Array.<!Function>}
* @private
*/
this.listeners_ = [];
/**
* @type {!Array.<!Blockly.Events.Abstract>}
* @private
*/
this.undoStack_ = [];
/**
* @type {!Array.<!Blockly.Events.Abstract>}
* @private
*/
this.redoStack_ = [];
/**
* @type {!Object}
* @private
*/
this.blockDB_ = Object.create(null);
/*
* @type {!Array.<!string>}
* A list of all of the named variables in the workspace, including variables
* that are not currently in use.
*/
this.variableList = [];
};
/**
* Workspaces may be headless.
* @type {boolean} True if visible. False if headless.
*/
Blockly.Workspace.prototype.rendered = false;
/**
* Maximum number of undo events in stack.
* @type {number} 0 to turn off undo, Infinity for unlimited.
*/
Blockly.Workspace.prototype.MAX_UNDO = 1024;
/**
* Dispose of this workspace.
* Unlink from all DOM elements to prevent memory leaks.
*/
Blockly.Workspace.prototype.dispose = function() {
this.listeners_.length = 0;
this.clear();
// Remove from workspace database.
delete Blockly.Workspace.WorkspaceDB_[this.id];
};
/**
* Angle away from the horizontal to sweep for blocks. Order of execution is
* generally top to bottom, but a small angle changes the scan to give a bit of
* a left to right bias (reversed in RTL). Units are in degrees.
* See: http://tvtropes.org/pmwiki/pmwiki.php/Main/DiagonalBilling.
*/
Blockly.Workspace.SCAN_ANGLE = 3;
/**
* Add a block to the list of top blocks.
* @param {!Blockly.Block} block Block to remove.
*/
Blockly.Workspace.prototype.addTopBlock = function(block) {
this.topBlocks_.push(block);
if (this.isFlyout) {
// This is for the (unlikely) case where you have a variable in a block in
// an always-open flyout. It needs to be possible to edit the block in the
// flyout, so the contents of the dropdown need to be correct.
var variables = Blockly.Variables.allUsedVariables(block);
for (var i = 0; i < variables.length; i++) {
if (this.variableList.indexOf(variables[i]) == -1) {
this.variableList.push(variables[i]);
}
}
}
};
/**
* Remove a block from the list of top blocks.
* @param {!Blockly.Block} block Block to remove.
*/
Blockly.Workspace.prototype.removeTopBlock = function(block) {
var found = false;
for (var child, i = 0; child = this.topBlocks_[i]; i++) {
if (child == block) {
this.topBlocks_.splice(i, 1);
found = true;
break;
}
}
if (!found) {
throw 'Block not present in workspace\'s list of top-most blocks.';
}
};
/**
* Finds the top-level blocks and returns them. Blocks are optionally sorted
* by position; top to bottom (with slight LTR or RTL bias).
* @param {boolean} ordered Sort the list if true.
* @return {!Array.<!Blockly.Block>} The top-level block objects.
*/
Blockly.Workspace.prototype.getTopBlocks = function(ordered) {
// Copy the topBlocks_ list.
var blocks = [].concat(this.topBlocks_);
if (ordered && blocks.length > 1) {
var offset = Math.sin(goog.math.toRadians(Blockly.Workspace.SCAN_ANGLE));
if (this.RTL) {
offset *= -1;
}
blocks.sort(function(a, b) {
var aXY = a.getRelativeToSurfaceXY();
var bXY = b.getRelativeToSurfaceXY();
return (aXY.y + offset * aXY.x) - (bXY.y + offset * bXY.x);
});
}
return blocks;
};
/**
* Find all blocks in workspace. No particular order.
* @return {!Array.<!Blockly.Block>} Array of blocks.
*/
Blockly.Workspace.prototype.getAllBlocks = function() {
var blocks = this.getTopBlocks(false);
for (var i = 0; i < blocks.length; i++) {
blocks.push.apply(blocks, blocks[i].getChildren());
}
return blocks;
};
/**
* Dispose of all blocks in workspace.
*/
Blockly.Workspace.prototype.clear = function() {
var existingGroup = Blockly.Events.getGroup();
if (!existingGroup) {
Blockly.Events.setGroup(true);
}
while (this.topBlocks_.length) {
this.topBlocks_[0].dispose();
}
if (!existingGroup) {
Blockly.Events.setGroup(false);
}
this.variableList.length = 0;
};
/**
* Walk the workspace and update the list of variables to only contain ones in
* use on the workspace. Use when loading new workspaces from disk.
* @param {boolean} clearList True if the old variable list should be cleared.
*/
Blockly.Workspace.prototype.updateVariableList = function(clearList) {
// TODO: Sort
if (!this.isFlyout) {
// Update the list in place so that the flyout's references stay correct.
if (clearList) {
this.variableList.length = 0;
}
var allVariables = Blockly.Variables.allUsedVariables(this);
for (var i = 0; i < allVariables.length; i++) {
this.createVariable(allVariables[i]);
}
}
};
/**
* Rename a variable by updating its name in the variable list.
* TODO: #468
* @param {string} oldName Variable to rename.
* @param {string} newName New variable name.
*/
Blockly.Workspace.prototype.renameVariable = function(oldName, newName) {
// Find the old name in the list.
var variableIndex = this.variableIndexOf(oldName);
var newVariableIndex = this.variableIndexOf(newName);
// We might be renaming to an existing name but with different case. If so,
// we will also update all of the blocks using the new name to have the
// correct case.
if (newVariableIndex != -1 &&
this.variableList[newVariableIndex] != newName) {
var oldCase = this.variableList[newVariableIndex];
}
Blockly.Events.setGroup(true);
var blocks = this.getAllBlocks();
// Iterate through every block.
for (var i = 0; i < blocks.length; i++) {
blocks[i].renameVar(oldName, newName);
if (oldCase) {
blocks[i].renameVar(oldCase, newName);
}
}
Blockly.Events.setGroup(false);
if (variableIndex == newVariableIndex ||
variableIndex != -1 && newVariableIndex == -1) {
// Only changing case, or renaming to a completely novel name.
this.variableList[variableIndex] = newName;
} else if (variableIndex != -1 && newVariableIndex != -1) {
// Renaming one existing variable to another existing variable.
this.variableList.splice(variableIndex, 1);
// The case might have changed.
this.variableList[newVariableIndex] = newName;
} else {
this.variableList.push(newName);
console.log('Tried to rename an non-existent variable.');
}
};
/**
* Create a variable with the given name.
* TODO: #468
* @param {string} name The new variable's name.
*/
Blockly.Workspace.prototype.createVariable = function(name) {
var index = this.variableIndexOf(name);
if (index == -1) {
this.variableList.push(name);
}
};
/**
* Find all the uses of a named variable.
* @param {string} name Name of variable.
* @return {!Array.<!Blockly.Block>} Array of block usages.
*/
Blockly.Workspace.prototype.getVariableUses = function(name) {
var uses = [];
var blocks = this.getAllBlocks();
// Iterate through every block and check the name.
for (var i = 0; i < blocks.length; i++) {
var blockVariables = blocks[i].getVars();
if (blockVariables) {
for (var j = 0; j < blockVariables.length; j++) {
var varName = blockVariables[j];
// Variable name may be null if the block is only half-built.
if (varName && Blockly.Names.equals(varName, name)) {
uses.push(blocks[i]);
}
}
}
}
return uses;
};
/**
* Delete a variables and all of its uses from this workspace.
* @param {string} name Name of variable to delete.
*/
Blockly.Workspace.prototype.deleteVariable = function(name) {
var variableIndex = this.variableIndexOf(name);
if (variableIndex != -1) {
var uses = this.getVariableUses(name);
if (uses.length > 1) {
for (var i = 0, block; block = uses[i]; i++) {
if (block.type == 'procedures_defnoreturn' ||
block.type == 'procedures_defreturn') {
var procedureName = block.getFieldValue('NAME');
window.alert(
Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.replace('%1', name).
replace('%2', procedureName));
return;
}
}
var ok = window.confirm(
Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1', uses.length).
replace('%2', name));
if (!ok) {
return;
}
}
Blockly.Events.setGroup(true);
for (var i = 0; i < uses.length; i++) {
uses[i].dispose(true, false);
}
Blockly.Events.setGroup(false);
this.variableList.splice(variableIndex, 1);
}
};
/**
* Check whether a variable exists with the given name. The check is
* case-insensitive.
* @param {string} name The name to check for.
* @return {number} The index of the name in the variable list, or -1 if it is
* not present.
*/
Blockly.Workspace.prototype.variableIndexOf = function(name) {
for (var i = 0, varname; varname = this.variableList[i]; i++) {
if (Blockly.Names.equals(varname, name)) {
return i;
}
}
return -1;
};
/**
* Returns the horizontal offset of the workspace.
* Intended for LTR/RTL compatibility in XML.
* Not relevant for a headless workspace.
* @return {number} Width.
*/
Blockly.Workspace.prototype.getWidth = function() {
return 0;
};
/**
* Obtain a newly created block.
* @param {?string} prototypeName Name of the language object containing
* type-specific functions for this block.
* @param {=string} opt_id Optional ID. Use this ID if provided, otherwise
* create a new id.
* @return {!Blockly.Block} The created block.
*/
Blockly.Workspace.prototype.newBlock = function(prototypeName, opt_id) {
return new Blockly.Block(this, prototypeName, opt_id);
};
/**
* The number of blocks that may be added to the workspace before reaching
* the maxBlocks.
* @return {number} Number of blocks left.
*/
Blockly.Workspace.prototype.remainingCapacity = function() {
if (isNaN(this.options.maxBlocks)) {
return Infinity;
}
return this.options.maxBlocks - this.getAllBlocks().length;
};
/**
* Undo or redo the previous action.
* @param {boolean} redo False if undo, true if redo.
*/
Blockly.Workspace.prototype.undo = function(redo) {
var inputStack = redo ? this.redoStack_ : this.undoStack_;
var outputStack = redo ? this.undoStack_ : this.redoStack_;
var inputEvent = inputStack.pop();
if (!inputEvent) {
return;
}
var events = [inputEvent];
// Do another undo/redo if the next one is of the same group.
while (inputStack.length && inputEvent.group &&
inputEvent.group == inputStack[inputStack.length - 1].group) {
events.push(inputStack.pop());
}
// Push these popped events on the opposite stack.
for (var i = 0, event; event = events[i]; i++) {
outputStack.push(event);
}
events = Blockly.Events.filter(events, redo);
Blockly.Events.recordUndo = false;
for (var i = 0, event; event = events[i]; i++) {
event.run(redo);
}
Blockly.Events.recordUndo = true;
};
/**
* Clear the undo/redo stacks.
*/
Blockly.Workspace.prototype.clearUndo = function() {
this.undoStack_.length = 0;
this.redoStack_.length = 0;
// Stop any events already in the firing queue from being undoable.
Blockly.Events.clearPendingUndo();
};
/**
* When something in this workspace changes, call a function.
* @param {!Function} func Function to call.
* @return {!Function} Function that can be passed to
* removeChangeListener.
*/
Blockly.Workspace.prototype.addChangeListener = function(func) {
this.listeners_.push(func);
return func;
};
/**
* Stop listening for this workspace's changes.
* @param {Function} func Function to stop calling.
*/
Blockly.Workspace.prototype.removeChangeListener = function(func) {
var i = this.listeners_.indexOf(func);
if (i != -1) {
this.listeners_.splice(i, 1);
}
};
/**
* Fire a change event.
* @param {!Blockly.Events.Abstract} event Event to fire.
*/
Blockly.Workspace.prototype.fireChangeListener = function(event) {
if (event.recordUndo) {
this.undoStack_.push(event);
this.redoStack_.length = 0;
if (this.undoStack_.length > this.MAX_UNDO) {
this.undoStack_.unshift();
}
}
for (var i = 0, func; func = this.listeners_[i]; i++) {
func(event);
}
};
/**
* Find the block on this workspace with the specified ID.
* @param {string} id ID of block to find.
* @return {Blockly.Block} The sought after block or null if not found.
*/
Blockly.Workspace.prototype.getBlockById = function(id) {
return this.blockDB_[id] || null;
};
/**
* Database of all workspaces.
* @private
*/
Blockly.Workspace.WorkspaceDB_ = Object.create(null);
/**
* Find the workspace with the specified ID.
* @param {string} id ID of workspace to find.
* @return {Blockly.Workspace} The sought after workspace or null if not found.
*/
Blockly.Workspace.getById = function(id) {
return Blockly.Workspace.WorkspaceDB_[id] || null;
};
// Export symbols that would otherwise be renamed by Closure compiler.
Blockly.Workspace.prototype['clear'] = Blockly.Workspace.prototype.clear;
Blockly.Workspace.prototype['clearUndo'] =
Blockly.Workspace.prototype.clearUndo;
Blockly.Workspace.prototype['addChangeListener'] =
Blockly.Workspace.prototype.addChangeListener;
Blockly.Workspace.prototype['removeChangeListener'] =
Blockly.Workspace.prototype.removeChangeListener;

176
core/workspace.js.rej Normal file
View File

@@ -0,0 +1,176 @@
***************
*** 26,31 ****
goog.provide('Blockly.Workspace');
// TODO(scr): Fix circular dependencies
// goog.require('Blockly.Block');
goog.require('Blockly.ScrollbarPair');
--- 26,33 ----
goog.provide('Blockly.Workspace');
+ goog.require('Blockly.Instrument'); // lyn's instrumentation code
+
// TODO(scr): Fix circular dependencies
// goog.require('Blockly.Block');
goog.require('Blockly.ScrollbarPair');
***************
*** 90,95 ****
Blockly.Workspace.prototype.trashcan = null;
/**
* PID of upcoming firing of a change event. Used to fire only one event
* after multiple changes.
* @type {?number}
--- 92,103 ----
Blockly.Workspace.prototype.trashcan = null;
/**
+ * The workspace's warning indicator.
+ * @type {Blockly.WarningIndicator}
+ */
+ Blockly.Workspace.prototype.warningIndicator = null;
+
+ /**
* PID of upcoming firing of a change event. Used to fire only one event
* after multiple changes.
* @type {?number}
***************
*** 144,149 ****
};
/**
* Get the SVG element that forms the drawing surface.
* @return {!Element} SVG element.
*/
--- 156,175 ----
};
/**
+ * Adds the warning indicator.
+ * @param {!Function} getMetrics A function that returns workspace's metrics.
+ */
+ Blockly.Workspace.prototype.addWarningIndicator = function(getMetrics) {
+ if (Blockly.WarningIndicator && !this.readOnly) {
+ this.warningIndicator = new Blockly.WarningIndicator(getMetrics);
+ var svgWarningIndicator = this.warningIndicator.createDom();
+ this.svgGroup_.insertBefore(svgWarningIndicator, this.svgBlockCanvas_);
+ this.warningIndicator.init();
+ }
+ };
+
+
+ /**
* Get the SVG element that forms the drawing surface.
* @return {!Element} SVG element.
*/
***************
*** 164,169 ****
* @param {!Blockly.Block} block Block to remove.
*/
Blockly.Workspace.prototype.addTopBlock = function(block) {
this.topBlocks_.push(block);
if (Blockly.Realtime.isEnabled() && this == Blockly.mainWorkspace) {
Blockly.Realtime.addTopBlock(block);
--- 190,197 ----
* @param {!Blockly.Block} block Block to remove.
*/
Blockly.Workspace.prototype.addTopBlock = function(block) {
+ if (block.workspace == Blockly.mainWorkspace) //Do not reset arrangements for the flyout
+ Blockly.resetWorkspaceArrangements();
this.topBlocks_.push(block);
if (Blockly.Realtime.isEnabled() && this == Blockly.mainWorkspace) {
Blockly.Realtime.addTopBlock(block);
***************
*** 177,186 ****
* @return {!Array.<!Blockly.Block>} Array of blocks.
*/
Blockly.Workspace.prototype.getAllBlocks = function() {
var blocks = this.getTopBlocks(false);
- for (var x = 0; x < blocks.length; x++) {
blocks.push.apply(blocks, blocks[x].getChildren());
}
return blocks;
};
--- 212,242 ----
* @return {!Array.<!Blockly.Block>} Array of blocks.
*/
Blockly.Workspace.prototype.getAllBlocks = function() {
+ var start = new Date().getTime(); //*** lyn instrumentation
var blocks = this.getTopBlocks(false);
+ Blockly.Instrument.stats.getAllBlocksAllocationCalls++;
+ if (Blockly.Instrument.useLynGetAllBlocksFix) {
+ // Lyn's version of getAllBlocks that avoids quadratic times for large numbers of blocks
+ // by mutating existing blocks array rather than creating new ones
+ for (var x = 0; x < blocks.length; x++) {
+ var children = blocks[x].getChildren();
+ blocks.push.apply(blocks, children);
+ Blockly.Instrument.stats.getAllBlocksAllocationSpace += children.length;
+ }
+ } else {
+ // Neil's version that has quadratic time for large number of blocks
+ // because each call to concat creates *new* array, and so this code does a *lot* of heap
+ // allocation when there are a large number of blocks.
+ for (var x = 0; x < blocks.length; x++) {
blocks.push.apply(blocks, blocks[x].getChildren());
+ Blockly.Instrument.stats.getAllBlocksAllocationCalls++;
+ Blockly.Instrument.stats.getAllBlocksAllocationSpace += blocks.length;
+ }
}
+ var stop = new Date().getTime(); //*** lyn instrumentation
+ var timeDiff = stop - start; //*** lyn instrumentation
+ Blockly.Instrument.stats.getAllBlocksCalls++;
+ Blockly.Instrument.stats.getAllBlocksTime += timeDiff;
return blocks;
};
***************
*** 198,209 ****
* Render all blocks in workspace.
*/
Blockly.Workspace.prototype.render = function() {
- var renderList = this.getAllBlocks();
- for (var x = 0, block; block = renderList[x]; x++) {
- if (!block.getChildren().length) {
- block.render();
}
}
};
/**
--- 254,286 ----
* Render all blocks in workspace.
*/
Blockly.Workspace.prototype.render = function() {
+ var start = new Date().getTime();
+ // [lyn, 04/08/14] Get both top and all blocks for stats
+ var topBlocks = this.getTopBlocks();
+ var allBlocks = this.getAllBlocks();
+ if (Blockly.Instrument.useRenderDown) {
+ for (var t = 0, topBlock; topBlock = topBlocks[t]; t++) {
+ Blockly.Instrument.timer(
+ function () { topBlock.renderDown(); },
+ function (result, timeDiffInner) {
+ Blockly.Instrument.stats.renderDownTime += timeDiffInner;
+ }
+ );
+ }
+ } else {
+ var renderList = allBlocks;
+ for (var x = 0, block; block = renderList[x]; x++) {
+ if (!block.getChildren().length) {
+ block.render();
+ }
}
}
+ var stop = new Date().getTime();
+ var timeDiffOuter = stop - start;
+ Blockly.Instrument.stats.blockCount = allBlocks.length;
+ Blockly.Instrument.stats.topBlockCount = topBlocks.length;
+ Blockly.Instrument.stats.workspaceRenderCalls++;
+ Blockly.Instrument.stats.workspaceRenderTime += timeDiffOuter;
};
/**

566
core/xml.js.orig Normal file
View File

@@ -0,0 +1,566 @@
/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview XML reader and writer.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
goog.provide('Blockly.Xml');
goog.require('goog.asserts');
goog.require('goog.dom');
/**
* Encode a block tree as XML.
* @param {!Blockly.Workspace} workspace The workspace containing blocks.
* @return {!Element} XML document.
*/
Blockly.Xml.workspaceToDom = function(workspace) {
var xml = goog.dom.createDom('xml');
var blocks = workspace.getTopBlocks(true);
for (var i = 0, block; block = blocks[i]; i++) {
xml.appendChild(Blockly.Xml.blockToDomWithXY(block));
}
return xml;
};
/**
* Encode a block subtree as XML with XY coordinates.
* @param {!Blockly.Block} block The root block to encode.
* @return {!Element} Tree of XML elements.
*/
Blockly.Xml.blockToDomWithXY = function(block) {
var width; // Not used in LTR.
if (block.workspace.RTL) {
width = block.workspace.getWidth();
}
var element = Blockly.Xml.blockToDom(block);
var xy = block.getRelativeToSurfaceXY();
element.setAttribute('x',
Math.round(block.workspace.RTL ? width - xy.x : xy.x));
element.setAttribute('y', Math.round(xy.y));
return element;
};
/**
* Encode a block subtree as XML.
* @param {!Blockly.Block} block The root block to encode.
* @return {!Element} Tree of XML elements.
*/
Blockly.Xml.blockToDom = function(block) {
var element = goog.dom.createDom(block.isShadow() ? 'shadow' : 'block');
element.setAttribute('type', block.type);
element.setAttribute('id', block.id);
if (block.mutationToDom) {
// Custom data for an advanced block.
var mutation = block.mutationToDom();
if (mutation && (mutation.hasChildNodes() || mutation.hasAttributes())) {
element.appendChild(mutation);
}
}
function fieldToDom(field) {
if (field.name && field.EDITABLE) {
var container = goog.dom.createDom('field', null, field.getValue());
container.setAttribute('name', field.name);
element.appendChild(container);
}
}
for (var i = 0, input; input = block.inputList[i]; i++) {
for (var j = 0, field; field = input.fieldRow[j]; j++) {
fieldToDom(field);
}
}
var commentText = block.getCommentText();
if (commentText) {
var commentElement = goog.dom.createDom('comment', null, commentText);
if (typeof block.comment == 'object') {
commentElement.setAttribute('pinned', block.comment.isVisible());
var hw = block.comment.getBubbleSize();
commentElement.setAttribute('h', hw.height);
commentElement.setAttribute('w', hw.width);
}
element.appendChild(commentElement);
}
if (block.data) {
var dataElement = goog.dom.createDom('data', null, block.data);
element.appendChild(dataElement);
}
for (var i = 0, input; input = block.inputList[i]; i++) {
var container;
var empty = true;
if (input.type == Blockly.DUMMY_INPUT) {
continue;
} else {
var childBlock = input.connection.targetBlock();
if (input.type == Blockly.INPUT_VALUE) {
container = goog.dom.createDom('value');
} else if (input.type == Blockly.NEXT_STATEMENT) {
container = goog.dom.createDom('statement');
}
var shadow = input.connection.getShadowDom();
if (shadow && (!childBlock || !childBlock.isShadow())) {
container.appendChild(Blockly.Xml.cloneShadow_(shadow));
}
if (childBlock) {
container.appendChild(Blockly.Xml.blockToDom(childBlock));
empty = false;
}
}
container.setAttribute('name', input.name);
if (!empty) {
element.appendChild(container);
}
}
if (block.inputsInlineDefault != block.inputsInline) {
element.setAttribute('inline', block.inputsInline);
}
if (block.isCollapsed()) {
element.setAttribute('collapsed', true);
}
if (block.disabled) {
element.setAttribute('disabled', true);
}
if (!block.isDeletable() && !block.isShadow()) {
element.setAttribute('deletable', false);
}
if (!block.isMovable() && !block.isShadow()) {
element.setAttribute('movable', false);
}
if (!block.isEditable()) {
element.setAttribute('editable', false);
}
var nextBlock = block.getNextBlock();
if (nextBlock) {
var container = goog.dom.createDom('next', null,
Blockly.Xml.blockToDom(nextBlock));
element.appendChild(container);
}
var shadow = block.nextConnection && block.nextConnection.getShadowDom();
if (shadow && (!nextBlock || !nextBlock.isShadow())) {
container.appendChild(Blockly.Xml.cloneShadow_(shadow));
}
return element;
};
/**
* Deeply clone the shadow's DOM so that changes don't back-wash to the block.
* @param {!Element} shadow A tree of XML elements.
* @return {!Element} A tree of XML elements.
* @private
*/
Blockly.Xml.cloneShadow_ = function(shadow) {
shadow = shadow.cloneNode(true);
// Walk the tree looking for whitespace. Don't prune whitespace in a tag.
var node = shadow;
var textNode;
while (node) {
if (node.firstChild) {
node = node.firstChild;
} else {
while (node && !node.nextSibling) {
textNode = node;
node = node.parentNode;
if (textNode.nodeType == 3 && textNode.data.trim() == '' &&
node.firstChild != textNode) {
// Prune whitespace after a tag.
goog.dom.removeNode(textNode);
}
}
if (node) {
textNode = node;
node = node.nextSibling;
if (textNode.nodeType == 3 && textNode.data.trim() == '') {
// Prune whitespace before a tag.
goog.dom.removeNode(textNode);
}
}
}
}
return shadow;
};
/**
* Converts a DOM structure into plain text.
* Currently the text format is fairly ugly: all one line with no whitespace.
* @param {!Element} dom A tree of XML elements.
* @return {string} Text representation.
*/
Blockly.Xml.domToText = function(dom) {
var oSerializer = new XMLSerializer();
return oSerializer.serializeToString(dom);
};
/**
* Converts a DOM structure into properly indented text.
* @param {!Element} dom A tree of XML elements.
* @return {string} Text representation.
*/
Blockly.Xml.domToPrettyText = function(dom) {
// This function is not guaranteed to be correct for all XML.
// But it handles the XML that Blockly generates.
var blob = Blockly.Xml.domToText(dom);
// Place every open and close tag on its own line.
var lines = blob.split('<');
// Indent every line.
var indent = '';
for (var i = 1; i < lines.length; i++) {
var line = lines[i];
if (line[0] == '/') {
indent = indent.substring(2);
}
lines[i] = indent + '<' + line;
if (line[0] != '/' && line.slice(-2) != '/>') {
indent += ' ';
}
}
// Pull simple tags back together.
// E.g. <foo></foo>
var text = lines.join('\n');
text = text.replace(/(<(\w+)\b[^>]*>[^\n]*)\n *<\/\2>/g, '$1</$2>');
// Trim leading blank line.
return text.replace(/^\n/, '');
};
/**
* Converts plain text into a DOM structure.
* Throws an error if XML doesn't parse.
* @param {string} text Text representation.
* @return {!Element} A tree of XML elements.
*/
Blockly.Xml.textToDom = function(text) {
var oParser = new DOMParser();
var dom = oParser.parseFromString(text, 'text/xml');
// The DOM should have one and only one top-level node, an XML tag.
if (!dom || !dom.firstChild ||
dom.firstChild.nodeName.toLowerCase() != 'xml' ||
dom.firstChild !== dom.lastChild) {
// Whatever we got back from the parser is not XML.
goog.asserts.fail('Blockly.Xml.textToDom did not obtain a valid XML tree.');
}
return dom.firstChild;
};
/**
* Decode an XML DOM and create blocks on the workspace.
* @param {!Element} xml XML DOM.
* @param {!Blockly.Workspace} workspace The workspace.
*/
Blockly.Xml.domToWorkspace = function(xml, workspace) {
if (xml instanceof Blockly.Workspace) {
var swap = xml;
xml = workspace;
workspace = swap;
console.warn('Deprecated call to Blockly.Xml.domToWorkspace, ' +
'swap the arguments.');
}
var width; // Not used in LTR.
if (workspace.RTL) {
width = workspace.getWidth();
}
Blockly.Field.startCache();
// Safari 7.1.3 is known to provide node lists with extra references to
// children beyond the lists' length. Trust the length, do not use the
// looping pattern of checking the index for an object.
var childCount = xml.childNodes.length;
var existingGroup = Blockly.Events.getGroup();
if (!existingGroup) {
Blockly.Events.setGroup(true);
}
for (var i = 0; i < childCount; i++) {
var xmlChild = xml.childNodes[i];
var name = xmlChild.nodeName.toLowerCase();
if (name == 'block' ||
(name == 'shadow' && !Blockly.Events.recordUndo)) {
// Allow top-level shadow blocks if recordUndo is disabled since
// that means an undo is in progress. Such a block is expected
// to be moved to a nested destination in the next operation.
var block = Blockly.Xml.domToBlock(xmlChild, workspace);
var blockX = parseInt(xmlChild.getAttribute('x'), 10);
var blockY = parseInt(xmlChild.getAttribute('y'), 10);
if (!isNaN(blockX) && !isNaN(blockY)) {
block.moveBy(workspace.RTL ? width - blockX : blockX, blockY);
}
} else if (name == 'shadow') {
goog.asserts.fail('Shadow block cannot be a top-level block.');
}
}
if (!existingGroup) {
Blockly.Events.setGroup(false);
}
Blockly.Field.stopCache();
workspace.updateVariableList(false);
};
/**
* Decode an XML block tag and create a block (and possibly sub blocks) on the
* workspace.
* @param {!Element} xmlBlock XML block element.
* @param {!Blockly.Workspace} workspace The workspace.
* @return {!Blockly.Block} The root block created.
*/
Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
if (xmlBlock instanceof Blockly.Workspace) {
var swap = xmlBlock;
xmlBlock = workspace;
workspace = swap;
console.warn('Deprecated call to Blockly.Xml.domToBlock, ' +
'swap the arguments.');
}
// Create top-level block.
Blockly.Events.disable();
try {
var topBlock = Blockly.Xml.domToBlockHeadless_(xmlBlock, workspace);
if (workspace.rendered) {
// Hide connections to speed up assembly.
topBlock.setConnectionsHidden(true);
// Generate list of all blocks.
var blocks = topBlock.getDescendants();
// Render each block.
for (var i = blocks.length - 1; i >= 0; i--) {
blocks[i].initSvg();
}
for (var i = blocks.length - 1; i >= 0; i--) {
blocks[i].render(false);
}
// Populating the connection database may be defered until after the
// blocks have rendered.
setTimeout(function() {
if (topBlock.workspace) { // Check that the block hasn't been deleted.
topBlock.setConnectionsHidden(false);
}
}, 1);
topBlock.updateDisabled();
// Allow the scrollbars to resize and move based on the new contents.
// TODO(@picklesrus): #387. Remove when domToBlock avoids resizing.
workspace.resizeContents();
}
} finally {
Blockly.Events.enable();
}
if (Blockly.Events.isEnabled()) {
Blockly.Events.fire(new Blockly.Events.Create(topBlock));
}
return topBlock;
};
/**
* Decode an XML block tag and create a block (and possibly sub blocks) on the
* workspace.
* @param {!Element} xmlBlock XML block element.
* @param {!Blockly.Workspace} workspace The workspace.
* @return {!Blockly.Block} The root block created.
* @private
*/
Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
var block = null;
var prototypeName = xmlBlock.getAttribute('type');
goog.asserts.assert(prototypeName, 'Block type unspecified: %s',
xmlBlock.outerHTML);
var id = xmlBlock.getAttribute('id');
block = workspace.newBlock(prototypeName, id);
var blockChild = null;
for (var i = 0, xmlChild; xmlChild = xmlBlock.childNodes[i]; i++) {
if (xmlChild.nodeType == 3) {
// Ignore any text at the <block> level. It's all whitespace anyway.
continue;
}
var input;
// Find any enclosed blocks or shadows in this tag.
var childBlockNode = null;
var childShadowNode = null;
for (var j = 0, grandchildNode; grandchildNode = xmlChild.childNodes[j];
j++) {
if (grandchildNode.nodeType == 1) {
if (grandchildNode.nodeName.toLowerCase() == 'block') {
childBlockNode = grandchildNode;
} else if (grandchildNode.nodeName.toLowerCase() == 'shadow') {
childShadowNode = grandchildNode;
}
}
}
// Use the shadow block if there is no child block.
if (!childBlockNode && childShadowNode) {
childBlockNode = childShadowNode;
}
var name = xmlChild.getAttribute('name');
switch (xmlChild.nodeName.toLowerCase()) {
case 'mutation':
// Custom data for an advanced block.
if (block.domToMutation) {
block.domToMutation(xmlChild);
if (block.initSvg) {
// Mutation may have added some elements that need initalizing.
block.initSvg();
}
}
break;
case 'comment':
block.setCommentText(xmlChild.textContent);
var visible = xmlChild.getAttribute('pinned');
if (visible && !block.isInFlyout) {
// Give the renderer a millisecond to render and position the block
// before positioning the comment bubble.
setTimeout(function() {
if (block.comment && block.comment.setVisible) {
block.comment.setVisible(visible == 'true');
}
}, 1);
}
var bubbleW = parseInt(xmlChild.getAttribute('w'), 10);
var bubbleH = parseInt(xmlChild.getAttribute('h'), 10);
if (!isNaN(bubbleW) && !isNaN(bubbleH) &&
block.comment && block.comment.setVisible) {
block.comment.setBubbleSize(bubbleW, bubbleH);
}
break;
case 'data':
block.data = xmlChild.textContent;
break;
case 'title':
// Titles were renamed to field in December 2013.
// Fall through.
case 'field':
var field = block.getField(name);
if (!field) {
console.warn('Ignoring non-existent field ' + name + ' in block ' +
prototypeName);
break;
}
field.setValue(xmlChild.textContent);
break;
case 'value':
case 'statement':
input = block.getInput(name);
if (!input) {
console.warn('Ignoring non-existent input ' + name + ' in block ' +
prototypeName);
break;
}
if (childShadowNode) {
input.connection.setShadowDom(childShadowNode);
}
if (childBlockNode) {
blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode,
workspace);
if (blockChild.outputConnection) {
input.connection.connect(blockChild.outputConnection);
} else if (blockChild.previousConnection) {
input.connection.connect(blockChild.previousConnection);
} else {
goog.asserts.fail(
'Child block does not have output or previous statement.');
}
}
break;
case 'next':
if (childShadowNode && block.nextConnection) {
block.nextConnection.setShadowDom(childShadowNode);
}
if (childBlockNode) {
goog.asserts.assert(block.nextConnection,
'Next statement does not exist.');
// If there is more than one XML 'next' tag.
goog.asserts.assert(!block.nextConnection.isConnected(),
'Next statement is already connected.');
blockChild = Blockly.Xml.domToBlockHeadless_(childBlockNode,
workspace);
goog.asserts.assert(blockChild.previousConnection,
'Next block does not have previous statement.');
block.nextConnection.connect(blockChild.previousConnection);
}
break;
default:
// Unknown tag; ignore. Same principle as HTML parsers.
console.warn('Ignoring unknown tag: ' + xmlChild.nodeName);
}
}
var inline = xmlBlock.getAttribute('inline');
if (inline) {
block.setInputsInline(inline == 'true');
}
var disabled = xmlBlock.getAttribute('disabled');
if (disabled) {
block.setDisabled(disabled == 'true');
}
var deletable = xmlBlock.getAttribute('deletable');
if (deletable) {
block.setDeletable(deletable == 'true');
}
var movable = xmlBlock.getAttribute('movable');
if (movable) {
block.setMovable(movable == 'true');
}
var editable = xmlBlock.getAttribute('editable');
if (editable) {
block.setEditable(editable == 'true');
}
var collapsed = xmlBlock.getAttribute('collapsed');
if (collapsed) {
block.setCollapsed(collapsed == 'true');
}
if (xmlBlock.nodeName.toLowerCase() == 'shadow') {
// Ensure all children are also shadows.
var children = block.getChildren();
for (var i = 0, child; child = children[i]; i++) {
goog.asserts.assert(child.isShadow(),
'Shadow block not allowed non-shadow child.');
}
block.setShadow(true);
}
return block;
};
/**
* Remove any 'next' block (statements in a stack).
* @param {!Element} xmlBlock XML block element.
*/
Blockly.Xml.deleteNext = function(xmlBlock) {
for (var i = 0, child; child = xmlBlock.childNodes[i]; i++) {
if (child.nodeName.toLowerCase() == 'next') {
xmlBlock.removeChild(child);
break;
}
}
};
// Export symbols that would otherwise be renamed by Closure compiler.
if (!goog.global['Blockly']) {
goog.global['Blockly'] = {};
}
if (!goog.global['Blockly']['Xml']) {
goog.global['Blockly']['Xml'] = {};
}
goog.global['Blockly']['Xml']['domToText'] = Blockly.Xml.domToText;
goog.global['Blockly']['Xml']['domToWorkspace'] = Blockly.Xml.domToWorkspace;
goog.global['Blockly']['Xml']['textToDom'] = Blockly.Xml.textToDom;
goog.global['Blockly']['Xml']['workspaceToDom'] = Blockly.Xml.workspaceToDom;

251
core/xml.js.rej Normal file
View File

@@ -0,0 +1,251 @@
***************
*** 26,31 ****
goog.provide('Blockly.Xml');
// TODO(scr): Fix circular dependencies
// goog.require('Blockly.Block');
--- 26,33 ----
goog.provide('Blockly.Xml');
+ goog.require('Blockly.Instrument'); // lyn's instrumentation code
+
// TODO(scr): Fix circular dependencies
// goog.require('Blockly.Block');
***************
*** 212,234 ****
* @param {!Element} xml XML DOM.
*/
Blockly.Xml.domToWorkspace = function(workspace, xml) {
- var width
- if (Blockly.RTL) {
- width = workspace.getMetrics().viewWidth;
- }
- for (var x = 0, xmlChild; xmlChild = xml.childNodes[x]; x++) {
- if (xmlChild.nodeName.toLowerCase() == 'block') {
- var block = Blockly.Xml.domToBlock(workspace, xmlChild);
- var blockX = parseInt(xmlChild.getAttribute('x'), 10);
- var blockY = parseInt(xmlChild.getAttribute('y'), 10);
- if (!isNaN(blockX) && !isNaN(blockY)) {
- block.moveBy(Blockly.RTL ? width - blockX : blockX, blockY);
}
- }
- }
};
/**
* Decode an XML block tag and create a block (and possibly sub blocks) on the
* workspace.
* @param {!Blockly.Workspace} workspace The workspace.
--- 214,245 ----
* @param {!Element} xml XML DOM.
*/
Blockly.Xml.domToWorkspace = function(workspace, xml) {
+ Blockly.Instrument.timer (
+ function () {
+ var width; // Not used in LTR.
+ if (Blockly.RTL) {
+ width = workspace.getMetrics().viewWidth;
+ }
+ for (var x = 0, xmlChild; xmlChild = xml.childNodes[x]; x++) {
+ if (xmlChild.nodeName.toLowerCase() == 'block') {
+ var block = Blockly.Xml.domToBlock(workspace, xmlChild);
+ var blockX = parseInt(xmlChild.getAttribute('x'), 10);
+ var blockY = parseInt(xmlChild.getAttribute('y'), 10);
+ if (!isNaN(blockX) && !isNaN(blockY)) {
+ block.moveBy(Blockly.RTL ? width - blockX : blockX, blockY);
+ }
+ }
+ }
+ },
+ function (result, timeDiff) {
+ Blockly.Instrument.stats.domToWorkspaceCalls++;
+ Blockly.Instrument.stats.domToWorkspaceTime = timeDiff;
}
+ );
};
/**
+ * Wrapper for domToBlockInner that renders blocks.
* Decode an XML block tag and create a block (and possibly sub blocks) on the
* workspace.
* @param {!Blockly.Workspace} workspace The workspace.
***************
*** 239,244 ****
* @private
*/
Blockly.Xml.domToBlock = function(workspace, xmlBlock, opt_reuseBlock) {
var block = null;
var prototypeName = xmlBlock.getAttribute('type');
if (!prototypeName) {
--- 250,317 ----
* @private
*/
Blockly.Xml.domToBlock = function(workspace, xmlBlock, opt_reuseBlock) {
+ return Blockly.Instrument.timer (
+ function () {
+ var block = Blockly.Xml.domToBlockInner(workspace, xmlBlock, opt_reuseBlock);
+ Blockly.Instrument.timer (
+ function () {
+ if (Blockly.Instrument.useRenderDown) {
+ block.renderDown();
+ }
+ },
+ function (result, timeDiffInner) {
+ if (Blockly.Instrument.useRenderDown) {
+ Blockly.Instrument.stats.renderDownTime += timeDiffInner;
+ }
+ }
+ );
+ // [lyn, 07/03/2014] Special case to handle renaming of event parameters in i8n
+ if (block && block.type == "component_event") {
+ // Create a dictionary mapping default event parameter names appearing in body
+ // to their possibly translated names in some source language
+ var eventParamDict = Blockly.LexicalVariable.eventParameterDict(block);
+ var sourceEventParams = []; // Event parameter names in source language
+ var targetEventParams = []; // Event parameter names in target language
+ for (var key in eventParamDict) {
+ var sourceEventParam = eventParamDict[key];
+ var targetEventParam = window.parent.BlocklyPanel_getLocalizedParameterName(key);
+ if (sourceEventParam != targetEventParam) { // Only add to translation if they're different
+ sourceEventParams.push(sourceEventParam);
+ targetEventParams.push(targetEventParam);
+ }
+ }
+ if (sourceEventParams.length > 0) { // Do we need to translated some source event parameters?
+ var childBlocks = block.getChildren(); // should be at most one body block
+ for (var j= 0, childBlock; childBlock = childBlocks[j]; j++) {
+ var freeSubstitution = new Blockly.Substitution(sourceEventParams, targetEventParams);
+ // renameFree does the translation.
+ Blockly.LexicalVariable.renameFree(childBlock, freeSubstitution);
+ }
+ }
+ }
+ return block;
+ },
+ function (block, timeDiffOuter) {
+ Blockly.Instrument.stats.domToBlockCalls++;
+ Blockly.Instrument.stats.domToBlockTime += timeDiffOuter;
+ return block;
+ }
+ );
+ }
+
+ /**
+ * Version of domToBlock that does not render blocks.
+ * Decode an XML block tag and create a block (and possibly sub blocks) on the
+ * workspace.
+ * @param {!Blockly.Workspace} workspace The workspace.
+ * @param {!Element} xmlBlock XML block element.
+ * @param {boolean=} opt_reuseBlock Optional arg indicating whether to
+ * reinitialize an existing block.
+ * @return {!Blockly.Block} The root block created.
+ * @private
+ */
+ Blockly.Xml.domToBlockInner = function(workspace, xmlBlock, opt_reuseBlock) {
+ Blockly.Instrument.stats.domToBlockInnerCalls++;
var block = null;
var prototypeName = xmlBlock.getAttribute('type');
if (!prototypeName) {
***************
*** 342,348 ****
}
if (firstRealGrandchild &&
firstRealGrandchild.nodeName.toLowerCase() == 'block') {
- blockChild = Blockly.Xml.domToBlock(workspace, firstRealGrandchild,
opt_reuseBlock);
if (blockChild.outputConnection) {
input.connection.connect(blockChild.outputConnection);
--- 415,421 ----
}
if (firstRealGrandchild &&
firstRealGrandchild.nodeName.toLowerCase() == 'block') {
+ blockChild = Blockly.Xml.domToBlockInner(workspace, firstRealGrandchild,
opt_reuseBlock);
if (blockChild.outputConnection) {
input.connection.connect(blockChild.outputConnection);
***************
*** 362,368 ****
// This could happen if there is more than one XML 'next' tag.
throw 'Next statement is already connected.';
}
- blockChild = Blockly.Xml.domToBlock(workspace, firstRealGrandchild,
opt_reuseBlock);
if (!blockChild.previousConnection) {
throw 'Next block does not have previous statement.';
--- 435,441 ----
// This could happen if there is more than one XML 'next' tag.
throw 'Next statement is already connected.';
}
+ blockChild = Blockly.Xml.domToBlockInner(workspace, firstRealGrandchild,
opt_reuseBlock);
if (!blockChild.previousConnection) {
throw 'Next block does not have previous statement.';
***************
*** 375,392 ****
}
}
var collapsed = xmlBlock.getAttribute('collapsed');
if (collapsed) {
block.setCollapsed(collapsed == 'true');
}
- var next = block.getNextBlock();
- if (next) {
- // Next block in a stack needs to square off its corners.
- // Rendering a child will render its parent.
- next.render();
- } else {
- block.render();
- }
return block;
};
--- 448,491 ----
}
}
+ // [lyn, 10/25/13] collapsing and friends need to be done *after* connections are made to sublocks.
+ // Otherwise, the subblocks won't be properly processed by block.setCollapsed and friends.
+ var inline = xmlBlock.getAttribute('inline');
+ if (inline) {
+ block.setInputsInline(inline == 'true');
+ }
+ var disabled = xmlBlock.getAttribute('disabled');
+ if (disabled) {
+ block.setDisabled(disabled == 'true');
+ }
+ var deletable = xmlBlock.getAttribute('deletable');
+ if (deletable) {
+ block.setDeletable(deletable == 'true');
+ }
+ var movable = xmlBlock.getAttribute('movable');
+ if (movable) {
+ block.setMovable(movable == 'true');
+ }
+ var editable = xmlBlock.getAttribute('editable');
+ if (editable) {
+ block.setEditable(editable == 'true');
+ }
+
+ if (! Blockly.Instrument.useRenderDown) {
+ // Neil's original rendering code
+ var next = block.getNextBlock();
+ if (next) {
+ // Next block in a stack needs to square off its corners.
+ // Rendering a child will render its parent.
+ next.render();
+ } else {
+ block.render();
+ }
+ }
var collapsed = xmlBlock.getAttribute('collapsed');
if (collapsed) {
block.setCollapsed(collapsed == 'true');
}
return block;
};

View File

@@ -3,7 +3,7 @@
# Code shared by translation conversion scripts.
#
# Copyright 2013 Google Inc.
# https://developers.google.com/blockly/
# https://blockly.googlecode.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -61,7 +61,7 @@ def read_json_file(filename):
return defs
except ValueError, e:
print('Error reading ' + filename)
raise InputError(filename, str(e))
raise InputError(file, str(e))
def _create_qqq_file(output_dir):

View File

@@ -3,7 +3,7 @@
# Generate .js files defining Blockly core and language messages.
#
# Copyright 2013 Google Inc.
# https://developers.google.com/blockly/
# https://blockly.googlecode.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -28,14 +28,6 @@ from common import read_json_file
_NEWLINE_PATTERN = re.compile('[\n\r]')
def string_is_ascii(s):
try:
s.decode('ascii')
return True
except UnicodeEncodeError:
return False
def main():
"""Generate .js files defining Blockly core and language messages."""
@@ -84,17 +76,10 @@ def main():
target_lang = filename[:filename.index('.')]
if target_lang not in ('qqq', 'keys', 'synonyms'):
target_defs = read_json_file(os.path.join(os.curdir, arg_file))
# Verify that keys are 'ascii'
bad_keys = [key for key in target_defs if not string_is_ascii(key)]
if bad_keys:
print(u'These keys in {0} contain non ascii characters: {1}'.format(
filename, ', '.join(bad_keys)))
# If there's a '\n' or '\r', remove it and print a warning.
for key, value in target_defs.items():
if _NEWLINE_PATTERN.search(value):
print(u'WARNING: definition of {0} in {1} contained '
print('WARNING: definition of {0} in {1} contained '
'a newline character.'.
format(key, arg_file))
target_defs[key] = _NEWLINE_PATTERN.sub(' ', value)
@@ -133,10 +118,10 @@ goog.require('Blockly.Msg');
synonym_keys = [key for key in target_defs if key in synonym_defs]
if not args.quiet:
if extra_keys:
print(u'These extra keys appeared in {0}: {1}'.format(
print('These extra keys appeared in {0}: {1}'.format(
filename, ', '.join(extra_keys)))
if synonym_keys:
print(u'These synonym keys appeared in {0}: {1}'.format(
print('These synonym keys appeared in {0}: {1}'.format(
filename, ', '.join(synonym_keys)))
outfile.write(synonym_text)

View File

@@ -7,7 +7,7 @@
# output.
#
# Copyright 2013 Google Inc.
# https://developers.google.com/blockly/
# https://blockly.googlecode.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -3,7 +3,7 @@
# Gives the translation status of the specified apps and languages.
#
# Copyright 2013 Google Inc.
# https://developers.google.com/blockly/
# https://blockly.googlecode.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -3,7 +3,7 @@
# Converts .json files into .js files for use within Blockly apps.
#
# Copyright 2013 Google Inc.
# https://developers.google.com/blockly/
# https://blockly.googlecode.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

247
i18n/status.py Normal file
View File

@@ -0,0 +1,247 @@
#!/usr/bin/python
# Gives the translation status of the specified apps and languages.
#
# Copyright 2013 Google Inc.
# https://blockly.googlecode.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Produce a table showing the translation status of each app by language.
@author Ellen Spertus (ellen.spertus@gmail.com)
"""
import argparse
import os
from common import read_json_file
# Bogus language name representing all messages defined.
TOTAL = 'qqq'
# List of key prefixes, which are app names, except for 'Apps', which
# has common messages. It is included here for convenience.
APPS = ['Apps', 'Code', 'Graph', 'Maze', 'Plane', 'Puzzle', 'Turtle']
def get_prefix(s):
"""Gets the portion of a string before the first period.
Args:
s: A string.
Returns:
The portion of the string before the first period, or the entire
string if it does not contain a period.
"""
return s.split('.')[0]
def get_prefix_count(prefix, arr):
"""Counts how many strings in the array start with the prefix.
Args:
prefix: The prefix string.
arr: An array of strings.
Returns:
The number of strings in arr starting with prefix.
"""
# This code was chosen for its elegance not its efficiency.
return len([elt for elt in arr if elt.startswith(prefix)])
def output_as_html(messages, apps, verbose):
"""Outputs the given prefix counts and percentages as HTML.
Specifically, a sortable HTML table is produced, where the app names
are column headers, and one language is output per row. Entries
are color-coded based on the percent completeness.
Args:
messages: A dictionary of dictionaries, where the outer keys are language
codes used by translatewiki (generally, ISO 639 language codes) or
the string TOTAL, used to indicate the total set of messages. The
inner dictionary makes message keys to values in that language.
apps: Apps to consider.
verbose: Whether to list missing keys.
"""
def generate_language_url(lang):
return 'https://translatewiki.net/wiki/Special:SupportedLanguages#' + lang
def generate_number_as_percent(num, total, tag):
percent = num * 100 / total
if percent == 100:
color = 'green'
elif percent >= 90:
color = 'orange'
elif percent >= 60:
color = 'black'
else:
color = 'gray'
s = '<font color={0}>{1} ({2}%)</font>'.format(color, num, percent)
if verbose and percent < 100:
return '<a href="#{0}">{1}'.format(tag, s)
else:
return s
print('<head><title>Blockly app translation status</title></head><body>')
print("<SCRIPT LANGUAGE='JavaScript1.2' SRC='https://neil.fraser.name/"
"software/tablesort/tablesort-min.js'></SCRIPT>")
print('<table cellspacing=5><thead><tr>')
print('<th class=nocase>Language</th><th class=num>' +
'</th><th class=num>'.join(apps) + '</th></tr></thead><tbody>')
for lang in messages:
if lang != TOTAL:
print('<tr><td><a href="{1}">{0}</a></td>'.format(
lang, generate_language_url(lang)))
for app in apps:
print '<td>'
print(generate_number_as_percent(
get_prefix_count(app, messages[lang]),
get_prefix_count(app, messages[TOTAL]),
(lang + app)))
print '</td>'
print('</tr>')
print('</tbody><tfoot><tr><td>ALL</td><td>')
print('</td><td>'.join([str(get_prefix_count(app, TOTAL)) for app in apps]))
print('</td></tr></tfoot></table>')
if verbose:
for lang in messages:
if lang != TOTAL:
for app in apps:
if (get_prefix_count(app, messages[lang]) <
get_prefix_count(app, messages[TOTAL])):
print('<div id={0}{1}><strong>{1} (<a href="{2}">{0}</a>)'.
format(lang, app, generate_language_url(lang)))
print('</strong> missing: ')
print(', '.join(
[key for key in messages[TOTAL] if
key.startswith(app) and key not in messages[lang]]))
print('<br><br></div>')
print('</body>')
def output_as_text(messages, apps, verbose):
"""Outputs the given prefix counts and percentages as text.
Args:
messages: A dictionary of dictionaries, where the outer keys are language
codes used by translatewiki (generally, ISO 639 language codes) or
the string TOTAL, used to indicate the total set of messages. The
inner dictionary makes message keys to values in that language.
apps: Apps to consider.
verbose: Whether to list missing keys.
"""
def generate_number_as_percent(num, total):
return '{0} ({1}%)'.format(num, num * 100 / total)
MAX_WIDTH = len('999 (100%)') + 1
FIELD_STRING = '{0: <' + str(MAX_WIDTH) + '}'
print(FIELD_STRING.format('Language') + ''.join(
[FIELD_STRING.format(app) for app in apps]))
print(('-' * (MAX_WIDTH - 1) + ' ') * (len(apps) + 1))
for lang in messages:
if lang != TOTAL:
print(FIELD_STRING.format(lang) +
''.join([FIELD_STRING.format(generate_number_as_percent(
get_prefix_count(app, messages[lang]),
get_prefix_count(app, messages[TOTAL])))
for app in apps]))
print(FIELD_STRING.format(TOTAL) +
''.join(
[FIELD_STRING.format(get_prefix_count(app, messages[TOTAL]))
for app in apps]))
if verbose:
for lang in messages:
if lang != TOTAL:
for app in apps:
missing = [key for key in messages[TOTAL]
if key.startswith(app) and key not in messages[lang]]
print('{0} {1}: Missing: {2}'.format(
app.upper(), lang, (', '.join(missing) if missing else 'none')))
def output_as_csv(messages, apps):
"""Outputs the given prefix counts and percentages as CSV.
Args:
messages: A dictionary of dictionaries, where the outer keys are language
codes used by translatewiki (generally, ISO 639 language codes) or
the string TOTAL, used to indicate the total set of messages. The
inner dictionary makes message keys to values in that language.
apps: Apps to consider.
"""
# Header row.
print('Language, ' + ', ,'.join(apps))
# Total row.
# Put at top, rather than bottom, so it can be frozen.
print('TOTAL, ' + ', '.join(
[str(get_prefix_count(app, messages[TOTAL])) + ', '
for app in apps]))
# One line per language.
for lang in messages:
if lang != TOTAL:
print(lang + ', ' + ', '.join(
[str(get_prefix_count(app, messages[lang]))
+ ', '
+ str((get_prefix_count(app, messages[lang]) * 1.0 /
get_prefix_count(app, messages[TOTAL])))
for app in apps]))
def main():
"""Processes input files and outputs results in specified format.
"""
# Argument parsing.
parser = argparse.ArgumentParser(
description='Display translation status by app and language.')
parser.add_argument('--key_file', default='json' + os.path.sep + 'keys.json',
help='file with complete list of keys.')
parser.add_argument('--output', default='text',
choices=['text', 'html', 'csv'],
help='output format')
parser.add_argument('--verbose', action='store_true', default=False,
help='whether to indicate which messages were translated '
'(only used in text and html output modes)')
parser.add_argument('--app', default=None, choices=APPS,
help='if set, only consider the specified app (prefix).')
parser.add_argument('lang_files', nargs='+',
help='names of JSON files to examine')
args = parser.parse_args()
apps = [args.app] if args.app else APPS
# Read in JSON files.
messages = {} # A dictionary of dictionaries.
messages[TOTAL] = read_json_file(args.key_file)
for lang_file in args.lang_files:
prefix = get_prefix(os.path.split(lang_file)[1])
# Skip non-language files.
if prefix not in ['qqq', 'keys']:
messages[prefix] = read_json_file(lang_file)
# Output results.
if args.output == 'text':
output_as_text(messages, apps, args.verbose)
elif args.output == 'html':
output_as_html(messages, apps, args.verbose)
elif args.output == 'csv':
output_as_csv(messages, apps)
else:
print('No output?!')
if __name__ == '__main__':
main()

View File

@@ -4,7 +4,7 @@
# Tests of i18n scripts.
#
# Copyright 2013 Google Inc.
# https://developers.google.com/blockly/
# https://blockly.googlecode.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

View File

@@ -3,7 +3,7 @@
# Converts .xlf files into .json files for use at http://translatewiki.net.
#
# Copyright 2013 Google Inc.
# https://developers.google.com/blockly/
# https://blockly.googlecode.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.

BIN
media/anon.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
media/backpack-closed.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
media/backpack-empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

BIN
media/backpack-full.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

BIN
media/backpack-small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
media/backpack-smaller.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
media/backpack.mp3 Normal file

Binary file not shown.

BIN
media/backpack.ogg Normal file

Binary file not shown.

BIN
media/backpack.wav Normal file

Binary file not shown.

255
media/blockly.css Normal file
View File

@@ -0,0 +1,255 @@
.blocklySvg {
background-color: #fff;
}
.blocklyWidgetDiv {
position: absolute;
display: none;
z-index: 999;
}
.blocklyDraggable {
cursor: url(handopen.cur) 8 5 , auto;
}
.blocklyResizeSE {
fill: #aaa;
cursor: se-resize;
}
.blocklyResizeSW {
fill: #aaa;
cursor: sw-resize;
}
.blocklyResizeLine {
stroke-width: 1;
stroke: #888;
}
.blocklyHighlightedConnectionPath {
stroke-width: 4px;
stroke: #fc3;
fill: none;
}
.blocklyPathLight {
fill: none;
stroke-width: 2;
stroke-linecap: round;
}
.blocklySelected>.blocklyPath {
stroke-width: 3px;
stroke: #fc3;
}
.blocklySelected>.blocklyPathLight {
display: none;
}
.blocklyDragging>.blocklyPath, .blocklyDragging>.blocklyPathLight {
fill-opacity: .8;
stroke-opacity: .8;
}
.blocklyDragging>.blocklyPathDark {
display: none;
}
.blocklyDisabled>.blocklyPath {
fill-opacity: .5;
stroke-opacity: .5;
}
.blocklyDisabled>.blocklyPathLight, .blocklyDisabled>.blocklyPathDark {
display: none;
}
.blocklyText {
cursor: default;
font-family: sans-serif;
font-size: 11pt;
fill: #fff;
}
.blocklyNonEditableText>text {
pointer-events: none;
}
.blocklyNonEditableText>rect, .blocklyEditableText>rect {
fill: #fff;
fill-opacity: .6;
}
.blocklyNonEditableText>text, .blocklyEditableText>text {
fill: #000;
}
.blocklyEditableText:hover>rect {
stroke-width: 2;
stroke: #fff;
}
.blocklySvg text {
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
cursor: inherit;
}
.blocklyHidden {
display: none;
}
.blocklyTooltipBackground {
fill: #ffffc7;
stroke-width: 1px;
stroke: #d8d8d8;
}
.blocklyTooltipShadow, .blocklyContextMenuShadow, .blocklyDropdownMenuShadow {
fill: #bbb;
filter: url(#blocklyShadowFilter);
}
.blocklyTooltipText {
font-family: sans-serif;
font-size: 9pt;
fill: #000;
}
.blocklyIconShield {
cursor: default;
fill: #00c;
stroke-width: 1px;
stroke: #ccc;
}
.blocklyIconGroup:hover>.blocklyIconShield {
fill: #00f;
stroke: #fff;
}
.blocklyIconGroup:hover>.blocklyIconMark {
fill: #fff;
}
.blocklyIconMark {
cursor: default !important;
font-family: sans-serif;
font-size: 9pt;
font-weight: bold;
fill: #ccc;
text-anchor: middle;
}
.blocklyMinimalBody {
margin: 0;
padding: 0;
}
.blocklyCommentTextarea {
margin: 0;
padding: 2px;
border: 0;
resize: none;
background-color: #ffc;
}
.blocklyHtmlInput {
font-family: sans-serif;
font-size: 11pt;
border: none;
outline: none;
}
.blocklyContextMenuBackground, .blocklyMutatorBackground {
fill: #fff;
stroke-width: 1;
stroke: #ddd;
}
.blocklyContextMenuOptions>.blocklyMenuDiv, .blocklyContextMenuOptions>.blocklyMenuDivDisabled, .blocklyDropdownMenuOptions>.blocklyMenuDiv {
fill: #fff;
}
.blocklyToolboxOptions>.blocklyMenuDiv {
fill: #ddd;
}
.blocklyToolboxOptions>.blocklyMenuDiv:hover {
fill: #e4e4e4;
}
.blocklyContextMenuOptions>.blocklyMenuDiv:hover>rect, .blocklyDropdownMenuOptions>.blocklyMenuDiv:hover>rect, .blocklyMenuSelected>rect {
fill: #57e;
}
.blocklyMenuText {
cursor: default !important;
font-family: sans-serif;
font-size: 15px;
fill: #000;
}
.blocklyContextMenuOptions>.blocklyMenuDiv:hover>.blocklyMenuText, .blocklyDropdownMenuOptions>.blocklyMenuDiv:hover>.blocklyMenuText, .blocklyMenuSelected>.blocklyMenuText {
fill: #fff;
}
.blocklyMenuDivDisabled>.blocklyMenuText {
fill: #ccc;
}
.blocklyToolboxBackground {
fill: #ddd;
}
.blocklyFlyoutBackground {
fill: #ddd;
fill-opacity: .8;
}
.blocklyColourBackground {
fill: #666;
}
.blocklyScrollbarBackground {
fill: #fff;
stroke-width: 1;
stroke: #e4e4e4;
}
.blocklyScrollbarKnob {
fill: #ccc;
}
.blocklyScrollbarBackground:hover+.blocklyScrollbarKnob, .blocklyScrollbarKnob:hover {
fill: #bbb;
}
.blocklyInvalidInput {
background: #faa;
}
.goog-palette {
outline: none;
cursor: default;
}
.goog-palette-table {
border: 1px solid #666;
border-collapse: collapse;
}
.goog-palette-cell {
height: 13px;
width: 15px;
margin: 0;
border: 0;
text-align: center;
vertical-align: middle;
border-right: 1px solid #666;
font-size: 1px;
}
.goog-palette-colorswatch {
position: relative;
height: 13px;
width: 15px;
border: 1px solid #666;
}
.goog-palette-cell-hover .goog-palette-colorswatch {
border: 1px solid #fff;
}
.goog-palette-cell-selected .goog-palette-colorswatch {
border: 1px solid #000;
color: #fff;
}
.blocklyErrorIconShield {
cursor: default;
fill: #f00;
stroke-width: 1px;
stroke: #ccc;
}
.blocklyIconGroup:hover>.blocklyErrorIconShield {
fill: #f00;
stroke: #fff;
}
.blocklyWarningIconShield {
cursor: default;
fill: #ff5;
stroke-width: 1px;
stroke: #555;
}
.blocklyIconGroup:hover>.blocklyWarningIconShield {
fill: #ff0;
stroke: #000;
}
.blocklyWarningIconMark {
cursor: default !important;
font-family: sans-serif;
font-size: 9pt;
font-weight: bold;
fill: #555;
text-anchor: middle;
}
.blocklyIconGroup:hover>.blocklyWarningIconMark {
fill: #000;
}

BIN
media/progress.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB