1/*
2 * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.lwawt.macosx;
27
28import java.awt.Component;
29import java.awt.Point;
30import java.awt.Rectangle;
31import java.awt.geom.Rectangle2D;
32import java.util.concurrent.Callable;
33
34import javax.accessibility.Accessible;
35import javax.accessibility.AccessibleContext;
36import javax.accessibility.AccessibleEditableText;
37import javax.accessibility.AccessibleText;
38import javax.swing.text.Element;
39import javax.swing.text.JTextComponent;
40
41class CAccessibleText {
42    static AccessibleEditableText getAccessibleEditableText(final Accessible a, final Component c) {
43        if (a == null) return null;
44
45        return CAccessibility.invokeAndWait(new Callable<AccessibleEditableText>() {
46            public AccessibleEditableText call() throws Exception {
47                final AccessibleContext ac = a.getAccessibleContext();
48                if (ac == null) return null;
49                return ac.getAccessibleEditableText();
50            }
51        }, c);
52    }
53
54    static String getSelectedText(final Accessible a, final Component c) {
55        if (a == null) return null;
56
57        return CAccessibility.invokeAndWait(new Callable<String>() {
58            public String call() throws Exception {
59                final AccessibleContext ac = a.getAccessibleContext();
60                if (ac == null) return null;
61
62                final AccessibleText at = ac.getAccessibleText();
63                if (at == null) return null;
64
65                return at.getSelectedText();
66            }
67        }, c);
68    }
69
70    // replace the currently selected text with newText
71    static void setSelectedText(final Accessible a, final Component c, final String newText) {
72        if (a == null) return;
73
74        CAccessibility.invokeLater(new Runnable() {
75            public void run() {
76                final AccessibleContext ac = a.getAccessibleContext();
77                if (ac == null) return;
78
79                final AccessibleEditableText aet = ac.getAccessibleEditableText();
80                if (aet == null) return;
81
82                final int selectionStart = aet.getSelectionStart();
83                final int selectionEnd = aet.getSelectionEnd();
84                aet.replaceText(selectionStart, selectionEnd, newText);
85            }
86        }, c);
87    }
88
89    static void setSelectedTextRange(final Accessible a, final Component c, final int startIndex, final int endIndex) {
90        if (a == null) return;
91
92        CAccessibility.invokeLater(new Runnable() {
93            public void run() {
94                final AccessibleContext ac = a.getAccessibleContext();
95                if (ac == null) return;
96
97                final AccessibleEditableText aet = ac.getAccessibleEditableText();
98                if (aet == null) return;
99
100                final boolean validRange = (startIndex >= 0) && (endIndex >= startIndex) && (endIndex <= aet.getCharCount());
101                if (!validRange) return;
102
103                aet.selectText(startIndex, endIndex);
104            }
105        }, c);
106    }
107
108    static String getTextRange(final AccessibleEditableText aet, final int start, final int stop, final Component c) {
109        if (aet == null) return null;
110
111        return CAccessibility.invokeAndWait(new Callable<String>() {
112            public String call() throws Exception {
113                return aet.getTextRange(start, stop);
114            }
115        }, c);
116    }
117
118    static int getCharacterIndexAtPosition(final Accessible a, final Component c, final int x, final int y) {
119        if (a == null) return 0;
120
121        return CAccessibility.invokeAndWait(new Callable<Integer>() {
122            public Integer call() throws Exception {
123                final AccessibleContext ac = a.getAccessibleContext();
124                if (ac == null) return null;
125                final AccessibleText at = ac.getAccessibleText();
126                if (at == null) return null;
127                // (x, y) passed in as java screen coords - (0, 0) at upper-left corner of screen.
128                // Convert to java component-local coords
129                final Point componentLocation = ac.getAccessibleComponent().getLocationOnScreen();
130                final int localX = x - (int)componentLocation.getX();
131                final int localY = y - (int)componentLocation.getY();
132
133                return at.getIndexAtPoint(new Point(localX, localY));
134            }
135        }, c);
136    }
137
138    static int[] getSelectedTextRange(final Accessible a, final Component c) {
139        if (a == null) return new int[2];
140
141        return CAccessibility.invokeAndWait(new Callable<int[]>() {
142            public int[] call() {
143                final AccessibleContext ac = a.getAccessibleContext();
144                if (ac == null) return new int[2];
145
146                final AccessibleText at = ac.getAccessibleText();
147                if (at == null) return new int[2];
148
149                final int[] ret = new int[2];
150                ret[0] = at.getSelectionStart();
151                ret[1] = at.getSelectionEnd();
152                return ret;
153            }
154        }, c);
155    }
156
157
158    static int[] getVisibleCharacterRange(final Accessible a, final Component c) {
159        if (a == null) return null;
160        return CAccessibility.invokeAndWait(new Callable<int[]>() {
161            public int[] call() {
162                return getVisibleCharacterRange(a);
163            }
164        }, c);
165    }
166
167    static int getLineNumberForIndex(final Accessible a, final Component c, final int index) {
168        if (a == null) return 0;
169        return CAccessibility.invokeAndWait(new Callable<Integer>() {
170            public Integer call() {
171                return Integer.valueOf(getLineNumberForIndex(a, index));
172            }
173        }, c);
174    }
175
176    static int getLineNumberForInsertionPoint(final Accessible a, final Component c) {
177        if (a == null) return 0;
178        return CAccessibility.invokeAndWait(new Callable<Integer>() {
179            public Integer call() {
180                return Integer.valueOf(getLineNumberForInsertionPoint(a));
181            }
182        }, c);
183    }
184
185    static int[] getRangeForLine(final Accessible a, final Component c, final int line) {
186        if (a == null) return null;
187        return CAccessibility.invokeAndWait(new Callable<int[]>() {
188            public int[] call() {
189                return getRangeForLine(a, line);
190            }
191        }, c);
192    }
193
194    static int[] getRangeForIndex(final Accessible a, final Component c, final int index) {
195        if (a == null) return new int[2];
196
197        return CAccessibility.invokeAndWait(new Callable<int[]>() {
198            public int[] call() {
199                final AccessibleContext ac = a.getAccessibleContext();
200                if (ac == null) return new int[2];
201
202                final AccessibleEditableText aet = ac.getAccessibleEditableText();
203                if (aet == null) return new int[2];
204
205                final int charCount = aet.getCharCount();
206                if (index >= charCount) return new int[2];
207
208                final String foundWord = aet.getAtIndex(AccessibleText.WORD, index);
209                final int foundWordLength = foundWord.length();
210                final String wholeString = aet.getTextRange(0, charCount - 1);
211
212                // now we need to find the index of the foundWord in wholeString. It's somewhere pretty close to the passed-in index,
213                // but we don't know if it's before or after the passed-in index. So, look behind and ahead of the passed-in index
214                int foundWordIndex = -1;
215                int offset = 0;
216                while ((foundWordIndex == -1) && (offset < foundWordLength)) {
217                    if (wholeString.regionMatches(true, index - offset, foundWord, 0, foundWordLength)) {
218                        // is the index of foundWord to the left of the passed-in index?
219                        foundWordIndex = index - offset;
220                    }
221                    if (wholeString.regionMatches(true, index + offset, foundWord, 0, foundWordLength)) {
222                        // is the index of the foundWord to the right of the passed-in index?
223                        foundWordIndex = index + offset;
224                    }
225                    offset++;
226                }
227
228                final int[] ret = new int[2];
229                ret[0] = foundWordIndex;
230                ret[1] = foundWordIndex + foundWordLength;
231                return ret;
232            }
233        }, c);
234    }
235
236    // cmcnote: this method does not currently work for JLabels. JLabels, for some reason unbeknownst to me, do not
237    // return a value from getAccessibleText. According to the javadocs, AccessibleJLabels implement AccessibleText,
238    // so this doesn't really make sense. Perhaps a sun bug? Investigate. We currently get the text value out of labels
239    // via "getAccessibleName". This just returns a String - so we don't know it's position, etc, as we do for
240    // AccessibleText.
241    static double[] getBoundsForRange(final Accessible a, final Component c, final int location, final int length) {
242        final double[] ret = new double[4];
243        if (a == null) return ret;
244
245        return CAccessibility.invokeAndWait(new Callable<double[]>() {
246            public double[] call() throws Exception {
247                final AccessibleContext ac = a.getAccessibleContext();
248                if (ac == null) return ret;
249
250                final AccessibleText at = ac.getAccessibleText();
251                if (at == null) {
252                    ac.getAccessibleName();
253                    ac.getAccessibleEditableText();
254                    return ret;
255                }
256
257                final Rectangle2D boundsStart = at.getCharacterBounds(location);
258                final Rectangle2D boundsEnd = at.getCharacterBounds(location + length - 1);
259                if (boundsEnd == null || boundsStart == null) return ret;
260                final Rectangle2D boundsUnion = boundsStart.createUnion(boundsEnd);
261                if (boundsUnion.isEmpty()) return ret;
262
263                final double localX = boundsUnion.getX();
264                final double localY = boundsUnion.getY();
265
266                final Point componentLocation = ac.getAccessibleComponent().getLocationOnScreen();
267                if (componentLocation == null) return ret;
268
269                final double screenX = componentLocation.getX() + localX;
270                final double screenY = componentLocation.getY() + localY;
271
272                ret[0] = screenX;
273                ret[1] = screenY; // in java screen coords (from top-left corner of screen)
274                ret[2] = boundsUnion.getWidth();
275                ret[3] = boundsUnion.getHeight();
276                return ret;
277            }
278        }, c);
279    }
280
281    static String getStringForRange(final Accessible a, final Component c, final int location, final int length) {
282        if (a == null) return null;
283        return CAccessibility.invokeAndWait(new Callable<String>() {
284            public String call() throws Exception {
285                final AccessibleContext ac = a.getAccessibleContext();
286                if (ac == null) return null;
287
288                final AccessibleEditableText aet = ac.getAccessibleEditableText();
289                if (aet == null) return null;
290
291                return aet.getTextRange(location, location + length);
292            }
293        }, c);
294    }
295
296    @SuppressWarnings("deprecation")
297    static int[] getVisibleCharacterRange(final Accessible a) {
298        final Accessible sa = CAccessible.getSwingAccessible(a);
299        if (!(sa instanceof JTextComponent)) return null;
300
301        final JTextComponent jc = (JTextComponent) sa;
302        final Rectangle rect = jc.getVisibleRect();
303        final Point topLeft = new Point(rect.x, rect.y);
304        final Point topRight = new Point(rect.x + rect.width, rect.y);
305        final Point bottomLeft = new Point(rect.x, rect.y + rect.height);
306        final Point bottomRight = new Point(rect.x + rect.width, rect.y + rect.height);
307
308        int start = Math.min(jc.viewToModel(topLeft), jc.viewToModel(topRight));
309        int end = Math.max(jc.viewToModel(bottomLeft), jc.viewToModel(bottomRight));
310        if (start < 0) start = 0;
311        if (end < 0) end = 0;
312        return new int[] { start, end };
313    }
314
315    static int getLineNumberForIndex(final Accessible a, int index) {
316        final Accessible sa = CAccessible.getSwingAccessible(a);
317        if (!(sa instanceof JTextComponent)) return -1;
318
319        final JTextComponent jc = (JTextComponent) sa;
320        final Element root = jc.getDocument().getDefaultRootElement();
321
322        // treat -1 special, returns the current caret position
323        if (index == -1) index = jc.getCaretPosition();
324
325        // Determine line number (can be -1)
326        return root.getElementIndex(index);
327    }
328
329    static int getLineNumberForInsertionPoint(final Accessible a) {
330        return getLineNumberForIndex(a, -1); // uses special -1 for above
331    }
332
333    static int[] getRangeForLine(final Accessible a, final int lineIndex) {
334        Accessible sa = CAccessible.getSwingAccessible(a);
335        if (!(sa instanceof JTextComponent)) return null;
336
337        final JTextComponent jc = (JTextComponent) sa;
338        final Element root = jc.getDocument().getDefaultRootElement();
339        final Element line = root.getElement(lineIndex);
340        if (line == null) return null;
341
342        return new int[] { line.getStartOffset(), line.getEndOffset() };
343    }
344}
345