diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.h b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.h index ebf314c7394..9a528879a5d 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.h +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, JetBrains s.r.o.. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -29,5 +29,6 @@ @interface NavigableTextAccessibility : CommonComponentAccessibility @property(readonly) BOOL accessibleIsPasswordText; +@property BOOL announceEditUpdates; @end diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.m index 138d502f10f..8e241e65b96 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, JetBrains s.r.o.. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -60,6 +60,22 @@ static jmethodID sjm_getAccessibleEditableText = NULL; return [fJavaRole isEqualToString:@"passwordtext"]; } +- (id)init { + self = [super init]; + if (self) { + _announceEditUpdates = YES; + } + return self; +} + +- (void)suppressEditUpdates { + _announceEditUpdates = NO; +} + +- (void)resumeEditUpdates { + _announceEditUpdates = YES; +} + // NSAccessibilityElement protocol methods - (NSRect)accessibilityFrameForRange:(NSRange)range @@ -117,6 +133,9 @@ static jmethodID sjm_getAccessibleEditableText = NULL; - (NSString *)accessibilityStringForRange:(NSRange)range { + if (!_announceEditUpdates) { + return @""; + } JNIEnv *env = [ThreadUtilities getJNIEnv]; GET_CACCESSIBLETEXT_CLASS_RETURN(nil); DECLARE_STATIC_METHOD_RETURN(jm_getStringForRange, sjc_CAccessibleText, "getStringForRange", @@ -306,6 +325,12 @@ static jmethodID sjm_getAccessibleEditableText = NULL; return [super accessibilityParent]; } +- (void)postSelectedTextChanged +{ + [super postSelectedTextChanged]; + [self resumeEditUpdates]; +} + /* * Other text methods - (NSRange)accessibilitySharedCharacterRange; diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/SpinboxAccessibility.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/SpinboxAccessibility.m index 4dac6bd93f9..0cec7f3eb2c 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/SpinboxAccessibility.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/SpinboxAccessibility.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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 @@ -24,6 +24,7 @@ */ #import "SpinboxAccessibility.h" +#import "ThreadUtilities.h" #define INCREMENT 0 #define DECREMENT 1 @@ -44,7 +45,15 @@ - (id _Nullable)accessibilityValue { - return [super accessibilityValue]; + id val = [super accessibilityValue]; + NSArray *clist = [super accessibilityChildren]; + for (NSUInteger i = 0; i < [clist count]; i++) { + id child = [clist objectAtIndex:i]; + if ([child conformsToProtocol:@protocol(NSAccessibilityNavigableStaticText)]) { + val = [child accessibilityValue]; + } + } + return val; } - (BOOL)accessibilityPerformIncrement @@ -68,4 +77,18 @@ return [super accessibilityParent]; } +- (void)postValueChanged +{ + AWT_ASSERT_APPKIT_THREAD; + NSAccessibilityPostNotification(self, NSAccessibilityValueChangedNotification); + NSArray *clist = [super accessibilityChildren]; + for (NSUInteger i = 0; i < [clist count]; i++) { + id child = [clist objectAtIndex:i]; + if ([child conformsToProtocol:@protocol(NSAccessibilityNavigableStaticText)]) { + NSAccessibilityPostNotification(child, NSAccessibilityLayoutChangedNotification); + [child suppressEditUpdates]; + } + } +} + @end diff --git a/test/jdk/javax/accessibility/JSpinner/CustomSpinnerAccessibilityTest.java b/test/jdk/javax/accessibility/JSpinner/CustomSpinnerAccessibilityTest.java new file mode 100644 index 00000000000..12c98c95110 --- /dev/null +++ b/test/jdk/javax/accessibility/JSpinner/CustomSpinnerAccessibilityTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2026, 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. + * + * 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 java.awt.GridLayout; +import java.lang.reflect.InvocationTargetException; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JSpinner; +import javax.swing.SpinnerListModel; + +/* + * @test + * @bug 8286258 + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @requires (os.family == "mac") + * @summary Checks that JSpinner with custom model announces + * the value every time it is changed + * @run main/manual CustomSpinnerAccessibilityTest + */ + +public class CustomSpinnerAccessibilityTest extends JPanel { + private static final String INSTRUCTIONS = """ + 1. Turn on VoiceOver + 2. In the window named "Test UI" click on the text editor inside the + spinner component + 3. Using up and down arrows change current month + 4. Wait for the VoiceOver to finish speaking + 5. Repeat steps 3 and 4 couple more times + + If every time value of the spinner is changed VoiceOver + announces the new value click "Pass". + If instead the value is narrated only partially + and the new value is never fully narrated press "Fail". + """; + + public CustomSpinnerAccessibilityTest() { + super(new GridLayout(0, 2)); + String[] monthStrings = new java.text.DateFormatSymbols().getMonths(); + int lastIndex = monthStrings.length - 1; + if (monthStrings[lastIndex] == null + || monthStrings[lastIndex].length() <= 0) { + String[] tmp = new String[lastIndex]; + System.arraycopy(monthStrings, 0, + tmp, 0, lastIndex); + monthStrings = tmp; + } + + SpinnerListModel model = new SpinnerListModel(monthStrings); + JLabel label = new JLabel("Month: "); + add(label); + JSpinner spinner = new JSpinner(model); + label.setLabelFor(spinner); + add(spinner); + } + + public static void main(String[] args) throws InterruptedException, + InvocationTargetException { + PassFailJFrame.builder() + .title("Custom Spinner Accessibility Test") + .instructions(INSTRUCTIONS) + .testUI(CustomSpinnerAccessibilityTest::new) + .build() + .awaitAndCheck(); + } +}