From 0a6a0c5e089518de1012fb466867ea4b199bf9a6 Mon Sep 17 00:00:00 2001 From: Alexander Zuev Date: Mon, 6 Jan 2025 10:42:46 -0800 Subject: [PATCH] 8343133: Create implementation of NSAccessibilityStepper protocol Initial implementation with a lot of debugging output --- .../com/sun/glass/ui/mac/MacAccessible.java | 1 + .../main/native-glass/mac/GlassAccessible.m | 21 ++ .../native-glass/mac/a11y/AccessibleBase.h | 5 + .../native-glass/mac/a11y/AccessibleBase.m | 67 +++++- .../mac/a11y/JFXNavigableTextAccessibility.h | 45 ++++ .../mac/a11y/JFXNavigableTextAccessibility.m | 218 ++++++++++++++++++ .../mac/a11y/JFXStaticTextAccessibility.h | 2 + .../mac/a11y/JFXStaticTextAccessibility.m | 29 ++- 8 files changed, 385 insertions(+), 3 deletions(-) create mode 100644 modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXNavigableTextAccessibility.h create mode 100644 modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXNavigableTextAccessibility.m diff --git a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacAccessible.java b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacAccessible.java index f728dd2e9f7..bd8a17a712a 100644 --- a/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacAccessible.java +++ b/modules/javafx.graphics/src/main/java/com/sun/glass/ui/mac/MacAccessible.java @@ -831,6 +831,7 @@ protected long getNativeAccessible() { if (this.peer == 0L) { AccessibleRole role = (AccessibleRole) getAttribute(ROLE); if (role == null) role = AccessibleRole.NODE; +// System.out.println("Creating peer for " + role.toString()); this.peer = _createAccessiblePeer(role.toString()); if (this.peer == 0L) { throw new RuntimeException("could not create platform accessible"); diff --git a/modules/javafx.graphics/src/main/native-glass/mac/GlassAccessible.m b/modules/javafx.graphics/src/main/native-glass/mac/GlassAccessible.m index e14b79cd4ee..1935c95317d 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/GlassAccessible.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/GlassAccessible.m @@ -37,6 +37,10 @@ - (id)initWithEnv:(JNIEnv*)env accessible:(jobject)acc if (self != nil) { self->jAccessible = (*env)->NewGlobalRef(env, acc); } + NSLog(@"Initializing with role %@", [self accessibilityAttributeValueImpl:@"AXRole"]); + if ([@"AXStaticText" isEqualToString:[self accessibilityAttributeValueImpl:@"AXRole"]]) { + NSLog(@"%@", NSThread.callStackSymbols); + } return self; } @@ -69,6 +73,11 @@ - (NSArray *)accessibilityAttributeNames } - (id)accessibilityAttributeValue:(NSString *)attribute +{ + return [self accessibilityAttributeValueImpl:attribute]; +} + +- (id)accessibilityAttributeValueImpl:(NSString *)attribute { jobject jresult = NULL; GET_MAIN_JENV; @@ -204,6 +213,7 @@ - (id)accessibilityHitTest:(NSPoint)point if (env == NULL) return NULL; result = (id)(*env)->CallLongMethod(env, self->jAccessible, jAccessibilityHitTest, point.x, point.y); GLASS_CHECK_EXCEPTION(env); + NSLog(@"accessibilityHitTest(%@) returns %@", [NSValue valueWithPoint:point], result); return result; } @@ -222,6 +232,15 @@ - (BOOL)accessibilityNotifiesWhenDestroyed return YES; } +- (NSString *)roleAndValue +{ + NSString *retVal = [NSString stringWithFormat:@"Old Role: %@ Value:%@", + [self accessibilityAttributeValueImpl:@"AXRole"], + [self accessibilityAttributeValueImpl:@"AXValue"]]; + return retVal; +} + + @end NSArray* jArrayToNSArray(JNIEnv *env, jarray srcArray, jMapper mapper) { @@ -686,6 +705,7 @@ id variantToID(JNIEnv *env, jobject variant) { JNIEXPORT void JNICALL Java_com_sun_glass_ui_mac_MacAccessible_NSAccessibilityPostNotification (JNIEnv *env, jclass jClass, jlong element, jlong notification) { + NSLog(@"Posting notification on %@ about %@", (id)jlong_to_ptr(element), (NSString*)jlong_to_ptr(notification)); NSAccessibilityPostNotification((id)jlong_to_ptr(element), (NSString*)jlong_to_ptr(notification)); } @@ -724,6 +744,7 @@ id variantToID(JNIEnv *env, jobject variant) { JNIEXPORT jobject JNICALL Java_com_sun_glass_ui_mac_MacAccessible_GlassAccessibleToMacAccessible (JNIEnv *env, jclass jClass, jlong glassAccessible) { + NSLog(@"GlassAccessibleToMacAccessible for %@", (id)jlong_to_ptr(glassAccessible)); GlassAccessible* accessible = (GlassAccessible*)jlong_to_ptr(glassAccessible); return accessible ? [accessible getJAccessible] : NULL; } diff --git a/modules/javafx.graphics/src/main/native-glass/mac/a11y/AccessibleBase.h b/modules/javafx.graphics/src/main/native-glass/mac/a11y/AccessibleBase.h index 772a0694d0d..bd014eb8fa1 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/a11y/AccessibleBase.h +++ b/modules/javafx.graphics/src/main/native-glass/mac/a11y/AccessibleBase.h @@ -26,15 +26,20 @@ #import #import +#define EmptyRange NSMakeRange(0, 0); + @interface AccessibleBase : NSAccessibilityElement { @private jobject jAccessible; id parent; +id jRole; } - (id)initWithEnv:(JNIEnv*)env accessible:(jobject)jAccessible; - (jobject)getJAccessible; +- (NSString *)getJavaRole; - (NSRect)accessibilityFrame; - (id)accessibilityParent; +- (NSArray *)accessibilityChildren; - (BOOL)isAccessibilityElement; - (BOOL)performAccessibleAction:(NSString*)actionId; + (void) initializeRolesMap; diff --git a/modules/javafx.graphics/src/main/native-glass/mac/a11y/AccessibleBase.m b/modules/javafx.graphics/src/main/native-glass/mac/a11y/AccessibleBase.m index 5d559c453c5..d579da1f5b3 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/a11y/AccessibleBase.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/a11y/AccessibleBase.m @@ -40,7 +40,7 @@ + (void) initializeRolesMap { * All JavaFX roles and corresponding available properties are defined in * enum javafx.scene.AccessibleRole */ - rolesMap = [[NSMutableDictionary alloc] initWithCapacity:9]; + rolesMap = [[NSMutableDictionary alloc] initWithCapacity:11]; [rolesMap setObject:@"JFXButtonAccessibility" forKey:@"BUTTON"]; [rolesMap setObject:@"JFXButtonAccessibility" forKey:@"DECREMENT_BUTTON"]; @@ -53,7 +53,8 @@ + (void) initializeRolesMap { [rolesMap setObject:@"JFXCheckboxAccessibility" forKey:@"CHECK_BOX"]; [rolesMap setObject:@"JFXCheckboxAccessibility" forKey:@"TOGGLE_BUTTON"]; [rolesMap setObject:@"JFXStaticTextAccessibility" forKey:@"TEXT"]; - + [rolesMap setObject:@"JFXNavigableTextAccessibility" forKey:@"TEXT_FIELD"]; + [rolesMap setObject:@"JFXNavigableTextAccessibility" forKey:@"TEXT_AREA"]; } + (Class) getComponentAccessibilityClass:(NSString *)role @@ -62,6 +63,8 @@ + (Class) getComponentAccessibilityClass:(NSString *)role [self initializeRolesMap]; } + NSLog(@"Role requested: %@", role); + NSString *className = [rolesMap objectForKey:role]; if (className != nil) { return NSClassFromString(className); @@ -95,6 +98,16 @@ - (jobject)getJAccessible return self->jAccessible; } +- (NSString *)getJavaRole +{ + return self->jRole; +} + +- (void)setJavaRole:(NSString *)newRole +{ + self->jRole = [newRole copy]; +} + - (id)accessibilityValue { jobject jresult = NULL; @@ -129,6 +142,27 @@ - (id)accessibilityParent return parent; } +- (NSArray *)accessibilityChildren +{ + NSLog(@"%@ requesting children list", [self accessibilityRole]); + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return NULL; + jresult = (jobject)(*env)->CallLongMethod(env, self->jAccessible, jAccessibilityAttributeValue, + (jlong) @"AXChildren"); + GLASS_CHECK_EXCEPTION(env); + NSArray * result = variantToID(env, jresult); + for (int i = 0; i < [result count]; i++) { + if ([result[i] respondsToSelector:@selector(roleAndValue)]) { + NSLog(@"child[%d] = %@", i, [result[i] roleAndValue]); + } else { + NSLog(@"child[%d] = %@", i, result[i]); + } + } + + return result; +} + // Actions support - (BOOL)performAccessibleAction:(NSString *)action { @@ -192,6 +226,29 @@ - (void)setAccessibilityFocused:(BOOL)value GLASS_CHECK_EXCEPTION(env); } +- (id)accessibilityHitTest:(NSPoint)point +{ + id result = NULL; + GET_MAIN_JENV; + if (env == NULL) return NULL; + result = (id)(*env)->CallLongMethod(env, self->jAccessible, jAccessibilityHitTest, point.x, point.y); + GLASS_CHECK_EXCEPTION(env); + NSLog(@"NEW!!! accessibilityHitTest(%@) returns %@", [NSValue valueWithPoint:point], result); + return result; +} + +/* + * Debug related methods + */ + +- (NSString *)roleAndValue +{ + NSString *retVal = [NSString stringWithFormat:@"New Role: %@ Value:%@", + [self accessibilityRole], + [self accessibilityValue]]; + return retVal; +} + @end /* @@ -206,6 +263,9 @@ - (void)setAccessibilityFocused:(BOOL)value Class classType = [AccessibleBase getComponentAccessibilityClass:roleName]; NSObject* accessible = NULL; accessible = [[classType alloc] initWithEnv: env accessible: jAccessible]; + if ([accessible isKindOfClass:[AccessibleBase class]]) { + [(AccessibleBase *)accessible setJavaRole:roleName]; + } return ptr_to_jlong(accessible); } @@ -248,3 +308,6 @@ - (void)setAccessibilityFocused:(BOOL)value [((AccessibleBase*) accessible) clearParent]; } } + + + diff --git a/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXNavigableTextAccessibility.h b/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXNavigableTextAccessibility.h new file mode 100644 index 00000000000..7fc98d97bc6 --- /dev/null +++ b/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXNavigableTextAccessibility.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#import "AccessibleBase.h" +#import +//#import "JFXStaticTextAccessibility.h" + +@interface JFXNavigableTextAccessibility : AccessibleBase { + +}; +- (NSAccessibilityRole)accessibilityRole; +- (NSAccessibilitySubrole)accessibilitySubrole; +- (BOOL)isAccessibilityEnabled; +- (NSArray *)accessibilitySharedFocusElements; +- (BOOL)isAccessibilityEdited; +- (NSArray *)accessibilityChildren; +- (NSString *)accessibilityValue; +- (NSRange)accessibilitySelectedTextRange; +//- (NSString *)accessibilitySelectedText; +- (NSRange)accessibilityVisibleCharacterRange; +- (NSAttributedString *) accessibilityAttributedStringForRange:(NSRange)range; +- (NSInteger)accessibilityNumberOfCharacters; +@end diff --git a/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXNavigableTextAccessibility.m b/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXNavigableTextAccessibility.m new file mode 100644 index 00000000000..7402bfc77a4 --- /dev/null +++ b/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXNavigableTextAccessibility.m @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#import "JFXNavigableTextAccessibility.h" +#import "GlassMacros.h" +#import "GlassAccessible.h" +#import "com_sun_glass_ui_mac_MacAccessible.h" +#import "com_sun_glass_ui_mac_MacVariant.h" +#import "common.h" + +@implementation JFXNavigableTextAccessibility +- (NSAccessibilityRole)accessibilityRole +{ +// NSLog(@"Asked for the role for %@", [self getJavaRole]); + if ([@"TEXT_FIELD" isEqualToString:[self getJavaRole]]) { + return NSAccessibilityTextFieldRole; + } + return NSAccessibilityTextAreaRole; +} + +- (NSAccessibilitySubrole)accessibilitySubrole +{ + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return NULL; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValue, (jlong)@"AXSubrole"); + GLASS_CHECK_EXCEPTION(env); + if (variantToID(env, jresult) == NULL) { + return NULL; + } + return NSAccessibilitySecureTextFieldSubrole; +} + +- (id)accessibilityParent +{ + return [super accessibilityParent]; +} + +- (BOOL)isAccessibilityEnabled +{ + return TRUE; +} + +- (NSArray *)accessibilitySharedFocusElements +{ + return NULL; +} + +- (BOOL)isAccessibilityEdited { + return TRUE; +} + +- (NSArray *)accessibilityChildren +{ + return [super accessibilityChildren]; +// return NULL; +} + +- (NSRect)accessibilityFrame +{ + return [super accessibilityFrame]; +} + +- (NSString *)accessibilityValue +{ + NSLog(@"Getting value as %@", [super accessibilityValue]); + return [super accessibilityValue]; +} + +- (nullable NSString *)accessibilityStringForRange:(NSRange)range +{ + id parameter = [NSValue valueWithRange:range]; + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return NULL; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValueForParameter, + (jlong)@"AXStringForRange", (jlong)parameter); + GLASS_CHECK_EXCEPTION(env); + NSString * ret = (NSString *)variantToID(env, jresult); + NSLog(@"accessibilityStringForRange(%@) returns %@", parameter, ret); + return ret; +} + +- (NSInteger)accessibilityLineForIndex:(NSInteger)index +{ + id parameter = [NSNumber numberWithInteger:index]; + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return 0; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValueForParameter, + (jlong)@"AXLineForIndex", (jlong)parameter); + GLASS_CHECK_EXCEPTION(env); + NSInteger ret = [variantToID(env, jresult) integerValue]; + NSLog(@"accessibilityLineForIndex(%@) returns %@", parameter, variantToID(env, jresult)); + return ret; +} + +- (NSRange)accessibilityRangeForLine:(NSInteger)lineNumber +{ + id parameter = [NSNumber numberWithInteger:lineNumber]; + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return EmptyRange; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValueForParameter, + (jlong)@"AXRangeForLine", (jlong)parameter); + GLASS_CHECK_EXCEPTION(env); + NSRange ret = [variantToID(env, jresult) rangeValue]; + NSLog(@"accessibilityRangeForLine(%@) returns %@", parameter, variantToID(env, jresult)); + return ret; +} + +- (NSRect)accessibilityFrameForRange:(NSRange)range +{ + id parameter = [NSValue valueWithRange:range]; + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return NSZeroRect; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValueForParameter, + (jlong)@"AXBoundsForRange", (jlong)parameter); + GLASS_CHECK_EXCEPTION(env); + NSLog(@"accessibilityFrameForRange(%@) returns %@", parameter, variantToID(env, jresult)); + return [variantToID(env, jresult) rectValue]; +} + +- (NSInteger)accessibilityNumberOfCharacters +{ + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return 0; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValue, (jlong)@"AXNumberOfCharacters"); + GLASS_CHECK_EXCEPTION(env); + NSLog(@"accessibilityNumberOfCharacters returns %@", variantToID(env, jresult)); + return [variantToID(env, jresult) integerValue]; +} + +- (NSRange)accessibilitySelectedTextRange +{ + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return EmptyRange; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValue, (jlong)@"AXSelectedTextRange"); + GLASS_CHECK_EXCEPTION(env); + NSRange ret = [variantToID(env, jresult) rangeValue]; + NSLog(@"accessibilitySelectedTextRange returns %@", variantToID(env, jresult)); + return ret; +} + +//- (NSString *)accessibilitySelectedText +//{ +// id parameter = [NSValue valueWithRange:[self accessibilitySelectedTextRange]]; +// jobject jresult = NULL; +// GET_MAIN_JENV; +// if (env == NULL) return NULL; +// jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], +// jAccessibilityAttributeValueForParameter, +// (jlong)@"AXStringForRange", (jlong)parameter); +// GLASS_CHECK_EXCEPTION(env); +// NSString * ret = (NSString *)variantToID(env, jresult); +// NSLog(@"accessibilitySelectedText returns %@", ret); +// return ret; +//} + +- (NSRange)accessibilityVisibleCharacterRange +{ + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return NSMakeRange(0, 0);; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValue, (jlong)@"AXVisibleCharacterRange"); + GLASS_CHECK_EXCEPTION(env); + return [variantToID(env, jresult) rangeValue]; +} + +- (NSAttributedString *) accessibilityAttributedStringForRange:(NSRange)range +{ + id parameter = [NSValue valueWithRange:range]; + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return NULL; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValueForParameter, + (jlong)@"AXAttributedStringForRange", (jlong)parameter); + GLASS_CHECK_EXCEPTION(env); + NSAttributedString * retval = variantToID(env, jresult); + NSLog(@"Returning attributedSubString: %@", retval); + return retval; +} + +@end \ No newline at end of file diff --git a/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXStaticTextAccessibility.h b/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXStaticTextAccessibility.h index 8e6164e1a5f..e5298698282 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXStaticTextAccessibility.h +++ b/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXStaticTextAccessibility.h @@ -31,5 +31,7 @@ }; - (NSAccessibilityRole)accessibilityRole; - (NSString *)accessibilityValue; +- (NSRange)accessibilityVisibleCharacterRange; +- (NSAttributedString *) accessibilityAttributedStringForRange:(NSRange) range; @end diff --git a/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXStaticTextAccessibility.m b/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXStaticTextAccessibility.m index 56044f92aa8..d6a6403fd3c 100644 --- a/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXStaticTextAccessibility.m +++ b/modules/javafx.graphics/src/main/native-glass/mac/a11y/JFXStaticTextAccessibility.m @@ -48,7 +48,34 @@ - (NSRect)accessibilityFrame - (NSString *)accessibilityValue { - return [super accessibilityValue]; + NSString *val = [super accessibilityValue]; + return val; +} + +- (NSRange)accessibilityVisibleCharacterRange +{ + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return NSMakeRange(0, 0);; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValue, (jlong)@"AXVisibleCharacterRange"); + GLASS_CHECK_EXCEPTION(env); + return [variantToID(env, jresult) rangeValue]; +} + +- (NSAttributedString *) accessibilityAttributedStringForRange:(NSRange)range +{ + id parameter = [NSValue valueWithRange:range]; + jobject jresult = NULL; + GET_MAIN_JENV; + if (env == NULL) return NULL; + jresult = (jobject)(*env)->CallLongMethod(env, [self getJAccessible], + jAccessibilityAttributeValueForParameter, + (jlong)@"AXAttributedStringForRange", (jlong)parameter); + GLASS_CHECK_EXCEPTION(env); + NSAttributedString * retval = variantToID(env, jresult); + NSLog(@"Returning attributedSubString: %@", retval); + return retval; } @end