[WIP] Rebase from Blockly SVN r1757 to git 641d720
@@ -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
510
core/block.js.rej
Normal 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
231
core/block_svg.js.rej
Normal 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_();
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
85
core/css.js
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
@@ -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
@@ -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);
|
||||
};
|
||||
@@ -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
17
core/flyout.js.rej
Normal 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
@@ -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
@@ -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_(
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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
@@ -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
@@ -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');
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
*/
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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()
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
After Width: | Height: | Size: 2.0 KiB |
BIN
media/backpack-closed.png
Normal file
|
After Width: | Height: | Size: 3.8 KiB |
BIN
media/backpack-empty.png
Normal file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
media/backpack-full.png
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
media/backpack-small.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
media/backpack-smaller.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
media/backpack.mp3
Normal file
BIN
media/backpack.ogg
Normal file
BIN
media/backpack.wav
Normal file
255
media/blockly.css
Normal 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
|
After Width: | Height: | Size: 19 KiB |