mirror of
https://github.com/google/blockly.git
synced 2026-01-10 18:37:09 +01:00
More work on connection type checks
This commit is contained in:
@@ -456,7 +456,8 @@ Blockly.Block.prototype.unplugFromRow_ = function(opt_healStack) {
|
||||
// Disconnect the child block.
|
||||
childConnection.disconnect();
|
||||
// Connect child to the parent if possible, otherwise bump away.
|
||||
if (childConnection.checkType(parentConnection)) {
|
||||
if (this.workspace.connectionTypeChecker.canConnect(
|
||||
childConnection, parentConnection, false, false)) {
|
||||
parentConnection.connect(childConnection);
|
||||
} else {
|
||||
childConnection.onFailedConnect(parentConnection);
|
||||
@@ -508,7 +509,9 @@ Blockly.Block.prototype.unplugFromStack_ = function(opt_healStack) {
|
||||
// Disconnect the next statement.
|
||||
var nextTarget = this.nextConnection.targetConnection;
|
||||
nextTarget.disconnect();
|
||||
if (previousTarget && previousTarget.checkType(nextTarget)) {
|
||||
if (previousTarget &&
|
||||
this.workspace.connectionTypeChecker.canConnect(
|
||||
previousTarget, nextTarget, false, false)) {
|
||||
// Attach the next statement to the previous statement.
|
||||
previousTarget.connect(nextTarget);
|
||||
}
|
||||
|
||||
@@ -147,10 +147,10 @@ Blockly.Connection.prototype.connect_ = function(childConnection) {
|
||||
if (nextBlock && !nextBlock.isShadow()) {
|
||||
newBlock = nextBlock;
|
||||
} else {
|
||||
if (orphanBlock.workspace.connectionTypeChecker.checkType(
|
||||
orphanBlock.previousConnection, newBlock.nextConnection)) {
|
||||
//orphanBlock.previousConnection.checkType(
|
||||
//newBlock.nextConnection)) {
|
||||
var typeChecker = orphanBlock.workspace.connectionTypeChecker;
|
||||
if (typeChecker.canConnect(
|
||||
orphanBlock.previousConnection, newBlock.nextConnection,
|
||||
false, false)) {
|
||||
newBlock.nextConnection.connect(orphanBlock.previousConnection);
|
||||
orphanBlock = null;
|
||||
}
|
||||
@@ -249,6 +249,7 @@ Blockly.Connection.prototype.isConnected = function() {
|
||||
* @param {Blockly.Connection} target Connection to check compatibility with.
|
||||
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
|
||||
* an error code otherwise.
|
||||
* @deprecated July 2020
|
||||
*/
|
||||
Blockly.Connection.prototype.canConnectWithReason = function(target) {
|
||||
// TODO: deprecation warning with date, plus tests.
|
||||
@@ -261,6 +262,7 @@ Blockly.Connection.prototype.canConnectWithReason = function(target) {
|
||||
* @param {Blockly.Connection} target The connection to check compatibility
|
||||
* with.
|
||||
* @package
|
||||
* @deprecated July 2020
|
||||
*/
|
||||
Blockly.Connection.prototype.checkConnection = function(target) {
|
||||
// TODO: Add deprecation warning notices *and* add tests to make sure these
|
||||
@@ -269,50 +271,21 @@ Blockly.Connection.prototype.checkConnection = function(target) {
|
||||
checker.canConnect(this, target, false, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the workspace's connection type checker object.
|
||||
* @return {!Blockly.ConnectionTypeChecker} The connection type checker for the
|
||||
* source block's workspace.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Connection.prototype.getConnectionTypeChecker = function() {
|
||||
return this.sourceBlock_.workspace.connectionTypeChecker;
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Check if the two connections can be dragged to connect to each other.
|
||||
// * This is used by the connection database when searching for the closest
|
||||
// * connection.
|
||||
// * @param {!Blockly.Connection} candidate A nearby connection to check, which
|
||||
// * must be a previous connection.
|
||||
// * @return {boolean} True if the connection is allowed, false otherwise.
|
||||
// * @private
|
||||
// */
|
||||
// Blockly.Connection.prototype.canConnectToPrevious_ = function(candidate) {
|
||||
// if (this.targetConnection) {
|
||||
// // This connection is already occupied.
|
||||
// // A next connection will never disconnect itself mid-drag.
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// // Don't let blocks try to connect to themselves or ones they nest.
|
||||
// if (Blockly.draggingConnections.indexOf(candidate) != -1) {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (!candidate.targetConnection) {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// var targetBlock = candidate.targetBlock();
|
||||
// // If it is connected to a real block, game over.
|
||||
// if (!targetBlock.isInsertionMarker()) {
|
||||
// return false;
|
||||
// }
|
||||
// // If it's connected to an insertion marker but that insertion marker
|
||||
// // is the first block in a stack, it's still fine. If that insertion
|
||||
// // marker is in the middle of a stack, it won't work.
|
||||
// return !targetBlock.getPreviousBlock();
|
||||
// };
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @deprecated July 2020
|
||||
*/
|
||||
Blockly.Connection.prototype.isConnectionAllowed = function(candidate) {
|
||||
return this.getConnectionTypeChecker().canConnect(this, candidate, true, false);
|
||||
@@ -339,22 +312,22 @@ Blockly.Connection.prototype.connect = function(otherConnection) {
|
||||
}
|
||||
|
||||
var checker = this.getConnectionTypeChecker();
|
||||
checker.canConnect(this, otherConnection, false, true);
|
||||
|
||||
var eventGroup = Blockly.Events.getGroup();
|
||||
if (!eventGroup) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
// Determine which block is superior (higher in the source stack).
|
||||
if (this.isSuperior()) {
|
||||
// Superior block.
|
||||
this.connect_(otherConnection);
|
||||
} else {
|
||||
// Inferior block.
|
||||
otherConnection.connect_(this);
|
||||
}
|
||||
if (!eventGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
if (checker.canConnect(this, otherConnection, false, true)) {
|
||||
var eventGroup = Blockly.Events.getGroup();
|
||||
if (!eventGroup) {
|
||||
Blockly.Events.setGroup(true);
|
||||
}
|
||||
// Determine which block is superior (higher in the source stack).
|
||||
if (this.isSuperior()) {
|
||||
// Superior block.
|
||||
this.connect_(otherConnection);
|
||||
} else {
|
||||
// Inferior block.
|
||||
otherConnection.connect_(this);
|
||||
}
|
||||
if (!eventGroup) {
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -383,10 +356,12 @@ Blockly.Connection.connectReciprocally_ = function(first, second) {
|
||||
*/
|
||||
Blockly.Connection.singleConnection_ = function(block, orphanBlock) {
|
||||
var connection = null;
|
||||
var output = orphanBlock.outputConnection;
|
||||
for (var i = 0; i < block.inputList.length; i++) {
|
||||
var thisConnection = block.inputList[i].connection;
|
||||
var typeChecker = output.getConnectionTypeChecker();
|
||||
if (thisConnection && thisConnection.type == Blockly.INPUT_VALUE &&
|
||||
orphanBlock.outputConnection.checkType(thisConnection)) {
|
||||
typeChecker.canConnect(output, thisConnection, false, false)) {
|
||||
if (connection) {
|
||||
return null; // More than one connection.
|
||||
}
|
||||
@@ -516,7 +491,7 @@ Blockly.Connection.prototype.targetBlock = function() {
|
||||
* @return {boolean} True if the connections share a type.
|
||||
*/
|
||||
Blockly.Connection.prototype.checkType = function(otherConnection) {
|
||||
return this.getConnectionTypeChecker().checkType(this, otherConnection);
|
||||
return this.getConnectionTypeChecker().canConnect(this, otherConnection, false, false);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -541,7 +516,8 @@ Blockly.Connection.prototype.checkType_ = function(otherConnection) {
|
||||
Blockly.Connection.prototype.onCheckChanged_ = function() {
|
||||
// The new value type may not be compatible with the existing connection.
|
||||
if (this.isConnected() && (!this.targetConnection ||
|
||||
!this.checkType(this.targetConnection))) {
|
||||
!this.getConnectionTypeChecker().canConnect(
|
||||
this, this.targetConnection, false, false))) {
|
||||
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
|
||||
child.unplug();
|
||||
}
|
||||
|
||||
@@ -11,6 +11,94 @@ goog.requireType('Blockly.Connection');
|
||||
Blockly.ConnectionTypeChecker = function() {
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the current connection can connect with the target
|
||||
* connection.
|
||||
* @param {Blockly.Connection} one Connection to check compatibility with.
|
||||
* @param {Blockly.Connection} two Connection to check compatibility with.
|
||||
* @param {boolean} isDragging True if the connection is being made by dragging
|
||||
* a block.
|
||||
* @param {boolean} shouldThrow Whether to throw an error when a connection is
|
||||
* invalid.
|
||||
* @return {boolean} Whether the connection is legal.
|
||||
*/
|
||||
Blockly.ConnectionTypeChecker.prototype.canConnect = function(one, two,
|
||||
isDragging, shouldThrow) {
|
||||
if (this.passesSafetyChecks(one, two, shouldThrow)) {
|
||||
if (this.passesTypeChecks(one, two, shouldThrow)) {
|
||||
if (!isDragging || this.passesDragChecks(one, two, shouldThrow)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check that connecting the given connections is safe, meaning that it would
|
||||
* not break any of Blockly's basic assumptions--no self connections, etc.
|
||||
* @param {!Blockly.Connection} one The first of the connections to check.
|
||||
* @param {!Blockly.Connection} two The second of the connections to check.
|
||||
* @param {boolean} shouldThrow Whether to throw an error if the connection is
|
||||
* unsafe.
|
||||
* @return {boolean} Whether the connection is safe.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ConnectionTypeChecker.prototype.passesSafetyChecks = function(one, two, shouldThrow) {
|
||||
var safety = this.doSafetyChecks_(one, two);
|
||||
if (safety == Blockly.Connection.CAN_CONNECT) {
|
||||
return true;
|
||||
}
|
||||
if (shouldThrow) {
|
||||
throw Error(this.getErrorMessage_(safety, one, two));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
Blockly.ConnectionTypeChecker.prototype.passesTypeChecks = function(one, two,
|
||||
shouldThrow) {
|
||||
if (this.doTypeChecks_(one, two)) {
|
||||
return true;
|
||||
}
|
||||
if (shouldThrow) {
|
||||
throw Error(this.getErrorMessage_(
|
||||
Blockly.Connection.REASON_CHECKS_FAILED, one, two));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two,
|
||||
shouldThrow) {
|
||||
if (this.doDragChecks_(one, two)) {
|
||||
return true;
|
||||
}
|
||||
if (shouldThrow) {
|
||||
throw Error(this.getErrorMessage_(
|
||||
Blockly.Connection.REASON_DRAG_CHECKS_FAILED, one, two));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the current connection can connect with the target
|
||||
* connection.
|
||||
* @param {Blockly.Connection} one Connection to check compatibility with.
|
||||
* @param {Blockly.Connection} two Connection to check compatibility with.
|
||||
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
|
||||
* an error code otherwise.
|
||||
*/
|
||||
Blockly.ConnectionTypeChecker.prototype.canConnectWithReason = function(one, two) {
|
||||
var safety = this.doSafetyChecks_(one, two);
|
||||
if (safety != Blockly.Connection.CAN_CONNECT) {
|
||||
return safety;
|
||||
}
|
||||
if (!this.doTypeChecks_(one, two)) {
|
||||
return Blockly.Connection.REASON_CHECKS_FAILED;
|
||||
}
|
||||
return Blockly.Connection.CAN_CONNECT;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper method that translates a connection error code into a string.
|
||||
* @param {number} errorCode The error code.
|
||||
@@ -18,9 +106,9 @@ Blockly.ConnectionTypeChecker = function() {
|
||||
* @param {!Blockly.Connection} two The second of the two connections being
|
||||
* checked.
|
||||
* @return {string} A developer-readable error string.
|
||||
* @package
|
||||
* @private
|
||||
*/
|
||||
Blockly.ConnectionTypeChecker.prototype.getErrorMessage = function(errorCode,
|
||||
Blockly.ConnectionTypeChecker.prototype.getErrorMessage_ = function(errorCode,
|
||||
one, two) {
|
||||
switch (errorCode) {
|
||||
case Blockly.Connection.REASON_SELF_CONNECTION:
|
||||
@@ -39,32 +127,21 @@ Blockly.ConnectionTypeChecker.prototype.getErrorMessage = function(errorCode,
|
||||
case Blockly.Connection.REASON_SHADOW_PARENT:
|
||||
return 'Connecting non-shadow to shadow block.';
|
||||
case Blockly.Connection.REASON_DRAG_CHECKS_FAILED:
|
||||
return 'Drag checks failed.'
|
||||
return 'Drag checks failed.';
|
||||
default:
|
||||
return 'Unknown connection failure: this should never happen!';
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the current connection can connect with the target
|
||||
* connection.
|
||||
* @param {Blockly.Connection} one Connection to check compatibility with.
|
||||
* @param {Blockly.Connection} two Connection to check compatibility with.
|
||||
* @return {number} Blockly.Connection.CAN_CONNECT if the connection is legal,
|
||||
* an error code otherwise.
|
||||
* Check that connecting the given connections is safe, meaning that it would
|
||||
* not break any of Blockly's basic assumptions--no self connections, etc.
|
||||
* @param {!Blockly.Connection} one The first of the connections to check.
|
||||
* @param {!Blockly.Connection} two The second of the connections to check.
|
||||
* @return {boolean} True if making this connection is safe.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ConnectionTypeChecker.prototype.canConnectWithReason = function(one, two) {
|
||||
var validity = this.doValidityChecks(one, two);
|
||||
if (validity != Blockly.Connection.CAN_CONNECT) {
|
||||
return validity;
|
||||
}
|
||||
if (!this.checkType(one, two)) {
|
||||
return Blockly.Connection.REASON_CHECKS_FAILED;
|
||||
}
|
||||
return Blockly.Connection.CAN_CONNECT;
|
||||
};
|
||||
|
||||
Blockly.ConnectionTypeChecker.prototype.doValidityChecks = function(one, two) {
|
||||
Blockly.ConnectionTypeChecker.prototype.doSafetyChecks_ = function(one, two) {
|
||||
if (!one || !two) {
|
||||
return Blockly.Connection.REASON_TARGET_NULL;
|
||||
}
|
||||
@@ -75,8 +152,6 @@ Blockly.ConnectionTypeChecker.prototype.doValidityChecks = function(one, two) {
|
||||
var blockB = one.getSourceBlock();
|
||||
var blockA = two.getSourceBlock();
|
||||
}
|
||||
// TODO (fenichel): The null checks seem like they're only for making tests
|
||||
// work better.
|
||||
if (blockA == blockB) {
|
||||
return Blockly.Connection.REASON_SELF_CONNECTION;
|
||||
} else if (two.type != Blockly.OPPOSITE_TYPE[one.type]) {
|
||||
@@ -90,53 +165,15 @@ Blockly.ConnectionTypeChecker.prototype.doValidityChecks = function(one, two) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether the current connection can connect with the target
|
||||
* connection.
|
||||
* @param {Blockly.Connection} one Connection to check compatibility with.
|
||||
* @param {Blockly.Connection} two Connection to check compatibility with.
|
||||
* @return {boolean} Whether the connection is legal.
|
||||
*/
|
||||
Blockly.ConnectionTypeChecker.prototype.canConnect = function(one, two,
|
||||
isDragging, shouldThrow) {
|
||||
var validity = this.doValidityChecks(one, two);
|
||||
if (validity != Blockly.Connection.CAN_CONNECT) {
|
||||
if (shouldThrow) {
|
||||
throw Error(this.getErrorMessage(validity, one, two));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
var passesTypeChecks = this.checkType(one, two);
|
||||
if (!passesTypeChecks) {
|
||||
|
||||
if (shouldThrow) {
|
||||
throw Error(this.getErrorMessage(
|
||||
Blockly.Connection.REASON_CHECKS_FAILED, one, two));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isDragging) {
|
||||
var passesDragChecks = this.passesDragChecks(one, two);
|
||||
if (!passesDragChecks) {
|
||||
if (shouldThrow) {
|
||||
throw Error(this.getErrorMessage(
|
||||
Blockly.Connection.REASON_DRAG_CHECKS_FAILED, one, two));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is this connection compatible with another connection with respect to the
|
||||
* value type system. E.g. square_root("Hello") is not compatible.
|
||||
* Check whether this connection is compatible with another connection with
|
||||
* respect to the value type system. E.g. square_root("Hello") is not
|
||||
* compatible.
|
||||
* @param {!Blockly.Connection} one Connection to compare.
|
||||
* @param {!Blockly.Connection} two Connection to compare against.
|
||||
* @return {boolean} True if the connections share a type.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ConnectionTypeChecker.prototype.checkType = function(one, two) {
|
||||
Blockly.ConnectionTypeChecker.prototype.doTypeChecks_ = function(one, two) {
|
||||
var checkArrayOne = one.getCheck();
|
||||
var checkArrayTwo = two.getCheck();
|
||||
|
||||
@@ -154,7 +191,14 @@ Blockly.ConnectionTypeChecker.prototype.checkType = function(one, two) {
|
||||
return false;
|
||||
};
|
||||
|
||||
Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two) {
|
||||
/**
|
||||
* Check whether this connection can be made by dragging.
|
||||
* @param {!Blockly.Connection} one Connection to compare.
|
||||
* @param {!Blockly.Connection} two Connection to compare against.
|
||||
* @return {boolean} True if the connections share a type.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ConnectionTypeChecker.prototype.doDragChecks_ = function(one, two) {
|
||||
// Don't consider insertion markers.
|
||||
if (two.sourceBlock_.isInsertionMarker()) {
|
||||
return false;
|
||||
@@ -198,7 +242,8 @@ Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two) {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw Error('Unknown connection type in passesDragChecks');
|
||||
// Unexpected connection type.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't let blocks try to connect to themselves or ones they nest.
|
||||
@@ -210,15 +255,13 @@ Blockly.ConnectionTypeChecker.prototype.passesDragChecks = function(one, two) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the two connections can be dragged to connect to each other.
|
||||
* This is used by the connection database when searching for the closest
|
||||
* connection.
|
||||
* Helper function for drag checking
|
||||
* @param {!Blockly.Connection} one The connection to check, which must be a
|
||||
* statement input or next connection.
|
||||
* @param {!Blockly.Connection} two A nearby connection to check, which
|
||||
* must be a previous connection.
|
||||
* @return {boolean} True if the connection is allowed, false otherwise.
|
||||
* @private
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ConnectionTypeChecker.prototype.canConnectToPrevious_ = function(one, two) {
|
||||
if (one.targetConnection) {
|
||||
|
||||
@@ -422,6 +422,7 @@ Blockly.RenderedConnection.prototype.startTrackingAll = function() {
|
||||
* @param {number=} maxRadius The maximum radius allowed for connections, in
|
||||
* workspace units.
|
||||
* @return {boolean} True if the connection is allowed, false otherwise.
|
||||
* @deprecated July 2020
|
||||
*/
|
||||
Blockly.RenderedConnection.prototype.isConnectionAllowed = function(candidate,
|
||||
maxRadius) {
|
||||
@@ -551,7 +552,8 @@ Blockly.RenderedConnection.prototype.connect_ = function(childConnection) {
|
||||
Blockly.RenderedConnection.prototype.onCheckChanged_ = function() {
|
||||
// The new value type may not be compatible with the existing connection.
|
||||
if (this.isConnected() && (!this.targetConnection ||
|
||||
!this.checkType(this.targetConnection))) {
|
||||
!this.getConnectionTypeChecker().canConnect(
|
||||
this, this.targetConnection, false, false))) {
|
||||
var child = this.isSuperior() ? this.targetBlock() : this.sourceBlock_;
|
||||
child.unplug();
|
||||
// Bump away.
|
||||
|
||||
@@ -254,7 +254,8 @@ Blockly.blockRendering.Renderer.prototype.orphanCanConnectAtEnd =
|
||||
if (!lastConnection) {
|
||||
return false;
|
||||
}
|
||||
return orphanConnection.checkType(lastConnection);
|
||||
return orphanConnection.getConnectionTypeChecker().canConnect(
|
||||
lastConnection, orphanConnection, false, false);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -229,13 +229,12 @@ suite('Connection type checker', function() {
|
||||
this.con2 = new Blockly.Connection({}, Blockly.NEXT_STATEMENT);
|
||||
});
|
||||
function assertCheckTypes(checker, one, two) {
|
||||
chai.assert.isTrue(checker.checkType(one, two));
|
||||
chai.assert.isTrue(checker.passesTypeChecks(one, two));
|
||||
// Order should not matter.
|
||||
chai.assert.isTrue(checker.checkType(one, two));
|
||||
chai.assert.isTrue(checker.passesTypeChecks(one, two));
|
||||
}
|
||||
test('No Types', function() {
|
||||
assertCheckTypes(this.checker, this.con1, this.con2);
|
||||
chai.assert.isTrue(this.checker.checkType(this.con1, this.con2));
|
||||
});
|
||||
test('Same Type', function() {
|
||||
this.con1.setCheck('type1');
|
||||
@@ -259,7 +258,7 @@ suite('Connection type checker', function() {
|
||||
test('No Compatible Types', function() {
|
||||
this.con1.setCheck('type1');
|
||||
this.con2.setCheck('type2');
|
||||
chai.assert.isFalse(this.checker.checkType(this.con1, this.con2));
|
||||
chai.assert.isFalse(this.checker.passesTypeChecks(this.con1, this.con2));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user