Skip to content
Permalink
Browse files

Add generic events for component

Change-Id: I1925eb15feb0e5f6b51e409538491fb0b5257928
  • Loading branch information...
wxbit authored and ewpatton committed Jun 11, 2018
1 parent ceb27bf commit 4ddd02dbb020dbcbf99483c1edeeb784e0d4f895
@@ -2462,6 +2462,11 @@ String newerVersionComponentException(String componentType, int srcCompVersion,
@Description("")
String componentParams();

@DefaultMessage("notAlreadyHandled")
@Description("Name of the event parameter that indicates whether an event has already been handled or not.")
String notAlreadyHandledParams();


@DefaultMessage("startX")
@Description("")
String startXParams();
@@ -56,7 +56,11 @@ Blockly.Blocks.component_event = {

var container = document.createElement('mutation');
container.setAttribute('component_type', this.typeName);
container.setAttribute('instance_name', this.instanceName);//instance name not needed
container.setAttribute('is_generic', this.isGeneric ? "true" : "false");
if (!this.isGeneric) {
container.setAttribute('instance_name', this.instanceName);//instance name not needed
}

container.setAttribute('event_name', this.eventName);
if (!this.horizontalParameters) {
container.setAttribute('vertical_parameters', "true"); // Only store an element for vertical
@@ -68,7 +72,11 @@ Blockly.Blocks.component_event = {
domToMutation : function(xmlElement) {

this.typeName = xmlElement.getAttribute('component_type');
this.instanceName = xmlElement.getAttribute('instance_name');//instance name not needed
this.isGeneric = xmlElement.getAttribute('is_generic') == 'true';
if (!this.isGeneric) {
this.instanceName = xmlElement.getAttribute('instance_name');//instance name not needed
}

this.eventName = xmlElement.getAttribute('event_name');
var horizParams = xmlElement.getAttribute('vertical_parameters') !== "true";

@@ -77,8 +85,6 @@ Blockly.Blocks.component_event = {
this.setColour(Blockly.ComponentBlock.COLOUR_EVENT);

this.componentDropDown = Blockly.ComponentBlock.createComponentDropDown(this);
this.componentDropDown.setValue(this.instanceName);

var localizedEventName;
var eventType = this.getEventTypeObject();
var componentDb = this.getTopWorkspace().getComponentDatabase();
@@ -89,10 +95,15 @@ Blockly.Blocks.component_event = {
localizedEventName = componentDb.getInternationalizedEventName(this.eventName);
}

this.appendDummyInput('WHENTITLE').appendField(Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_WHEN)
if (!this.isGeneric) {
this.appendDummyInput('WHENTITLE').appendField(Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_WHEN)
.appendField(this.componentDropDown, Blockly.ComponentBlock.COMPONENT_SELECTOR)
.appendField('.' + localizedEventName);
this.componentDropDown.setValue(this.instanceName);
this.componentDropDown.setValue(this.instanceName);
} else {
this.appendDummyInput('WHENTITLE').appendField(Blockly.Msg.LANG_COMPONENT_BLOCK_GENERIC_EVENT_TITLE
+ componentDb.getInternationalizedComponentType(this.typeName) + '.' + localizedEventName);
}
this.setParameterOrientation(horizParams);
var tooltipDescription;
if (eventType) {
@@ -194,6 +205,12 @@ Blockly.Blocks.component_event = {
getParameters: function () {
/** @type {EventDescriptor} */
var eventType = this.getEventTypeObject();
if (this.isGeneric) {
return [
{name:'component', type:'component'},
{name:'notAlreadyHandled', type: 'boolean'}
].concat((eventType && eventType.parameters) || []);
}
return eventType && eventType.parameters;
},
// Renames the block's instanceName and type (set in BlocklyBlock constructor), and revises its title
@@ -264,8 +281,10 @@ Blockly.Blocks.component_event = {
typeblock : function(){
var componentDb = Blockly.mainWorkspace.getComponentDatabase();
var tb = [];
var types = {};

componentDb.forEachInstance(function(instance) {
types[instance.typeName] = true;
componentDb.forEventInType(instance.typeName, function(_, eventName) {
tb.push({
translatedName: Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_WHEN + instance.name + '.' +
@@ -279,6 +298,20 @@ Blockly.Blocks.component_event = {
});
});

Object.keys(types).forEach(function(typeName) {
componentDb.forEventInType(typeName, function(_, eventName) {
tb.push({
translatedName: Blockly.Msg.LANG_COMPONENT_BLOCK_GENERIC_EVENT_TITLE +
componentDb.getInternationalizedComponentType(typeName),
mutatorAttributes: {
component_type: typeName,
is_generic: true,
event_name: eventName
}
});
});
});

return tb;
},
customContextMenu: function (options) {
@@ -305,6 +338,10 @@ Blockly.Blocks.component_event = {
// check parameters
var varList = this.getVars();
var params = event.parameters;
if (this.isGeneric) {
varList.splice(0, 2); // remove component and wasDefined parameters
// since we know they are well-defined
}
if (varList.length != params.length) {
return false; // parameters have changed
}
@@ -155,6 +155,11 @@ Blockly.Component.buildComponentMap = function(warnings, errors, forRepl, compil
if(block.blockType != "event") {
continue;
}
if (block.isGeneric) {
map.globals.push(block);
continue;
}

if (!map.components[instanceName]) {
map.components[instanceName] = []; // first block we've found for this component
}
@@ -223,6 +223,15 @@ Blockly.Drawer.prototype.componentTypeToXMLArray = function(typeName) {
var xmlArray = [];
var componentInfo = this.workspace_.getComponentDatabase().getType(typeName);

//create generic event blocks
goog.object.forEach(componentInfo.eventDictionary, function(event, name){
if(!event.deprecated){
Array.prototype.push.apply(xmlArray, this.blockTypeToXMLArray('component_event', {
component_type: typeName, event_name: name, is_generic: 'true'
}));
}
}, this);

//create generic method blocks
goog.object.forEach(componentInfo.methodDictionary, function(method, name) {
if (!method.deprecated) {
@@ -53,6 +53,7 @@ Blockly.Yail.YAIL_COMPONENT_REMOVE = "(remove-component ";
Blockly.Yail.YAIL_COMPONENT_TYPE = "component";
Blockly.Yail.YAIL_DEFINE = "(def ";
Blockly.Yail.YAIL_DEFINE_EVENT = "(define-event ";
Blockly.Yail.YAIL_DEFINE_GENERIC_EVENT = '(define-generic-event ';
Blockly.Yail.YAIL_DEFINE_FORM = "(define-form ";
Blockly.Yail.YAIL_DO_AFTER_FORM_CREATION = "(do-after-form-creation ";
Blockly.Yail.YAIL_DOUBLE_QUOTE = "\"";
@@ -27,17 +27,28 @@ goog.provide('Blockly.Yail.componentblock');
* @returns {Function} event code generation function with instanceName and eventName bound in
*/
Blockly.Yail.component_event = function() {
var body = Blockly.Yail.statementToCode(this, 'DO', Blockly.Yail.ORDER_NONE);

var preamble;
if (this.isGeneric) {
preamble = Blockly.Yail.YAIL_DEFINE_GENERIC_EVENT
+ this.typeName
+ Blockly.Yail.YAIL_SPACER
+ this.eventName;
} else {
preamble = Blockly.Yail.YAIL_DEFINE_EVENT
+ this.getFieldValue("COMPONENT_SELECTOR")
+ Blockly.Yail.YAIL_SPACER
+ this.eventName;
}

var body = Blockly.Yail.statementToCode(this, 'DO');
// TODO: handle deactivated block, null body
if(body == ""){
body = Blockly.Yail.YAIL_NULL;
}


var code = Blockly.Yail.YAIL_DEFINE_EVENT
+ this.getFieldValue("COMPONENT_SELECTOR")
+ Blockly.Yail.YAIL_SPACER
+ this.eventName
var code = preamble
+ Blockly.Yail.YAIL_OPEN_COMBINATION
// TODO: formal params go here
// declaredNames gives us names in local language, but we want the default
@@ -1035,6 +1035,7 @@ Blockly.Msg.en.switch_language_to_english = {
Blockly.Msg.LANG_COMPONENT_BLOCK_HELPURL = '';
Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_WHEN = 'when ';
Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_DO = 'do';
Blockly.Msg.LANG_COMPONENT_BLOCK_GENERIC_EVENT_TITLE = 'when any ';

Blockly.Msg.LANG_COMPONENT_BLOCK_METHOD_HELPURL = '';
Blockly.Msg.LANG_COMPONENT_BLOCK_METHOD_TITLE_CALL = 'call ';
@@ -1780,8 +1780,10 @@ Blockly.Versioning.AllUpgradeMaps =
24: "noUpgrade",

// AI2: In BLOCKS_LANGUAGE_VERSION 25, added Join With Separator Block
25: "noUpgrade"
25: "noUpgrade",

// AI2: In BLOCKS_LANGUAGE_VERSION 26, Added generic event handlers
26: "noUpgrade"

}, // End Language upgraders

@@ -508,6 +508,10 @@ Blockly.WorkspaceSvg.prototype.buildComponentMap = function(warnings, errors, fo
if (block.type == 'procedures_defnoreturn' || block.type == 'procedures_defreturn' || block.type == 'global_declaration') {
map.globals.push(block);
} else if (block.category == 'Component' && block.type == 'event') {
if (block.isGeneric) {
map.globals.push(block);
continue;
}
var instanceName = block.instanceName;
if (!map.components[instanceName]) {
map.components[instanceName] = [];
@@ -943,6 +947,10 @@ Blockly.WorkspaceSvg.prototype.buildComponentMap = function(warnings, errors, fo
if(block.blockType != "event") {
continue;
}
if (block.isGeneric) {
map.globals.push(block);
continue;
}
if (!map.components[instanceName]) {
map.components[instanceName] = []; // first block we've found for this component
}
@@ -307,6 +307,9 @@
(module-static form-name)
(require <com.google.youngandroid.runtime>)

(define (get-simple-name object)
(*:getSimpleName (*:getClass object)))

(define (onCreate icicle :: android.os.Bundle) :: void
;(android.util.Log:i "AppInventorCompatActivity" "in YAIL oncreate")
(com.google.appinventor.components.runtime.AppInventorCompatActivity:setClassicModeFromYail classic-theme)
@@ -474,6 +477,48 @@
registeredComponentName eventName)
#f))))

(define (dispatchGenericEvent componentObject :: com.google.appinventor.components.runtime.Component
eventName :: java.lang.String
notAlreadyHandled :: boolean
args :: java.lang.Object[]) :: void
; My first attempt was to use the gen-generic-event-name
; here, but unfortunately the version of Kawa that we use
; does not correctly import functions from the runtime module
; into the form. The macro expands, but the symbol-append
; function is not found. Below is an "optimization" that
; concatenates the strings first and then calls
; string->symbol, which is effectively the same thing. Most
; of the logic then follows that of dispatchEvent above.
(let* ((handler-symbol (string->symbol (string-append "any$" (get-simple-name componentObject) "$" eventName)))
(handler (lookup-in-form-environment handler-symbol)))
(if handler
(try-catch
(begin
(apply handler (cons componentObject (cons notAlreadyHandled (gnu.lists.LList:makeList args 0))))
#t)
(exception com.google.appinventor.components.runtime.errors.PermissionException
(begin
(exception:printStackTrace)
;; Test to see if the event we are handling is the
;; PermissionDenied of the current form. If so, then we will
;; need to avoid re-invoking PermissionDenied.
(if (and (eq? (this) componentObject)
(equal? eventName "PermissionNeeded"))
;; Error is occurring in the PermissionDenied handler, so we
;; use the more general exception handler to prevent going
;; into an infinite loop.
(process-exception exception)
((this):PermissionDenied componentObject eventName
(exception:getPermissionNeeded)))
#f))
(exception java.lang.Throwable
(begin
(android-log-form (exception:getMessage))
;;; Comment out the line below to inhibit a stack trace on a RunTimeError
(exception:printStackTrace)
(process-exception exception)
#f))))))

(define (lookup-handler componentName eventName)
(lookup-in-form-environment
(string->symbol
@@ -608,6 +653,14 @@
((_ component-name event-name)
(datum->syntax-object stx #'(symbol-append component-name '$ event-name))))))

;;; (gen-generic-event-name Button Click)
;;; ==> any$Button$Click
(define-syntax gen-generic-event-name
(lambda (stx)
(syntax-case stx ()
((_ component-type event-name)
(datum->syntax-object stx #'(symbol-append 'any$ component-type '$ event-name))))))

;;; define-event-helper looks suspiciously like define, but we need it because
;;; if we use define directly in the define-event definition below, the call
;;; to gen-event-name makes it look like we're just defining a function called
@@ -676,6 +729,13 @@
;; If it's not the REPL the form's $define() method will do the registration
(add-to-events 'component-name 'event-name)))))))

(define-syntax define-generic-event
(lambda (stx)
(syntax-case stx ()
((_ component-type event-name args . body)
#`(begin
(define-event-helper ,(gen-generic-event-name #`component-type #`event-name) args body))))))

;;;; def

;;; Def here is putting things (1) in the form environment; (2) in a
@@ -450,8 +450,10 @@ private YaVersion() {
// - VIDEOPLAYER_COMPONENT_VERSION was incremented to 6
// For YOUNG_ANDROID_VERSION 181:
// - BLOCKS_LANGUAGE_VERSION was incremented to 25
// For YOUNG_ANDROID_VERSION 182:
// - BLOCKS_LANGUAGE_VERSION was incremented to 26

public static final int YOUNG_ANDROID_VERSION = 181;
public static final int YOUNG_ANDROID_VERSION = 182;

// ............................... Blocks Language Version Number ...............................

@@ -521,8 +523,10 @@ private YaVersion() {
// - List reverse block was added.
// For BLOCKS_LANGUAGE_VERSION 25:
// - List join with separator block was added.
// For BLOCKS_LANGUAGE_VERSION 26:
// - Generic event handlers were added.

public static final int BLOCKS_LANGUAGE_VERSION = 25;
public static final int BLOCKS_LANGUAGE_VERSION = 26;

// ................................. Component Version Numbers ..................................

@@ -197,6 +197,7 @@ public static boolean dispatchEvent(Component component, String eventName, Objec
if (eventClosures != null && eventClosures.size() > 0) {
dispatched = delegateDispatchEvent(dispatchDelegate, eventClosures, component, args);
}
dispatchDelegate.dispatchGenericEvent(component, eventName, !dispatched, args);
}
return dispatched;
}
@@ -866,6 +866,11 @@ public boolean dispatchEvent(Component component, String componentName, String e
throw new UnsupportedOperationException();
}

@Override
public void dispatchGenericEvent(Component component, String eventName,
boolean notAlreadyHandled, Object[] args) {
throw new UnsupportedOperationException();
}

/**
* Initialize event handler.
@@ -20,4 +20,15 @@ public boolean dispatchEvent(Component component, String componentName, String e

void dispatchErrorOccurredEvent(Component component, String functionName, int errorCode,
Object... args);

/**
* Request that the entity that handles the event send a generic
* event for corresponding component class, event name pair.
*
* @param component the component originating the event
* @param eventName the name of the event to fire
* @param notAlreadyHandled true if the event was not handled by an event handler on the component, otherwise false
* @param args any event-specific arguments to pass to the event handler block
*/
void dispatchGenericEvent(Component component, String eventName, boolean notAlreadyHandled, Object[] args);
}
@@ -74,6 +74,9 @@ private void outputComponent(ComponentInfo component, Set<String> outProperties,
outMethods.add(propertyName);
}
}
// This special case adds the notAlreadyHandled parameter, which is the second parameter for the generic event
// handlers. Since it's not explicitly declared in any event handler, we add it here for internationalization.
parameters.put("notAlreadyHandled", new Parameter("notAlreadyHandled", "boolean"));
sb.append("\n\n/* Parameters */\n\n");
for (Parameter parameter : parameters.values()) {
sb.append(" map.put(\"PARAM-" + parameter.name + "\", MESSAGES." +

0 comments on commit 4ddd02d

Please sign in to comment.
You can’t perform that action at this time.