| /* DefaultKeyboardFocusManager.java -- |
| Copyright (C) 2002, 2004 Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA |
| 02111-1307 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package java.awt; |
| |
| import java.awt.event.ActionEvent; |
| import java.awt.event.FocusEvent; |
| import java.awt.event.KeyEvent; |
| import java.awt.event.WindowEvent; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.Set; |
| import java.util.SortedSet; |
| import java.util.TreeSet; |
| |
| // FIXME: finish documentation |
| public class DefaultKeyboardFocusManager extends KeyboardFocusManager |
| { |
| /** |
| * This class models a request to delay the dispatch of events that |
| * arrive after a certain time, until a certain component becomes |
| * the focus owner. |
| */ |
| private class EventDelayRequest implements Comparable |
| { |
| /** A {@link java.util.List} of {@link java.awt.event.KeyEvent}s |
| that are being delayed, pending this request's {@link |
| Component} receiving the keyboard focus. */ |
| private LinkedList enqueuedKeyEvents = new LinkedList (); |
| |
| /** An event timestamp. All events that arrive after this time |
| should be queued in the {@link #enqueuedKeyEvents} {@link |
| java.util.List}. */ |
| public long timestamp; |
| /** When this {@link Component} becomes focused, all events |
| between this EventDelayRequest and the next one in will be |
| dispatched from {@link #enqueuedKeyEvents}. */ |
| public Component focusedComp; |
| |
| /** |
| * Construct a new EventDelayRequest. |
| * |
| * @param timestamp events that arrive after this time will be |
| * delayed |
| * @param focusedComp the Component that needs to receive focus |
| * before events are dispatched |
| */ |
| public EventDelayRequest (long timestamp, Component focusedComp) |
| { |
| this.timestamp = timestamp; |
| this.focusedComp = focusedComp; |
| } |
| |
| public int compareTo (Object o) |
| { |
| if (!(o instanceof EventDelayRequest)) |
| throw new ClassCastException (); |
| |
| EventDelayRequest request = (EventDelayRequest) o; |
| |
| if (request.timestamp < timestamp) |
| return -1; |
| else if (request.timestamp == timestamp) |
| return 0; |
| else |
| return 1; |
| } |
| |
| public boolean equals (Object o) |
| { |
| if (!(o instanceof EventDelayRequest) || o == null) |
| return false; |
| |
| EventDelayRequest request = (EventDelayRequest) o; |
| |
| return (request.timestamp == timestamp |
| && request.focusedComp == focusedComp); |
| } |
| |
| public void enqueueEvent (KeyEvent e) |
| { |
| KeyEvent last = (KeyEvent) enqueuedKeyEvents.getLast (); |
| if (last != null && e.getWhen () < last.getWhen ()) |
| throw new RuntimeException ("KeyEvents enqueued out-of-order"); |
| |
| if (e.getWhen () <= timestamp) |
| throw new RuntimeException ("KeyEvents enqueued before starting timestamp"); |
| |
| enqueuedKeyEvents.add (e); |
| } |
| |
| public void dispatchEvents () |
| { |
| int size = enqueuedKeyEvents.size (); |
| for (int i = 0; i < size; i++) |
| { |
| KeyEvent e = (KeyEvent) enqueuedKeyEvents.remove (0); |
| dispatchKeyEvent (e); |
| } |
| } |
| |
| public void discardEvents () |
| { |
| enqueuedKeyEvents.clear (); |
| } |
| } |
| |
| /** The {@link java.util.SortedSet} of current {@link |
| #EventDelayRequest}s. */ |
| private SortedSet delayRequests = new TreeSet (); |
| |
| public DefaultKeyboardFocusManager () |
| { |
| } |
| |
| public boolean dispatchEvent (AWTEvent e) |
| { |
| if (e instanceof WindowEvent) |
| { |
| Window target = (Window) e.getSource (); |
| |
| if (e.id == WindowEvent.WINDOW_ACTIVATED) |
| setGlobalActiveWindow (target); |
| else if (e.id == WindowEvent.WINDOW_GAINED_FOCUS) |
| setGlobalFocusedWindow (target); |
| else if (e.id != WindowEvent.WINDOW_LOST_FOCUS |
| && e.id != WindowEvent.WINDOW_DEACTIVATED) |
| return false; |
| |
| redispatchEvent(target, e); |
| return true; |
| } |
| else if (e instanceof FocusEvent) |
| { |
| Component target = (Component) e.getSource (); |
| |
| if (e.id == FocusEvent.FOCUS_GAINED) |
| { |
| if (! (target instanceof Window)) |
| { |
| if (((FocusEvent) e).isTemporary ()) |
| setGlobalFocusOwner (target); |
| else |
| setGlobalPermanentFocusOwner (target); |
| } |
| |
| // Keep track of this window's focus owner. |
| |
| // Find the target Component's top-level ancestor. |
| Container parent = target.getParent (); |
| |
| while (parent != null |
| && !(parent instanceof Window)) |
| parent = parent.getParent (); |
| |
| Window toplevel = parent == null ? |
| (Window) target : (Window) parent; |
| |
| Component focusOwner = getFocusOwner (); |
| if (focusOwner != null |
| && ! (focusOwner instanceof Window)) |
| toplevel.setFocusOwner (focusOwner); |
| } |
| else if (e.id == FocusEvent.FOCUS_LOST) |
| { |
| if (((FocusEvent) e).isTemporary ()) |
| setGlobalFocusOwner (null); |
| else |
| setGlobalPermanentFocusOwner (null); |
| } |
| |
| redispatchEvent(target, e); |
| |
| return true; |
| } |
| else if (e instanceof KeyEvent) |
| { |
| // Loop through all registered KeyEventDispatchers, giving |
| // each a chance to handle this event. |
| Iterator i = getKeyEventDispatchers().iterator(); |
| |
| while (i.hasNext ()) |
| { |
| KeyEventDispatcher dispatcher = (KeyEventDispatcher) i.next (); |
| if (dispatcher.dispatchKeyEvent ((KeyEvent) e)) |
| return true; |
| } |
| |
| // processKeyEvent checks if this event represents a focus |
| // traversal key stroke. |
| Component focusOwner = getGlobalPermanentFocusOwner (); |
| |
| if (focusOwner != null) |
| processKeyEvent (focusOwner, (KeyEvent) e); |
| |
| if (e.isConsumed ()) |
| return true; |
| |
| if (enqueueKeyEvent ((KeyEvent) e)) |
| // This event was enqueued for dispatch at a later time. |
| return true; |
| else |
| // This event wasn't handled by any of the registered |
| // KeyEventDispatchers, and wasn't enqueued for dispatch |
| // later, so send it to the default dispatcher. |
| return dispatchKeyEvent ((KeyEvent) e); |
| } |
| |
| return false; |
| } |
| |
| private boolean enqueueKeyEvent (KeyEvent e) |
| { |
| Iterator i = delayRequests.iterator (); |
| boolean oneEnqueued = false; |
| while (i.hasNext ()) |
| { |
| EventDelayRequest request = (EventDelayRequest) i.next (); |
| if (e.getWhen () > request.timestamp) |
| { |
| request.enqueueEvent (e); |
| oneEnqueued = true; |
| } |
| } |
| return oneEnqueued; |
| } |
| |
| public boolean dispatchKeyEvent (KeyEvent e) |
| { |
| Component focusOwner = getGlobalPermanentFocusOwner (); |
| |
| if (focusOwner != null) |
| redispatchEvent(focusOwner, e); |
| |
| // Loop through all registered KeyEventPostProcessors, giving |
| // each a chance to process this event. |
| Iterator i = getKeyEventPostProcessors().iterator(); |
| |
| while (i.hasNext ()) |
| { |
| KeyEventPostProcessor processor = (KeyEventPostProcessor) i.next (); |
| if (processor.postProcessKeyEvent ((KeyEvent) e)) |
| return true; |
| } |
| |
| // The event hasn't been consumed yet. Check if it is an |
| // MenuShortcut. |
| if (postProcessKeyEvent (e)) |
| return true; |
| |
| // Always return true. |
| return true; |
| } |
| |
| public boolean postProcessKeyEvent (KeyEvent e) |
| { |
| // Check if this event represents a menu shortcut. |
| |
| // MenuShortcuts are activated by Ctrl- KeyEvents, only on KEY_PRESSED. |
| int modifiers = e.getModifiersEx (); |
| if (e.getID() == KeyEvent.KEY_PRESSED |
| && (modifiers & KeyEvent.CTRL_DOWN_MASK) != 0) |
| { |
| Window focusedWindow = getGlobalFocusedWindow (); |
| if (focusedWindow instanceof Frame) |
| { |
| MenuBar menubar = ((Frame) focusedWindow).getMenuBar (); |
| |
| if (menubar != null) |
| { |
| // If there's a menubar, loop through all menu items, |
| // checking whether each one has a shortcut, and if |
| // so, whether this key event should activate it. |
| int numMenus = menubar.getMenuCount (); |
| |
| for (int i = 0; i < numMenus; i++) |
| { |
| Menu menu = menubar.getMenu (i); |
| int numItems = menu.getItemCount (); |
| |
| for (int j = 0; j < numItems; j++) |
| { |
| MenuItem item = menu.getItem (j); |
| MenuShortcut shortcut = item.getShortcut (); |
| |
| if (item.isEnabled() && shortcut != null) |
| { |
| // Dispatch a new ActionEvent if: |
| // |
| // a) this is a Shift- KeyEvent, and the |
| // shortcut requires the Shift modifier |
| // |
| // or, b) this is not a Shift- KeyEvent, and the |
| // shortcut does not require the Shift |
| // modifier. |
| if (shortcut.getKey () == e.getKeyCode () |
| && ((shortcut.usesShiftModifier () |
| && (modifiers & KeyEvent.SHIFT_DOWN_MASK) != 0) |
| || (! shortcut.usesShiftModifier () |
| && (modifiers & KeyEvent.SHIFT_DOWN_MASK) == 0))) |
| { |
| item.dispatchEvent (new ActionEvent (item, |
| ActionEvent.ACTION_PERFORMED, |
| item.getActionCommand (), |
| modifiers)); |
| // The event was dispatched. |
| return true; |
| } |
| } |
| } |
| } |
| } |
| } |
| } |
| return false; |
| } |
| |
| public void processKeyEvent (Component comp, KeyEvent e) |
| { |
| AWTKeyStroke eventKeystroke = AWTKeyStroke.getAWTKeyStrokeForEvent (e); |
| // For every focus traversal keystroke, we need to also consume |
| // the other two key event types for the same key (e.g. if |
| // KEY_PRESSED TAB is a focus traversal keystroke, we also need to |
| // consume KEY_RELEASED and KEY_TYPED TAB key events). |
| AWTKeyStroke oppositeKeystroke = AWTKeyStroke.getAWTKeyStroke (e.getKeyCode (), |
| e.getModifiersEx (), |
| !(e.id == KeyEvent.KEY_RELEASED)); |
| |
| Set forwardKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS); |
| Set backwardKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS); |
| Set upKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS); |
| Set downKeystrokes = null; |
| if (comp instanceof Container) |
| downKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS); |
| |
| if (forwardKeystrokes.contains (eventKeystroke)) |
| { |
| focusNextComponent (comp); |
| e.consume (); |
| } |
| else if (backwardKeystrokes.contains (eventKeystroke)) |
| { |
| focusPreviousComponent (comp); |
| e.consume (); |
| } |
| else if (upKeystrokes.contains (eventKeystroke)) |
| { |
| upFocusCycle (comp); |
| e.consume (); |
| } |
| else if (comp instanceof Container |
| && downKeystrokes.contains (eventKeystroke)) |
| { |
| downFocusCycle ((Container) comp); |
| e.consume (); |
| } |
| else if (forwardKeystrokes.contains (oppositeKeystroke) |
| || backwardKeystrokes.contains (oppositeKeystroke) |
| || upKeystrokes.contains (oppositeKeystroke) |
| || (comp instanceof Container && |
| downKeystrokes.contains (oppositeKeystroke))) |
| e.consume (); |
| } |
| |
| protected void enqueueKeyEvents (long after, Component untilFocused) |
| { |
| delayRequests.add (new EventDelayRequest (after, untilFocused)); |
| } |
| |
| protected void dequeueKeyEvents (long after, Component untilFocused) |
| { |
| // FIXME: need synchronization on delayRequests and enqueuedKeyEvents. |
| |
| // Remove the KeyEvent with the oldest timestamp, which should be |
| // the first element in the SortedSet. |
| if (after < 0) |
| { |
| int size = delayRequests.size (); |
| if (size > 0) |
| delayRequests.remove (delayRequests.first ()); |
| } |
| else |
| { |
| EventDelayRequest template = new EventDelayRequest (after, untilFocused); |
| if (delayRequests.contains (template)) |
| { |
| EventDelayRequest actual = (EventDelayRequest) delayRequests.tailSet (template).first (); |
| delayRequests.remove (actual); |
| actual.dispatchEvents (); |
| } |
| } |
| } |
| |
| protected void discardKeyEvents (Component comp) |
| { |
| // FIXME: need synchronization on delayRequests and enqueuedKeyEvents. |
| |
| Iterator i = delayRequests.iterator (); |
| |
| while (i.hasNext ()) |
| { |
| EventDelayRequest request = (EventDelayRequest) i.next (); |
| |
| if (request.focusedComp == comp |
| || (comp instanceof Container |
| && ((Container) comp).isAncestorOf (request.focusedComp))) |
| request.discardEvents (); |
| } |
| } |
| |
| public void focusPreviousComponent (Component comp) |
| { |
| Component focusComp = (comp == null) ? getGlobalFocusOwner () : comp; |
| Container focusCycleRoot = focusComp.getFocusCycleRootAncestor (); |
| FocusTraversalPolicy policy = focusCycleRoot.getFocusTraversalPolicy (); |
| |
| Component previous = policy.getComponentBefore (focusCycleRoot, focusComp); |
| if (previous != null) |
| previous.requestFocusInWindow (); |
| } |
| |
| public void focusNextComponent (Component comp) |
| { |
| Component focusComp = (comp == null) ? getGlobalFocusOwner () : comp; |
| Container focusCycleRoot = focusComp.getFocusCycleRootAncestor (); |
| FocusTraversalPolicy policy = focusCycleRoot.getFocusTraversalPolicy (); |
| |
| Component next = policy.getComponentAfter (focusCycleRoot, focusComp); |
| if (next != null) |
| next.requestFocusInWindow (); |
| } |
| |
| public void upFocusCycle (Component comp) |
| { |
| Component focusComp = (comp == null) ? getGlobalFocusOwner () : comp; |
| Container focusCycleRoot = focusComp.getFocusCycleRootAncestor (); |
| |
| if (focusCycleRoot instanceof Window) |
| { |
| FocusTraversalPolicy policy = focusCycleRoot.getFocusTraversalPolicy (); |
| Component defaultComponent = policy.getDefaultComponent (focusCycleRoot); |
| if (defaultComponent != null) |
| defaultComponent.requestFocusInWindow (); |
| } |
| else |
| { |
| Container parentFocusCycleRoot = focusCycleRoot.getFocusCycleRootAncestor (); |
| |
| focusCycleRoot.requestFocusInWindow (); |
| setGlobalCurrentFocusCycleRoot (parentFocusCycleRoot); |
| } |
| } |
| |
| public void downFocusCycle (Container cont) |
| { |
| if (cont == null) |
| return; |
| |
| if (cont.isFocusCycleRoot (cont)) |
| { |
| FocusTraversalPolicy policy = cont.getFocusTraversalPolicy (); |
| Component defaultComponent = policy.getDefaultComponent (cont); |
| if (defaultComponent != null) |
| defaultComponent.requestFocusInWindow (); |
| setGlobalCurrentFocusCycleRoot (cont); |
| } |
| } |
| } // class DefaultKeyboardFocusManager |