001/* DefaultKeyboardFocusManager.java -- 
002   Copyright (C) 2002, 2004  Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package java.awt;
040
041import java.awt.event.ActionEvent;
042import java.awt.event.FocusEvent;
043import java.awt.event.KeyEvent;
044import java.awt.event.WindowEvent;
045import java.util.Iterator;
046import java.util.LinkedList;
047import java.util.Set;
048import java.util.SortedSet;
049import java.util.TreeSet;
050
051// FIXME: finish documentation
052public class DefaultKeyboardFocusManager extends KeyboardFocusManager
053{
054  /**
055   * This class models a request to delay the dispatch of events that
056   * arrive after a certain time, until a certain component becomes
057   * the focus owner.
058   */
059  private class EventDelayRequest implements Comparable
060  {
061    /** A {@link java.util.List} of {@link java.awt.event.KeyEvent}s
062        that are being delayed, pending this request's {@link
063        Component} receiving the keyboard focus. */
064    private LinkedList enqueuedKeyEvents = new LinkedList ();
065
066    /** An event timestamp.  All events that arrive after this time
067        should be queued in the {@link #enqueuedKeyEvents} {@link
068        java.util.List}. */
069    public long timestamp;
070    /** When this {@link Component} becomes focused, all events
071        between this EventDelayRequest and the next one in will be
072        dispatched from {@link #enqueuedKeyEvents}. */
073    public Component focusedComp;
074
075    /**
076     * Construct a new EventDelayRequest.
077     *
078     * @param timestamp events that arrive after this time will be
079     * delayed
080     * @param focusedComp the Component that needs to receive focus
081     * before events are dispatched
082     */
083    public EventDelayRequest (long timestamp, Component focusedComp)
084    {
085      this.timestamp = timestamp;
086      this.focusedComp = focusedComp;
087    }
088
089    public int compareTo (Object o)
090    {
091      if (!(o instanceof EventDelayRequest))
092        throw new ClassCastException ();
093
094      EventDelayRequest request = (EventDelayRequest) o;
095
096      if (request.timestamp < timestamp)
097        return -1;
098      else if (request.timestamp == timestamp)
099        return 0;
100      else
101        return 1;
102    }
103
104    public boolean equals (Object o)
105    {
106      if (!(o instanceof EventDelayRequest) || o == null)
107        return false;
108
109      EventDelayRequest request = (EventDelayRequest) o;
110
111      return (request.timestamp == timestamp
112              && request.focusedComp == focusedComp);
113    }
114
115    public void enqueueEvent (KeyEvent e)
116    {
117      KeyEvent last = (KeyEvent) enqueuedKeyEvents.getLast ();
118      if (last != null && e.getWhen () < last.getWhen ())
119        throw new RuntimeException ("KeyEvents enqueued out-of-order");
120
121      if (e.getWhen () <= timestamp)
122        throw new RuntimeException ("KeyEvents enqueued before starting timestamp");
123
124      enqueuedKeyEvents.add (e);
125    }
126
127    public void dispatchEvents ()
128    {
129      int size = enqueuedKeyEvents.size ();
130      for (int i = 0; i < size; i++)
131        {
132          KeyEvent e = (KeyEvent) enqueuedKeyEvents.remove (0);
133          dispatchKeyEvent (e);
134        }
135    }
136
137    public void discardEvents ()
138    {
139      enqueuedKeyEvents.clear ();
140    }
141  }
142
143  /**
144   * This flag indicates for which focus traversal key release event we
145   * possibly wait, before letting any more KEY_TYPED events through.
146   */
147  private AWTKeyStroke waitForKeyStroke = null;
148
149  /** The {@link java.util.SortedSet} of current 
150   * {@link EventDelayRequest}s. */
151  private SortedSet delayRequests = new TreeSet ();
152
153  public DefaultKeyboardFocusManager ()
154  {
155  }
156
157  public boolean dispatchEvent (AWTEvent e)
158  {
159    if (e instanceof WindowEvent)
160      {
161        Window target = (Window) e.getSource ();
162
163        if (e.id == WindowEvent.WINDOW_ACTIVATED)
164          setGlobalActiveWindow (target);
165        else if (e.id == WindowEvent.WINDOW_GAINED_FOCUS)
166          {
167            setGlobalFocusedWindow (target);
168            FocusTraversalPolicy p = target.getFocusTraversalPolicy();
169            Component toFocus = p.getInitialComponent(target);
170            if (toFocus != null)
171              toFocus.requestFocusInWindow();
172          }
173        else if (e.id != WindowEvent.WINDOW_LOST_FOCUS
174                 && e.id != WindowEvent.WINDOW_DEACTIVATED)
175          return false;
176
177        redispatchEvent(target, e);
178        return true;
179      }
180    else if (e instanceof FocusEvent)
181      {
182        FocusEvent fe = (FocusEvent) e;
183        Component target = fe.getComponent ();
184
185        boolean retval = false;
186        if (e.id == FocusEvent.FOCUS_GAINED)
187          {
188            retval = handleFocusGained(fe);
189          }
190        else if (e.id == FocusEvent.FOCUS_LOST)
191          {
192            retval = handleFocusLost(fe);
193          }
194        return true;
195      }
196    else if (e instanceof KeyEvent)
197      {
198        // Loop through all registered KeyEventDispatchers, giving
199        // each a chance to handle this event.
200        Iterator i = getKeyEventDispatchers().iterator();
201
202        while (i.hasNext ())
203          {
204            KeyEventDispatcher dispatcher = (KeyEventDispatcher) i.next ();
205            if (dispatcher.dispatchKeyEvent ((KeyEvent) e))
206              return true;
207          }
208
209        // processKeyEvent checks if this event represents a focus
210        // traversal key stroke.
211        Component focusOwner = getGlobalPermanentFocusOwner ();
212
213        if (focusOwner != null)
214          processKeyEvent (focusOwner, (KeyEvent) e);
215
216        if (e.isConsumed ())
217          return true;
218
219        if (enqueueKeyEvent ((KeyEvent) e))
220          // This event was enqueued for dispatch at a later time.
221          return true;
222        else
223          // This event wasn't handled by any of the registered
224          // KeyEventDispatchers, and wasn't enqueued for dispatch
225          // later, so send it to the default dispatcher.
226          return dispatchKeyEvent ((KeyEvent) e);
227      }
228
229    return false;
230  }
231
232  /**
233   * Handles FOCUS_GAINED events in {@link #dispatchEvent(AWTEvent)}.
234   *
235   * @param fe the focus event
236   */
237  private boolean handleFocusGained(FocusEvent fe)
238  {
239    Component target = fe.getComponent ();
240
241    // If old focus owner != new focus owner, notify old focus
242    // owner that it has lost focus.
243    Component oldFocusOwner = getGlobalFocusOwner();
244    if (oldFocusOwner != null && oldFocusOwner != target)
245      {
246        FocusEvent lost = new FocusEvent(oldFocusOwner,
247                                         FocusEvent.FOCUS_LOST,
248                                         fe.isTemporary(), target);
249        oldFocusOwner.dispatchEvent(lost);
250      }
251
252     setGlobalFocusOwner (target);
253     if (target != getGlobalFocusOwner())
254       {
255         // Focus transfer was rejected, like when the target is not
256         // focusable.
257         dequeueKeyEvents(-1, target);
258         // FIXME: Restore focus somehow.
259       }
260     else
261       {
262         if (! fe.isTemporary())
263           {
264             setGlobalPermanentFocusOwner (target);
265             if (target != getGlobalPermanentFocusOwner())
266               {
267                 // Focus transfer was rejected, like when the target is not
268                 // focusable.
269                 dequeueKeyEvents(-1, target);
270                 // FIXME: Restore focus somehow.
271               }
272             else
273               {
274                 redispatchEvent(target, fe);
275               }
276           }
277       }
278
279     return true;
280  }
281
282  /**
283   * Handles FOCUS_LOST events for {@link #dispatchEvent(AWTEvent)}.
284   *
285   * @param fe the focus event
286   *
287   * @return if the event has been handled
288   */
289  private boolean handleFocusLost(FocusEvent fe)
290  {
291    Component currentFocus = getGlobalFocusOwner();
292    if (currentFocus != fe.getOppositeComponent())
293      {
294        setGlobalFocusOwner(null);
295        if (getGlobalFocusOwner() != null)
296          {
297            // TODO: Is this possible? If so, then we should try to restore
298            // the focus.
299          }
300        else
301          {
302            if (! fe.isTemporary())
303              {
304                setGlobalPermanentFocusOwner(null);
305                if (getGlobalPermanentFocusOwner() != null)
306                  {
307                    // TODO: Is this possible? If so, then we should try to
308                    // restore the focus.
309                  }
310                else
311                  {
312                    fe.setSource(currentFocus);
313                    redispatchEvent(currentFocus, fe);
314                  }
315              }
316          }
317      }
318    return true;
319  }
320
321  private boolean enqueueKeyEvent (KeyEvent e)
322  {
323    Iterator i = delayRequests.iterator ();
324    boolean oneEnqueued = false;
325    while (i.hasNext ())
326      {
327        EventDelayRequest request = (EventDelayRequest) i.next ();
328        if (e.getWhen () > request.timestamp)
329          {
330            request.enqueueEvent (e);
331            oneEnqueued = true;
332          }
333      }
334    return oneEnqueued;
335  }
336
337  public boolean dispatchKeyEvent (KeyEvent e)
338  {
339    Component focusOwner = getFocusOwner();
340    if (focusOwner == null)
341      focusOwner = getFocusedWindow();
342    
343    if (focusOwner != null)
344      redispatchEvent(focusOwner, e);      
345
346    // Loop through all registered KeyEventPostProcessors, giving
347    // each a chance to process this event.
348    Iterator i = getKeyEventPostProcessors().iterator();
349
350    while (i.hasNext ())
351      {
352        KeyEventPostProcessor processor = (KeyEventPostProcessor) i.next ();
353        if (processor.postProcessKeyEvent (e))
354          return true;
355      }
356
357    // The event hasn't been consumed yet.  Check if it is an
358    // MenuShortcut.
359    if (postProcessKeyEvent (e))
360      return true;
361    
362    // Always return true.
363    return true;
364  }
365
366  public boolean postProcessKeyEvent (KeyEvent e)
367  {
368    // Check if this event represents a menu shortcut.
369
370    // MenuShortcuts are activated by Ctrl- KeyEvents, only on KEY_PRESSED.
371    int modifiers = e.getModifiersEx ();
372    if (e.getID() == KeyEvent.KEY_PRESSED
373        && (modifiers & KeyEvent.CTRL_DOWN_MASK) != 0)
374      {
375        Window focusedWindow = getGlobalFocusedWindow ();
376        if (focusedWindow instanceof Frame)
377          {
378            MenuBar menubar = ((Frame) focusedWindow).getMenuBar ();
379
380            if (menubar != null)
381              {
382                // If there's a menubar, loop through all menu items,
383                // checking whether each one has a shortcut, and if
384                // so, whether this key event should activate it.
385                int numMenus = menubar.getMenuCount ();
386
387                for (int i = 0; i < numMenus; i++)
388                  {
389                    Menu menu = menubar.getMenu (i);
390                    int numItems = menu.getItemCount ();
391
392                    for (int j = 0; j < numItems; j++)
393                      {
394                        MenuItem item = menu.getItem (j);
395                        MenuShortcut shortcut = item.getShortcut ();
396
397                        if (item.isEnabled() && shortcut != null)
398                          {
399                            // Dispatch a new ActionEvent if:
400                            //
401                            //     a) this is a Shift- KeyEvent, and the
402                            //        shortcut requires the Shift modifier
403                            //
404                            // or, b) this is not a Shift- KeyEvent, and the
405                            //        shortcut does not require the Shift
406                            //        modifier.
407                            if (shortcut.getKey () == e.getKeyCode ()
408                                && ((shortcut.usesShiftModifier ()
409                                     && (modifiers & KeyEvent.SHIFT_DOWN_MASK) != 0)
410                                    || (! shortcut.usesShiftModifier ()
411                                        && (modifiers & KeyEvent.SHIFT_DOWN_MASK) == 0)))
412                              {
413                                item.dispatchEvent (new ActionEvent (item,
414                                                                     ActionEvent.ACTION_PERFORMED,
415                                                                     item.getActionCommand (),
416                                                                     modifiers));
417                                // The event was dispatched.
418                                return true;
419                              }
420                          }
421                      }
422                  }
423              }
424          }
425      }
426    return false;
427  }
428
429  public void processKeyEvent (Component comp, KeyEvent e)
430  {
431    AWTKeyStroke eventKeystroke = AWTKeyStroke.getAWTKeyStrokeForEvent (e);
432    // For every focus traversal keystroke, we need to also consume
433    // the other two key event types for the same key (e.g. if
434    // KEY_PRESSED TAB is a focus traversal keystroke, we also need to
435    // consume KEY_RELEASED and KEY_TYPED TAB key events).
436    // consuming KEY_RELEASED is easy, because their keyCodes matches
437    // the KEY_PRESSED event. Consuming the intermediate KEY_TYPED is
438    // very difficult because their is no clean way that we can know
439    // which KEY_TYPED belongs to a focusTraversalKey and which not.
440    // To address this problem we swallow every KEY_TYPE between the
441    // KEY_PRESSED event that matches a focusTraversalKey and the
442    // corresponding KEY_RELEASED.
443    AWTKeyStroke oppositeKeystroke = AWTKeyStroke.getAWTKeyStroke (e.getKeyCode (),
444                                                                   e.getModifiersEx (),
445                                                                   !(e.id == KeyEvent.KEY_RELEASED));
446
447    // Here we check if we are currently waiting for a KEY_RELEASED and
448    // swallow all KeyEvents that are to be delivered in between. This
449    // should only be the KEY_TYPED events that correspond to the
450    // focusTraversalKey's KEY_PRESSED event
451    if (waitForKeyStroke != null)
452      {
453        if (eventKeystroke.equals(waitForKeyStroke))
454          // release this lock
455          waitForKeyStroke = null;
456
457        // as long as we are waiting for the KEY_RELEASED, we swallow every
458        // KeyEvent, including the KEY_RELEASED
459        e.consume();
460        return;
461      }
462
463    Set forwardKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS);
464    Set backwardKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS);
465    Set upKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS);
466    Set downKeystrokes = null;
467    if (comp instanceof Container)
468      downKeystrokes = comp.getFocusTraversalKeys (KeyboardFocusManager.DOWN_CYCLE_TRAVERSAL_KEYS);
469
470    if (forwardKeystrokes.contains (eventKeystroke))
471      {
472        waitForKeyStroke = oppositeKeystroke;        
473        focusNextComponent (comp);
474        e.consume ();
475      }
476    else if (backwardKeystrokes.contains (eventKeystroke))
477      {
478        waitForKeyStroke = oppositeKeystroke;        
479        focusPreviousComponent (comp);
480        e.consume ();
481      }
482    else if (upKeystrokes.contains (eventKeystroke))
483      {
484        waitForKeyStroke = oppositeKeystroke;        
485        upFocusCycle (comp);
486        e.consume ();
487      }
488    else if (comp instanceof Container
489             && downKeystrokes.contains (eventKeystroke))
490      {
491        waitForKeyStroke = oppositeKeystroke;        
492        downFocusCycle ((Container) comp);
493        e.consume ();
494      }
495  }
496
497  protected void enqueueKeyEvents (long after, Component untilFocused)
498  {
499    delayRequests.add (new EventDelayRequest (after, untilFocused));
500  }
501
502  protected void dequeueKeyEvents (long after, Component untilFocused)
503  {
504    // FIXME: need synchronization on delayRequests and enqueuedKeyEvents.
505
506    // Remove the KeyEvent with the oldest timestamp, which should be
507    // the first element in the SortedSet.
508    if (after < 0)
509      {
510        int size = delayRequests.size ();
511        if (size > 0)
512          delayRequests.remove (delayRequests.first ());
513      }
514    else
515      {
516        EventDelayRequest template = new EventDelayRequest (after, untilFocused);
517        if (delayRequests.contains (template))
518          {
519            EventDelayRequest actual = (EventDelayRequest) delayRequests.tailSet (template).first ();
520            delayRequests.remove (actual);
521            actual.dispatchEvents ();
522          }
523      }
524  }
525
526  protected void discardKeyEvents (Component comp)
527  {
528    // FIXME: need synchronization on delayRequests and enqueuedKeyEvents.
529
530    Iterator i = delayRequests.iterator ();
531
532    while (i.hasNext ())
533      {
534        EventDelayRequest request = (EventDelayRequest) i.next ();
535
536        if (request.focusedComp == comp
537            || (comp instanceof Container
538                && ((Container) comp).isAncestorOf (request.focusedComp)))
539          request.discardEvents ();
540      }
541  }
542
543  public void focusPreviousComponent (Component comp)
544  {
545    if (comp != null)
546      comp.transferFocusBackward();
547  }
548
549  public void focusNextComponent (Component comp)
550  {
551    if (comp != null)
552      comp.transferFocus();
553  }
554
555  public void upFocusCycle (Component comp)
556  {
557    if (comp != null)
558      comp.transferFocusUpCycle();
559  }
560
561  public void downFocusCycle (Container cont)
562  {
563    if (cont != null)
564      cont.transferFocusDownCycle();
565  }
566} // class DefaultKeyboardFocusManager