EventQueueMonitor.java revision 13430:5e8370fb3ed9
1/*
2 * Copyright (c) 2002, 2015, 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 com.sun.java.accessibility.util;
27
28import java.util.*;
29import java.awt.*;
30import java.awt.event.*;
31import javax.accessibility.*;
32import java.security.AccessController;
33import java.security.PrivilegedAction;
34
35/**
36 * The {@code EventQueueMonitor} class provides key core functionality for Assistive
37 * Technologies (and other system-level technologies that need some of the same
38 * things that Assistive Technology needs).
39 *
40 * @see AWTEventMonitor
41 * @see SwingEventMonitor
42 */
43public class EventQueueMonitor
44        implements AWTEventListener {
45
46    // NOTE:  All of the following properties are static.  The reason
47    //        for this is that there may be multiple EventQueue instances
48    //        in use in the same VM.  By making these properties static,
49    //        we can guarantee we get the information from all of the
50    //        EventQueue instances.
51
52    // The stuff that is cached.
53    //
54    static Vector<Container>topLevelWindows = new Vector<>();
55    static Window topLevelWindowWithFocus  = null;
56    static Point currentMousePosition      = null;
57    static Component currentMouseComponent = null;
58
59    // Low-level listener interfaces
60    //
61    static GUIInitializedListener guiInitializedListener = null;
62    static TopLevelWindowListener topLevelWindowListener = null;
63    static MouseMotionListener    mouseMotionListener    = null;
64
65    /**
66     * Class variable stating whether the assistive technologies have
67     * been loaded yet or not.  The assistive technologies won't be
68     * loaded until the first event is posted to the EventQueue.  This
69     * gives the toolkit a chance to do all the necessary initialization
70     * it needs to do.
71     */
72
73    /**
74     * Class variable stating whether the GUI subsystem has been initialized
75     * or not.
76     *
77     * @see #isGUIInitialized
78     */
79    static boolean guiInitialized = false;
80
81    /**
82     * Queue that holds events for later processing.
83     */
84    static EventQueueMonitorItem componentEventQueue = null;
85
86    /**
87     * Class that tells us what the component event dispatch thread is.
88     */
89    static private ComponentEvtDispatchThread cedt = null;
90
91    /**
92     * Handle the synchronization between the thing that populates the
93     * component event dispatch thread ({@link #queueComponentEvent})
94     * and the thing that processes the events ({@link ComponentEvtDispatchThread}).
95     */
96    static Object componentEventQueueLock = new Object();
97
98    /**
99     * Create a new {@code EventQueueMonitor} instance.  Normally, this will
100     * be called only by the AWT Toolkit during initialization time.
101     * Assistive technologies should not create instances of
102     * EventQueueMonitor by themselves.  Instead, they should either
103     * refer to it directly via the static methods in this class, e.g.,
104     * {@link #getCurrentMousePosition} or obtain the instance by asking the
105     * Toolkit, e.g., {@link java.awt.Toolkit#getSystemEventQueue}.
106     */
107    public EventQueueMonitor() {
108        if (cedt == null) {
109            cedt = new ComponentEvtDispatchThread("EventQueueMonitor-ComponentEvtDispatch");
110
111            cedt.setDaemon(true);
112            cedt.start();
113        }
114    }
115
116    /**
117     * Queue up a {@link java.awt.event.ComponentEvent ComponentEvent} for later
118     * processing by the {@link ComponentEvtDispatch} thread.
119     *
120     * @param e a {@code ComponentEvent}
121     */
122    static void queueComponentEvent(ComponentEvent e) {
123        synchronized(componentEventQueueLock) {
124            EventQueueMonitorItem eqi = new EventQueueMonitorItem(e);
125            if (componentEventQueue == null) {
126                componentEventQueue = eqi;
127            } else {
128                EventQueueMonitorItem q = componentEventQueue;
129                while (true) {
130                    if (q.next != null) {
131                        q = q.next;
132                    } else {
133                        break;
134                    }
135                }
136                q.next = eqi;
137            }
138            componentEventQueueLock.notifyAll();
139        }
140    }
141
142    /**
143     * Tell the {@code EventQueueMonitor} to start listening for events.
144     */
145    public static void maybeInitialize() {
146        if (cedt == null) {
147            java.security.AccessController.doPrivileged(
148                new java.security.PrivilegedAction<Void>() {
149                    public Void run() {
150                        try {
151                            long eventMask = AWTEvent.WINDOW_EVENT_MASK |
152                                AWTEvent.FOCUS_EVENT_MASK |
153                                AWTEvent.MOUSE_MOTION_EVENT_MASK;
154
155                            Toolkit.getDefaultToolkit().addAWTEventListener(new EventQueueMonitor(), eventMask);
156                        } catch (Exception e) {
157                        }
158                        return null;
159                    }
160                }
161            );
162        }
163    }
164
165    /**
166     * Handle events as a result of registering a listener
167     * on the {@link java.awt.EventQueue EventQueue} in {@link #maybeInitialize}.
168     */
169    public void eventDispatched(AWTEvent theEvent) {
170        processEvent(theEvent);
171    }
172
173    /**
174     * Assisitive technologies that have
175     * registered a {@link GUIInitializedListener} will be notified.
176     *
177     * @see #addGUIInitializedListener
178     */
179    static void maybeNotifyAssistiveTechnologies() {
180
181        if (!guiInitialized) {
182            guiInitialized = true;
183            if (guiInitializedListener != null) {
184                guiInitializedListener.guiInitialized();
185            }
186        }
187
188    }
189
190    /********************************************************************/
191    /*                                                                  */
192    /* Package Private Methods                                          */
193    /*                                                                  */
194    /********************************************************************/
195
196    /**
197     * Add a Container to the list of top-level containers
198     * in the cache.  This follows the object's hierarchy up the
199     * tree until it finds the top most parent.  If the parent is
200     * not already in the list of Containers, it adds it to the list.
201     *
202     * @param c the Container
203     */
204    static void addTopLevelWindow(Component c) {
205        Container parent;
206
207        if (c == null) {
208            return;
209        }
210
211        if (!(c instanceof Window)) {
212            addTopLevelWindow(c.getParent());
213            return;
214        }
215
216        if ((c instanceof Dialog) || (c instanceof Window)) {
217            parent = (Container) c;
218        } else {
219            parent = c.getParent();
220            if (parent != null) {
221                addTopLevelWindow(parent);
222                return;
223            }
224        }
225
226        if (parent == null) {
227            parent = (Container) c;
228        }
229
230        // Because this method is static, do not make it synchronized because
231        // it can lock the whole class.  Instead, just lock what needs to be
232        // locked.
233        //
234        synchronized (topLevelWindows) {
235            if ((parent != null) && !topLevelWindows.contains(parent)) {
236                topLevelWindows.addElement(parent);
237                if (topLevelWindowListener != null) {
238                    topLevelWindowListener.topLevelWindowCreated((Window) parent);
239                }
240            }
241        }
242    }
243
244    /**
245     * Removes a container from the list of top level containers in the cache.
246     *
247     * @param c the top level container to remove
248     */
249    static void removeTopLevelWindow(Window w) {
250
251        // Because this method is static, do not make it synchronized because
252        // it can lock the whole class.  Instead, just lock what needs to be
253        // locked.
254        //
255        synchronized (topLevelWindows) {
256            if (topLevelWindows.contains(w)) {
257                topLevelWindows.removeElement(w);
258                if (topLevelWindowListener != null) {
259                    topLevelWindowListener.topLevelWindowDestroyed(w);
260                }
261            }
262        }
263    }
264
265    /**
266     * Update current mouse position.
267     *
268     * @param mouseEvent the MouseEvent that holds the new mouse position.
269     */
270    static void updateCurrentMousePosition(MouseEvent mouseEvent) {
271        Point oldMousePos = currentMousePosition;
272        // Be careful here.  The component in the event might be
273        // hidden by the time we process the event.
274        try {
275            Point eventPoint      = mouseEvent.getPoint();
276            currentMouseComponent = (Component) (mouseEvent.getSource());
277            currentMousePosition  = currentMouseComponent.getLocationOnScreen();
278            currentMousePosition.translate(eventPoint.x,eventPoint.y);
279        } catch (Exception e) {
280            currentMousePosition = oldMousePos;
281        }
282    }
283
284    /**
285     * Process the event.  This maintains the event cache in addition
286     * to calling all the registered listeners.  NOTE: The events that
287     * come through here are from peered Components.
288     *
289     * @param theEvent the AWTEvent
290     */
291    static void processEvent(AWTEvent theEvent) {
292        switch (theEvent.getID()) {
293        case MouseEvent.MOUSE_MOVED:
294        case MouseEvent.MOUSE_DRAGGED:
295        case FocusEvent.FOCUS_GAINED:
296        case WindowEvent.WINDOW_DEACTIVATED:
297            queueComponentEvent((ComponentEvent) theEvent);
298            break;
299
300        case WindowEvent.WINDOW_ACTIVATED:
301            // Dialogs fire WINDOW_ACTIVATED and FOCUS_GAINED events
302            // before WINDOW_OPENED so we need to add topLevelListeners
303            // for the dialog when it is first activated to get a
304            // focus gained event for the focus component in the dialog.
305            if (theEvent instanceof ComponentEvent) {
306                ComponentEvent ce = (ComponentEvent)theEvent;
307                if (ce.getComponent() instanceof Window) {
308                    EventQueueMonitor.addTopLevelWindow(ce.getComponent());
309                    EventQueueMonitor.maybeNotifyAssistiveTechnologies();
310                } else {
311                    EventQueueMonitor.maybeNotifyAssistiveTechnologies();
312                    EventQueueMonitor.addTopLevelWindow(ce.getComponent());
313                }
314            }
315            queueComponentEvent((ComponentEvent) theEvent);
316            break;
317
318            // handle WINDOW_OPENED and WINDOW_CLOSED events synchronously
319        case WindowEvent.WINDOW_OPENED:
320            if (theEvent instanceof ComponentEvent) {
321                ComponentEvent ce = (ComponentEvent)theEvent;
322                if (ce.getComponent() instanceof Window) {
323                    EventQueueMonitor.addTopLevelWindow(ce.getComponent());
324                    EventQueueMonitor.maybeNotifyAssistiveTechnologies();
325                } else {
326                    EventQueueMonitor.maybeNotifyAssistiveTechnologies();
327                    EventQueueMonitor.addTopLevelWindow(ce.getComponent());
328                }
329            }
330            break;
331        case WindowEvent.WINDOW_CLOSED:
332            if (theEvent instanceof ComponentEvent) {
333                ComponentEvent ce = (ComponentEvent)theEvent;
334                EventQueueMonitor.removeTopLevelWindow((Window) (ce.getComponent()));
335            }
336            break;
337
338        default:
339            break;
340        }
341    }
342
343    /**
344     * Internal test
345     */
346    static synchronized Component getShowingComponentAt(Container c, int x, int y) {
347        if (!c.contains(x, y)) {
348            return null;
349        }
350        int ncomponents = c.getComponentCount();
351        for (int i = 0 ; i < ncomponents ; i++) {
352            Component comp = c.getComponent(i);
353            if (comp != null && comp.isShowing()) {
354                Point location = comp.getLocation();
355                if (comp.contains(x - location.x, y - location.y)) {
356                    return comp;
357                }
358            }
359        }
360        return c;
361    }
362
363    /**
364     * Return the Component at the given Point on the screen in the
365     * given Container.
366     *
367     * @param c the Container to search
368     * @param p the Point in screen coordinates
369     * @return the Component at the given Point on the screen in the
370     * given Container -- can be null if no Component is at that Point
371     */
372    static synchronized Component getComponentAt(Container c, Point p) {
373        if (!c.isShowing()) {
374            return null;
375        }
376
377        Component comp;
378        Point containerLoc = c.getLocationOnScreen();
379        Point containerPoint = new Point(p.x - containerLoc.x,
380                                         p.y - containerLoc.y);
381
382        comp = getShowingComponentAt(c, containerPoint.x, containerPoint.y);
383
384        if ((comp != c) && (comp instanceof Container)) {
385            return getComponentAt((Container)comp,p);
386        } else {
387            return comp;
388        }
389    }
390
391    /**
392     * Obtain the {@link javax.accessibility.Accessible Accessible} object at the given point on the Screen.
393     * The return value may be null if an {@code Accessible} object cannot be
394     * found at the particular point.
395     *
396     * @param p the point to be accessed
397     * @return the {@code Accessible} at the specified point
398     */
399    static public Accessible getAccessibleAt(Point p) {
400        Window w = getTopLevelWindowWithFocus();
401        Window[] wins = getTopLevelWindows();
402        Component c = null;
403
404        // See if the point we're being asked about is the
405        // currentMousePosition.  If so, start with the component
406        // that we know the currentMousePostion is over
407        //
408        if (currentMousePosition == null) {
409            return null;
410        }
411        if (currentMousePosition.equals(p)) {
412            if (currentMouseComponent instanceof Container) {
413                c = getComponentAt((Container) currentMouseComponent, p);
414            }
415        }
416
417        // Try the window with focus next
418        //
419        if (c == null && w != null) {
420            c = getComponentAt(w,p);
421        }
422
423        // Try the other windows next.  [[[WDW: Stacking order???]]]
424        if (c == null) {
425            for (int i = 0; i < wins.length; i++) {
426                c = getComponentAt(wins[i],p);
427                if (c != null) {
428                    break;
429                }
430            }
431        }
432
433        if (c instanceof Accessible) {
434            AccessibleContext ac = ((Accessible) c).getAccessibleContext();
435            if (ac != null) {
436                AccessibleComponent acmp = ac.getAccessibleComponent();
437                if ((acmp != null) && (ac.getAccessibleChildrenCount() != 0)) {
438                    Point location = acmp.getLocationOnScreen();
439                    location.move(p.x - location.x, p.y - location.y);
440                    return acmp.getAccessibleAt(location);
441                }
442            }
443            return (Accessible) c;
444        } else {
445            return Translator.getAccessible(c);
446        }
447    }
448
449    /********************************************************************/
450    /*                                                                  */
451    /* Public Methods                                                   */
452    /*                                                                  */
453    /********************************************************************/
454
455    /**
456     * Says whether the GUI subsystem has been initialized or not.
457     * If this returns true, the assistive technology can freely
458     * create GUI component instances.  If the return value is false,
459     * the assistive technology should register a {@link GUIInitializedListener}
460     * and wait to create GUI component instances until the listener is
461     * called.
462     *
463     * @return true if the GUI subsystem has been initialized
464     * @see #addGUIInitializedListener
465     */
466    static public boolean isGUIInitialized() {
467        maybeInitialize();
468        return guiInitialized;
469    }
470
471    /**
472     * Adds the specified listener to be notified when the GUI subsystem
473     * is initialized.  Assistive technologies should get the results of
474     * {@link #isGUIInitialized} before calling this method.
475     *
476     * @param l the listener to add
477     * @see #isGUIInitialized
478     * @see #removeTopLevelWindowListener
479     */
480    static public void addGUIInitializedListener(GUIInitializedListener l) {
481        maybeInitialize();
482        guiInitializedListener =
483            GUIInitializedMulticaster.add(guiInitializedListener,l);
484    }
485
486    /**
487     * Removes the specified listener to be notified when the GUI subsystem
488     * is initialized.
489     *
490     * @param l the listener to remove
491     * @see #addGUIInitializedListener
492     */
493    static public void removeGUIInitializedListener(GUIInitializedListener l) {
494        guiInitializedListener =
495            GUIInitializedMulticaster.remove(guiInitializedListener,l);
496    }
497
498    /**
499     * Adds the specified listener to be notified when a top level window
500     * is created or destroyed.
501     *
502     * @param l the listener to add
503     * @see #removeTopLevelWindowListener
504     */
505    static public void addTopLevelWindowListener(TopLevelWindowListener l) {
506        topLevelWindowListener =
507            TopLevelWindowMulticaster.add(topLevelWindowListener,l);
508    }
509
510    /**
511     * Removes the specified listener to be notified when a top level window
512     * is created or destroyed.
513     *
514     * @param l the listener to remove
515     * @see #addTopLevelWindowListener
516     */
517    static public void removeTopLevelWindowListener(TopLevelWindowListener l) {
518        topLevelWindowListener =
519            TopLevelWindowMulticaster.remove(topLevelWindowListener,l);
520    }
521
522    /**
523     * Return the last recorded position of the mouse in screen coordinates.
524     *
525     * @return the last recorded position of the mouse in screen coordinates
526     */
527    static public Point getCurrentMousePosition() {
528        return currentMousePosition;
529    }
530
531    /**
532     * Return the list of top level Windows in use in the Java Virtual Machine.
533     *
534     * @return an array of top level {@code Window}s in use in the Java Virtual Machine
535     */
536    static public Window[] getTopLevelWindows() {
537
538        // Because this method is static, do not make it synchronized because
539        // it can lock the whole class.  Instead, just lock what needs to be
540        // locked.
541        //
542        synchronized (topLevelWindows) {
543            int count = topLevelWindows.size();
544            if (count > 0) {
545                Window[] w = new Window[count];
546                for (int i = 0; i < count; i++) {
547                    w[i] = (Window)topLevelWindows.elementAt(i);
548                }
549                return w;
550            } else {
551                return new Window[0];
552            }
553        }
554    }
555
556    /**
557     * Return the top level {@code Window} that currently has keyboard focus.
558     *
559     * @return the top level {@code Window} that currently has keyboard focus
560     */
561    static public Window getTopLevelWindowWithFocus() {
562        return topLevelWindowWithFocus;
563    }
564}
565
566/**
567 * Handle all Component events in a separate thread.  The reason for this is
568 * that WindowEvents tend to be used to do lots of processing on the Window
569 * hierarchy.  As a result, it can frequently result in deadlock situations.
570 */
571class ComponentEvtDispatchThread extends Thread {
572    public ComponentEvtDispatchThread(String name) {
573        super(name);
574    }
575    public void run() {
576        ComponentEvent ce = null;
577        while (true) {
578            synchronized(EventQueueMonitor.componentEventQueueLock) {
579                while (EventQueueMonitor.componentEventQueue == null) {
580                    try {
581                        EventQueueMonitor.componentEventQueueLock.wait();
582                    } catch (InterruptedException e) {
583                    }
584                }
585                ce = (ComponentEvent)EventQueueMonitor.componentEventQueue.event;
586                EventQueueMonitor.componentEventQueue =
587                    EventQueueMonitor.componentEventQueue.next;
588            }
589            switch (ce.getID()) {
590            case MouseEvent.MOUSE_MOVED:
591            case MouseEvent.MOUSE_DRAGGED:
592                EventQueueMonitor.updateCurrentMousePosition((MouseEvent) ce);
593                break;
594            case WindowEvent.WINDOW_ACTIVATED:
595                EventQueueMonitor.maybeNotifyAssistiveTechnologies();
596                EventQueueMonitor.topLevelWindowWithFocus = ((WindowEvent) ce).getWindow();
597                break;
598
599            default:
600                break;
601            }
602        }
603    }
604}
605
606/**
607 * EventQueueMonitorItem is the basic type that handles the
608 * queue for queueComponentEvent and the ComponentEvtDispatchThread.
609 */
610class EventQueueMonitorItem {
611    AWTEvent event;
612    EventQueueMonitorItem next;
613
614    EventQueueMonitorItem(AWTEvent evt) {
615        event = evt;
616            next = null;
617    }
618}
619