1 /* DefaultKeyboardFocusManager.java --
2 Copyright (C) 2002, 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING. If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library. Thus, the terms and
23 conditions of the GNU General Public License cover the whole
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module. An independent module is a module which is not derived from
33 or based on this library. If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so. If you do not wish to do so, delete this
36 exception statement from your version. */
41 import java.awt.event.ActionEvent;
42 import java.awt.event.FocusEvent;
43 import java.awt.event.KeyEvent;
44 import java.awt.event.WindowEvent;
45 import java.util.Iterator;
46 import java.util.LinkedList;
48 import java.util.SortedSet;
49 import java.util.TreeSet;
51 // FIXME: finish documentation
52 public class DefaultKeyboardFocusManager extends KeyboardFocusManager
55 * This class models a request to delay the dispatch of events that
56 * arrive after a certain time, until a certain component becomes
59 private class EventDelayRequest implements Comparable
61 /** A {@link java.util.List} of {@link java.awt.event.KeyEvent}s
62 that are being delayed, pending this request's {@link
63 Component} receiving the keyboard focus. */
64 private LinkedList enqueuedKeyEvents = new LinkedList ();
66 /** An event timestamp. All events that arrive after this time
67 should be queued in the {@link #enqueuedKeyEvents} {@link
69 public long timestamp;
70 /** When this {@link Component} becomes focused, all events
71 between this EventDelayRequest and the next one in will be
72 dispatched from {@link #enqueuedKeyEvents}. */
73 public Component focusedComp;
76 * Construct a new EventDelayRequest.
78 * @param timestamp events that arrive after this time will be
80 * @param focusedComp the Component that needs to receive focus
81 * before events are dispatched
83 public EventDelayRequest (long timestamp, Component focusedComp)
85 this.timestamp = timestamp;
86 this.focusedComp = focusedComp;
89 public int compareTo (Object o)
91 if (!(o instanceof EventDelayRequest))
92 throw new ClassCastException ();
94 EventDelayRequest request = (EventDelayRequest) o;
96 if (request.timestamp < timestamp)
98 else if (request.timestamp == timestamp)
104 public boolean equals (Object o)
106 if (!(o instanceof EventDelayRequest) || o == null)
109 EventDelayRequest request = (EventDelayRequest) o;
111 return (request.timestamp == timestamp
112 && request.focusedComp == focusedComp);
115 public void enqueueEvent (KeyEvent e)
117 KeyEvent last = (KeyEvent) enqueuedKeyEvents.getLast ();
118 if (last != null && e.getWhen () < last.getWhen ())
119 throw new RuntimeException ("KeyEvents enqueued out-of-order");
121 if (e.getWhen () <= timestamp)
122 throw new RuntimeException ("KeyEvents enqueued before starting timestamp");
124 enqueuedKeyEvents.add (e);
127 public void dispatchEvents ()
129 int size = enqueuedKeyEvents.size ();
130 for (int i = 0; i < size; i++)
132 KeyEvent e = (KeyEvent) enqueuedKeyEvents.remove (0);
133 dispatchKeyEvent (e);
137 public void discardEvents ()
139 enqueuedKeyEvents.clear ();
144 * This flag indicates for which focus traversal key release event we
145 * possibly wait, before letting any more KEY_TYPED events through.
147 private AWTKeyStroke waitForKeyStroke = null;
149 /** The {@link java.util.SortedSet} of current
150 * {@link EventDelayRequest}s. */
151 private SortedSet delayRequests = new TreeSet ();
153 public DefaultKeyboardFocusManager ()
157 public boolean dispatchEvent (AWTEvent e)
159 if (e instanceof WindowEvent)
161 Window target = (Window) e.getSource ();
163 if (e.id == WindowEvent.WINDOW_ACTIVATED)
164 setGlobalActiveWindow (target);
165 else if (e.id == WindowEvent.WINDOW_GAINED_FOCUS)
166 setGlobalFocusedWindow (target);
167 else if (e.id != WindowEvent.WINDOW_LOST_FOCUS
168 && e.id != WindowEvent.WINDOW_DEACTIVATED)
171 redispatchEvent(target, e);
174 else if (e instanceof FocusEvent)
176 Component target = (Component) e.getSource ();
178 if (e.id == FocusEvent.FOCUS_GAINED)
180 if (! (target instanceof Window))
182 if (((FocusEvent) e).isTemporary ())
183 setGlobalFocusOwner (target);
185 setGlobalPermanentFocusOwner (target);
188 // Keep track of this window's focus owner.
190 // Find the target Component's top-level ancestor. target
192 Container parent = target.getParent ();
194 while (parent != null
195 && !(parent instanceof Window))
196 parent = parent.getParent ();
198 // If the parent is null and target is not a window, then target is an
199 // unanchored component and so we don't want to set the focus owner.
200 if (! (parent == null && ! (target instanceof Window)))
202 Window toplevel = parent == null ?
203 (Window) target : (Window) parent;
205 Component focusOwner = getFocusOwner ();
206 if (focusOwner != null
207 && ! (focusOwner instanceof Window))
208 toplevel.setFocusOwner (focusOwner);
211 else if (e.id == FocusEvent.FOCUS_LOST)
213 if (((FocusEvent) e).isTemporary ())
214 setGlobalFocusOwner (null);
216 setGlobalPermanentFocusOwner (null);
219 redispatchEvent(target, e);
223 else if (e instanceof KeyEvent)
225 // Loop through all registered KeyEventDispatchers, giving
226 // each a chance to handle this event.
227 Iterator i = getKeyEventDispatchers().iterator();
231 KeyEventDispatcher dispatcher = (KeyEventDispatcher) i.next ();
232 if (dispatcher.dispatchKeyEvent ((KeyEvent) e))
236 // processKeyEvent checks if this event represents a focus
237 // traversal key stroke.
238 Component focusOwner = getGlobalPermanentFocusOwner ();
240 if (focusOwner != null)
241 processKeyEvent (focusOwner, (KeyEvent) e);
246 if (enqueueKeyEvent ((KeyEvent) e))
247 // This event was enqueued for dispatch at a later time.
250 // This event wasn't handled by any of the registered
251 // KeyEventDispatchers, and wasn't enqueued for dispatch
252 // later, so send it to the default dispatcher.
253 return dispatchKeyEvent ((KeyEvent) e);
259 private boolean enqueueKeyEvent (KeyEvent e)
261 Iterator i = delayRequests.iterator ();
262 boolean oneEnqueued = false;
265 EventDelayRequest request = (EventDelayRequest) i.next ();
266 if (e.getWhen () > request.timestamp)
268 request.enqueueEvent (e);
275 public boolean dispatchKeyEvent (KeyEvent e)
277 Component focusOwner = getGlobalPermanentFocusOwner ();
279 if (focusOwner != null)
280 redispatchEvent(focusOwner, e);
282 // Loop through all registered KeyEventPostProcessors, giving
283 // each a chance to process this event.
284 Iterator i = getKeyEventPostProcessors().iterator();
288 KeyEventPostProcessor processor = (KeyEventPostProcessor) i.next ();
289 if (processor.postProcessKeyEvent ((KeyEvent) e))
293 // The event hasn't been consumed yet. Check if it is an
295 if (postProcessKeyEvent (e))
298 // Always return true.
302 public boolean postProcessKeyEvent (KeyEvent e)
304 // Check if this event represents a menu shortcut.
306 // MenuShortcuts are activated by Ctrl- KeyEvents, only on KEY_PRESSED.
307 int modifiers = e.getModifiersEx ();
308 if (e.getID() == KeyEvent.KEY_PRESSED
309 && (modifiers & KeyEvent.CTRL_DOWN_MASK) != 0)
311 Window focusedWindow = getGlobalFocusedWindow ();
312 if (focusedWindow instanceof Frame)
314 MenuBar menubar = ((Frame) focusedWindow).getMenuBar ();
318 // If there's a menubar, loop through all menu items,
319 // checking whether each one has a shortcut, and if
320 // so, whether this key event should activate it.
321 int numMenus = menubar.getMenuCount ();
323 for (int i = 0; i < numMenus; i++)
325 Menu menu = menubar.getMenu (i);
326 int numItems = menu.getItemCount ();
328 for (int j = 0; j < numItems; j++)
330 MenuItem item = menu.getItem (j);
331 MenuShortcut shortcut = item.getShortcut ();
333 if (item.isEnabled() && shortcut != null)
335 // Dispatch a new ActionEvent if:
337 // a) this is a Shift- KeyEvent, and the
338 // shortcut requires the Shift modifier
340 // or, b) this is not a Shift- KeyEvent, and the
341 // shortcut does not require the Shift
343 if (shortcut.getKey () == e.getKeyCode ()
344 && ((shortcut.usesShiftModifier ()
345 && (modifiers & KeyEvent.SHIFT_DOWN_MASK) != 0)
346 || (! shortcut.usesShiftModifier ()
347 && (modifiers & KeyEvent.SHIFT_DOWN_MASK) == 0)))
349 item.dispatchEvent (new ActionEvent (item,
350 ActionEvent.ACTION_PERFORMED,
351 item.getActionCommand (),
353 // The event was dispatched.
365 public void processKeyEvent (Component comp, KeyEvent e)
367 AWTKeyStroke eventKeystroke = AWTKeyStroke.getAWTKeyStrokeForEvent (e);
368 // For every focus traversal keystroke, we need to also consume
369 // the other two key event types for the same key (e.g. if
370 // KEY_PRESSED TAB is a focus traversal keystroke, we also need to
371 // consume KEY_RELEASED and KEY_TYPED TAB key events).
372 // consuming KEY_RELEASED is easy, because their keyCodes matches
373 // the KEY_PRESSED event. Consuming the intermediate KEY_TYPED is
374 // very difficult because their is no clean way that we can know
375 // which KEY_TYPED belongs to a focusTraversalKey and which not.
376 // To address this problem we swallow every KEY_TYPE between the
377 // KEY_PRESSED event that matches a focusTraversalKey and the
378 // corresponding KEY_RELEASED.
379 AWTKeyStroke oppositeKeystroke = AWTKeyStroke.getAWTKeyStroke (e.getKeyCode (),
381 !(e.id == KeyEvent.KEY_RELEASED));
383 // Here we check if we are currently waiting for a KEY_RELEASED and
384 // swallow all KeyEvents that are to be delivered in between. This
385 // should only be the KEY_TYPED events that correspond to the
386 // focusTraversalKey's KEY_PRESSED event
387 if (waitForKeyStroke != null)
389 if (eventKeystroke.equals(waitForKeyStroke))
391 waitForKeyStroke = null;
393 // as long as we are waiting for the KEY_RELEASED, we swallow every
394 // KeyEvent, including the KEY_RELEASED
399 Set forwardKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
400 Set backwardKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS);
401 Set upKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS);
402 Set downKeystrokes = null;
403 if (comp instanceof Container)
404 downKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS);
406 if (forwardKeystrokes.contains (eventKeystroke))
408 waitForKeyStroke = oppositeKeystroke;
409 focusNextComponent (comp);
412 else if (backwardKeystrokes.contains (eventKeystroke))
414 waitForKeyStroke = oppositeKeystroke;
415 focusPreviousComponent (comp);
418 else if (upKeystrokes.contains (eventKeystroke))
420 waitForKeyStroke = oppositeKeystroke;
424 else if (comp instanceof Container
425 && downKeystrokes.contains (eventKeystroke))
427 waitForKeyStroke = oppositeKeystroke;
428 downFocusCycle ((Container) comp);
433 protected void enqueueKeyEvents (long after, Component untilFocused)
435 delayRequests.add (new EventDelayRequest (after, untilFocused));
438 protected void dequeueKeyEvents (long after, Component untilFocused)
440 // FIXME: need synchronization on delayRequests and enqueuedKeyEvents.
442 // Remove the KeyEvent with the oldest timestamp, which should be
443 // the first element in the SortedSet.
446 int size = delayRequests.size ();
448 delayRequests.remove (delayRequests.first ());
452 EventDelayRequest template = new EventDelayRequest (after, untilFocused);
453 if (delayRequests.contains (template))
455 EventDelayRequest actual = (EventDelayRequest) delayRequests.tailSet (template).first ();
456 delayRequests.remove (actual);
457 actual.dispatchEvents ();
462 protected void discardKeyEvents (Component comp)
464 // FIXME: need synchronization on delayRequests and enqueuedKeyEvents.
466 Iterator i = delayRequests.iterator ();
470 EventDelayRequest request = (EventDelayRequest) i.next ();
472 if (request.focusedComp == comp
473 || (comp instanceof Container
474 && ((Container) comp).isAncestorOf (request.focusedComp)))
475 request.discardEvents ();
479 public void focusPreviousComponent (Component comp)
482 comp.transferFocusBackward();
485 public void focusNextComponent (Component comp)
488 comp.transferFocus();
491 public void upFocusCycle (Component comp)
494 comp.transferFocusUpCycle();
497 public void downFocusCycle (Container cont)
500 cont.transferFocusDownCycle();
502 } // class DefaultKeyboardFocusManager