Skip to content

Commit

Permalink
Add generic events for component
Browse files Browse the repository at this point in the history
Change-Id: I1925eb15feb0e5f6b51e409538491fb0b5257928
  • Loading branch information
wxbit authored and ewpatton committed Mar 7, 2019
1 parent ceb27bf commit 4ddd02d
Show file tree
Hide file tree
Showing 15 changed files with 177 additions and 14 deletions.
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -2462,6 +2462,11 @@ String newerVersionComponentException(String componentType, int srcCompVersion,
@Description("") @Description("")
String componentParams(); 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") @DefaultMessage("startX")
@Description("") @Description("")
String startXParams(); String startXParams();
Expand Down
49 changes: 43 additions & 6 deletions appinventor/blocklyeditor/src/blocks/components.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -56,7 +56,11 @@ Blockly.Blocks.component_event = {


var container = document.createElement('mutation'); var container = document.createElement('mutation');
container.setAttribute('component_type', this.typeName); 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); container.setAttribute('event_name', this.eventName);
if (!this.horizontalParameters) { if (!this.horizontalParameters) {
container.setAttribute('vertical_parameters', "true"); // Only store an element for vertical container.setAttribute('vertical_parameters', "true"); // Only store an element for vertical
Expand All @@ -68,7 +72,11 @@ Blockly.Blocks.component_event = {
domToMutation : function(xmlElement) { domToMutation : function(xmlElement) {


this.typeName = xmlElement.getAttribute('component_type'); 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'); this.eventName = xmlElement.getAttribute('event_name');
var horizParams = xmlElement.getAttribute('vertical_parameters') !== "true"; var horizParams = xmlElement.getAttribute('vertical_parameters') !== "true";


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


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

var localizedEventName; var localizedEventName;
var eventType = this.getEventTypeObject(); var eventType = this.getEventTypeObject();
var componentDb = this.getTopWorkspace().getComponentDatabase(); var componentDb = this.getTopWorkspace().getComponentDatabase();
Expand All @@ -89,10 +95,15 @@ Blockly.Blocks.component_event = {
localizedEventName = componentDb.getInternationalizedEventName(this.eventName); 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(this.componentDropDown, Blockly.ComponentBlock.COMPONENT_SELECTOR)
.appendField('.' + localizedEventName); .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); this.setParameterOrientation(horizParams);
var tooltipDescription; var tooltipDescription;
if (eventType) { if (eventType) {
Expand Down Expand Up @@ -194,6 +205,12 @@ Blockly.Blocks.component_event = {
getParameters: function () { getParameters: function () {
/** @type {EventDescriptor} */ /** @type {EventDescriptor} */
var eventType = this.getEventTypeObject(); var eventType = this.getEventTypeObject();
if (this.isGeneric) {
return [
{name:'component', type:'component'},
{name:'notAlreadyHandled', type: 'boolean'}
].concat((eventType && eventType.parameters) || []);
}
return eventType && eventType.parameters; return eventType && eventType.parameters;
}, },
// Renames the block's instanceName and type (set in BlocklyBlock constructor), and revises its title // Renames the block's instanceName and type (set in BlocklyBlock constructor), and revises its title
Expand Down Expand Up @@ -264,8 +281,10 @@ Blockly.Blocks.component_event = {
typeblock : function(){ typeblock : function(){
var componentDb = Blockly.mainWorkspace.getComponentDatabase(); var componentDb = Blockly.mainWorkspace.getComponentDatabase();
var tb = []; var tb = [];
var types = {};


componentDb.forEachInstance(function(instance) { componentDb.forEachInstance(function(instance) {
types[instance.typeName] = true;
componentDb.forEventInType(instance.typeName, function(_, eventName) { componentDb.forEventInType(instance.typeName, function(_, eventName) {
tb.push({ tb.push({
translatedName: Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_WHEN + instance.name + '.' + translatedName: Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_WHEN + instance.name + '.' +
Expand All @@ -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; return tb;
}, },
customContextMenu: function (options) { customContextMenu: function (options) {
Expand All @@ -305,6 +338,10 @@ Blockly.Blocks.component_event = {
// check parameters // check parameters
var varList = this.getVars(); var varList = this.getVars();
var params = event.parameters; 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) { if (varList.length != params.length) {
return false; // parameters have changed return false; // parameters have changed
} }
Expand Down
5 changes: 5 additions & 0 deletions appinventor/blocklyeditor/src/component.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -155,6 +155,11 @@ Blockly.Component.buildComponentMap = function(warnings, errors, forRepl, compil
if(block.blockType != "event") { if(block.blockType != "event") {
continue; continue;
} }
if (block.isGeneric) {
map.globals.push(block);
continue;
}

if (!map.components[instanceName]) { if (!map.components[instanceName]) {
map.components[instanceName] = []; // first block we've found for this component map.components[instanceName] = []; // first block we've found for this component
} }
Expand Down
9 changes: 9 additions & 0 deletions appinventor/blocklyeditor/src/drawer.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -223,6 +223,15 @@ Blockly.Drawer.prototype.componentTypeToXMLArray = function(typeName) {
var xmlArray = []; var xmlArray = [];
var componentInfo = this.workspace_.getComponentDatabase().getType(typeName); 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 //create generic method blocks
goog.object.forEach(componentInfo.methodDictionary, function(method, name) { goog.object.forEach(componentInfo.methodDictionary, function(method, name) {
if (!method.deprecated) { if (!method.deprecated) {
Expand Down
1 change: 1 addition & 0 deletions appinventor/blocklyeditor/src/generators/yail.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Blockly.Yail.YAIL_COMPONENT_REMOVE = "(remove-component ";
Blockly.Yail.YAIL_COMPONENT_TYPE = "component"; Blockly.Yail.YAIL_COMPONENT_TYPE = "component";
Blockly.Yail.YAIL_DEFINE = "(def "; Blockly.Yail.YAIL_DEFINE = "(def ";
Blockly.Yail.YAIL_DEFINE_EVENT = "(define-event "; 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_DEFINE_FORM = "(define-form ";
Blockly.Yail.YAIL_DO_AFTER_FORM_CREATION = "(do-after-form-creation "; Blockly.Yail.YAIL_DO_AFTER_FORM_CREATION = "(do-after-form-creation ";
Blockly.Yail.YAIL_DOUBLE_QUOTE = "\""; Blockly.Yail.YAIL_DOUBLE_QUOTE = "\"";
Expand Down
21 changes: 16 additions & 5 deletions appinventor/blocklyeditor/src/generators/yail/componentblock.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -27,17 +27,28 @@ goog.provide('Blockly.Yail.componentblock');
* @returns {Function} event code generation function with instanceName and eventName bound in * @returns {Function} event code generation function with instanceName and eventName bound in
*/ */
Blockly.Yail.component_event = function() { 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 // TODO: handle deactivated block, null body
if(body == ""){ if(body == ""){
body = Blockly.Yail.YAIL_NULL; body = Blockly.Yail.YAIL_NULL;
} }




var code = Blockly.Yail.YAIL_DEFINE_EVENT var code = preamble
+ this.getFieldValue("COMPONENT_SELECTOR")
+ Blockly.Yail.YAIL_SPACER
+ this.eventName
+ Blockly.Yail.YAIL_OPEN_COMBINATION + Blockly.Yail.YAIL_OPEN_COMBINATION
// TODO: formal params go here // TODO: formal params go here
// declaredNames gives us names in local language, but we want the default // declaredNames gives us names in local language, but we want the default
Expand Down
1 change: 1 addition & 0 deletions appinventor/blocklyeditor/src/msg/en/_messages.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1035,6 +1035,7 @@ Blockly.Msg.en.switch_language_to_english = {
Blockly.Msg.LANG_COMPONENT_BLOCK_HELPURL = ''; Blockly.Msg.LANG_COMPONENT_BLOCK_HELPURL = '';
Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_WHEN = 'when '; Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_WHEN = 'when ';
Blockly.Msg.LANG_COMPONENT_BLOCK_TITLE_DO = 'do'; 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_HELPURL = '';
Blockly.Msg.LANG_COMPONENT_BLOCK_METHOD_TITLE_CALL = 'call '; Blockly.Msg.LANG_COMPONENT_BLOCK_METHOD_TITLE_CALL = 'call ';
Expand Down
4 changes: 3 additions & 1 deletion appinventor/blocklyeditor/src/versioning.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1780,8 +1780,10 @@ Blockly.Versioning.AllUpgradeMaps =
24: "noUpgrade", 24: "noUpgrade",


// AI2: In BLOCKS_LANGUAGE_VERSION 25, added Join With Separator Block // 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 }, // End Language upgraders


Expand Down
8 changes: 8 additions & 0 deletions appinventor/blocklyeditor/src/workspace_svg.js
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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') { if (block.type == 'procedures_defnoreturn' || block.type == 'procedures_defreturn' || block.type == 'global_declaration') {
map.globals.push(block); map.globals.push(block);
} else if (block.category == 'Component' && block.type == 'event') { } else if (block.category == 'Component' && block.type == 'event') {
if (block.isGeneric) {
map.globals.push(block);
continue;
}
var instanceName = block.instanceName; var instanceName = block.instanceName;
if (!map.components[instanceName]) { if (!map.components[instanceName]) {
map.components[instanceName] = []; map.components[instanceName] = [];
Expand Down Expand Up @@ -943,6 +947,10 @@ Blockly.WorkspaceSvg.prototype.buildComponentMap = function(warnings, errors, fo
if(block.blockType != "event") { if(block.blockType != "event") {
continue; continue;
} }
if (block.isGeneric) {
map.globals.push(block);
continue;
}
if (!map.components[instanceName]) { if (!map.components[instanceName]) {
map.components[instanceName] = []; // first block we've found for this component map.components[instanceName] = []; // first block we've found for this component
} }
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -307,6 +307,9 @@
(module-static form-name) (module-static form-name)
(require <com.google.youngandroid.runtime>) (require <com.google.youngandroid.runtime>)


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

(define (onCreate icicle :: android.os.Bundle) :: void (define (onCreate icicle :: android.os.Bundle) :: void
;(android.util.Log:i "AppInventorCompatActivity" "in YAIL oncreate") ;(android.util.Log:i "AppInventorCompatActivity" "in YAIL oncreate")
(com.google.appinventor.components.runtime.AppInventorCompatActivity:setClassicModeFromYail classic-theme) (com.google.appinventor.components.runtime.AppInventorCompatActivity:setClassicModeFromYail classic-theme)
Expand Down Expand Up @@ -474,6 +477,48 @@
registeredComponentName eventName) registeredComponentName eventName)
#f)))) #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) (define (lookup-handler componentName eventName)
(lookup-in-form-environment (lookup-in-form-environment
(string->symbol (string->symbol
Expand Down Expand Up @@ -608,6 +653,14 @@
((_ component-name event-name) ((_ component-name event-name)
(datum->syntax-object stx #'(symbol-append 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 ;;; 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 ;;; 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 ;;; to gen-event-name makes it look like we're just defining a function called
Expand Down Expand Up @@ -676,6 +729,13 @@
;; If it's not the REPL the form's $define() method will do the registration ;; If it's not the REPL the form's $define() method will do the registration
(add-to-events 'component-name 'event-name))))))) (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


;;; Def here is putting things (1) in the form environment; (2) in a ;;; Def here is putting things (1) in the form environment; (2) in a
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -450,8 +450,10 @@ private YaVersion() {
// - VIDEOPLAYER_COMPONENT_VERSION was incremented to 6 // - VIDEOPLAYER_COMPONENT_VERSION was incremented to 6
// For YOUNG_ANDROID_VERSION 181: // For YOUNG_ANDROID_VERSION 181:
// - BLOCKS_LANGUAGE_VERSION was incremented to 25 // - 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 ............................... // ............................... Blocks Language Version Number ...............................


Expand Down Expand Up @@ -521,8 +523,10 @@ private YaVersion() {
// - List reverse block was added. // - List reverse block was added.
// For BLOCKS_LANGUAGE_VERSION 25: // For BLOCKS_LANGUAGE_VERSION 25:
// - List join with separator block was added. // - 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 .................................. // ................................. Component Version Numbers ..................................


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


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


/** /**
* Initialize event handler. * Initialize event handler.
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -20,4 +20,15 @@ public boolean dispatchEvent(Component component, String componentName, String e


void dispatchErrorOccurredEvent(Component component, String functionName, int errorCode, void dispatchErrorOccurredEvent(Component component, String functionName, int errorCode,
Object... args); 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);
} }
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ private void outputComponent(ComponentInfo component, Set<String> outProperties,
outMethods.add(propertyName); 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"); sb.append("\n\n/* Parameters */\n\n");
for (Parameter parameter : parameters.values()) { for (Parameter parameter : parameters.values()) {
sb.append(" map.put(\"PARAM-" + parameter.name + "\", MESSAGES." + sb.append(" map.put(\"PARAM-" + parameter.name + "\", MESSAGES." +
Expand Down

0 comments on commit 4ddd02d

Please sign in to comment.