Source for javax.swing.text.JTextComponent

   1: /* JTextComponent.java --
   2:    Copyright (C) 2002, 2004, 2005  Free Software Foundation, Inc.
   3: 
   4: This file is part of GNU Classpath.
   5: 
   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)
   9: any later version.
  10: 
  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.
  15: 
  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
  19: 02110-1301 USA.
  20: 
  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
  24: combination.
  25: 
  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. */
  37: 
  38: 
  39: package javax.swing.text;
  40: 
  41: import java.awt.AWTEvent;
  42: import java.awt.Color;
  43: import java.awt.Container;
  44: import java.awt.Dimension;
  45: import java.awt.Insets;
  46: import java.awt.Point;
  47: import java.awt.Rectangle;
  48: import java.awt.Shape;
  49: import java.awt.datatransfer.Clipboard;
  50: import java.awt.datatransfer.DataFlavor;
  51: import java.awt.datatransfer.StringSelection;
  52: import java.awt.datatransfer.Transferable;
  53: import java.awt.datatransfer.UnsupportedFlavorException;
  54: import java.awt.event.ActionEvent;
  55: import java.awt.event.InputMethodListener;
  56: import java.awt.event.KeyEvent;
  57: import java.awt.event.MouseEvent;
  58: import java.io.IOException;
  59: import java.io.Reader;
  60: import java.io.Writer;
  61: import java.text.BreakIterator;
  62: import java.util.Enumeration;
  63: import java.util.Hashtable;
  64: 
  65: import javax.accessibility.Accessible;
  66: import javax.accessibility.AccessibleAction;
  67: import javax.accessibility.AccessibleContext;
  68: import javax.accessibility.AccessibleEditableText;
  69: import javax.accessibility.AccessibleRole;
  70: import javax.accessibility.AccessibleState;
  71: import javax.accessibility.AccessibleStateSet;
  72: import javax.accessibility.AccessibleText;
  73: import javax.swing.Action;
  74: import javax.swing.ActionMap;
  75: import javax.swing.InputMap;
  76: import javax.swing.JComponent;
  77: import javax.swing.JViewport;
  78: import javax.swing.KeyStroke;
  79: import javax.swing.Scrollable;
  80: import javax.swing.SwingConstants;
  81: import javax.swing.TransferHandler;
  82: import javax.swing.UIManager;
  83: import javax.swing.event.CaretEvent;
  84: import javax.swing.event.CaretListener;
  85: import javax.swing.event.DocumentEvent;
  86: import javax.swing.event.DocumentListener;
  87: import javax.swing.plaf.ActionMapUIResource;
  88: import javax.swing.plaf.InputMapUIResource;
  89: import javax.swing.plaf.TextUI;
  90: 
  91: public abstract class JTextComponent extends JComponent
  92:   implements Scrollable, Accessible
  93: {
  94:   /**
  95:    * AccessibleJTextComponent implements accessibility hooks for
  96:    * JTextComponent.  It allows an accessibility driver to read and
  97:    * manipulate the text component's contents as well as update UI
  98:    * elements such as the caret.
  99:    */
 100:   public class AccessibleJTextComponent extends AccessibleJComponent implements
 101:       AccessibleText, CaretListener, DocumentListener, AccessibleAction,
 102:       AccessibleEditableText
 103:   {
 104:     private static final long serialVersionUID = 7664188944091413696L;
 105: 
 106:     /**
 107:      * The caret's offset.
 108:      */
 109:     private int caretDot;
 110: 
 111:     /**
 112:      * Construct an AccessibleJTextComponent.
 113:      */
 114:     public AccessibleJTextComponent()
 115:     {
 116:       super();
 117:       JTextComponent.this.addCaretListener(this);
 118:       caretDot = getCaretPosition();
 119:     }
 120: 
 121:     /**
 122:      * Retrieve the current caret position.  The index of the first
 123:      * caret position is 0.
 124:      *
 125:      * @return caret position
 126:      */
 127:     public int getCaretPosition()
 128:     {
 129:       return JTextComponent.this.getCaretPosition();
 130:     }
 131: 
 132:     /**
 133:      * Retrieve the current text selection.  If no text is selected
 134:      * this method returns null.
 135:      *
 136:      * @return the currently selected text or null
 137:      */
 138:     public String getSelectedText()
 139:     {
 140:       return JTextComponent.this.getSelectedText();
 141:     }
 142: 
 143:     /**
 144:      * Retrieve the index of the first character in the current text
 145:      * selection.  If there is no text in the text component, this
 146:      * method returns 0.  If there is text in the text component, but
 147:      * there is no selection, this method returns the current caret
 148:      * position.
 149:      *
 150:      * @return the index of the first character in the selection, the
 151:      * current caret position or 0
 152:      */
 153:     public int getSelectionStart()
 154:     {
 155:       if (getSelectedText() == null
 156:           || (JTextComponent.this.getText().equals("")))
 157:         return 0;
 158:       return JTextComponent.this.getSelectionStart();
 159:     }
 160: 
 161:     /**
 162:      * Retrieve the index of the last character in the current text
 163:      * selection.  If there is no text in the text component, this
 164:      * method returns 0.  If there is text in the text component, but
 165:      * there is no selection, this method returns the current caret
 166:      * position.
 167:      *
 168:      * @return the index of the last character in the selection, the
 169:      * current caret position or 0
 170:      */
 171:     public int getSelectionEnd()
 172:     {
 173:       return JTextComponent.this.getSelectionEnd();
 174:     }
 175: 
 176:     /**
 177:      * Handle a change in the caret position and fire any applicable
 178:      * property change events.
 179:      *
 180:      * @param e - the caret update event
 181:      */
 182:     public void caretUpdate(CaretEvent e)
 183:     {
 184:       int dot = e.getDot();
 185:       int mark = e.getMark();
 186:       if (caretDot != dot)
 187:         {
 188:           firePropertyChange(ACCESSIBLE_CARET_PROPERTY, new Integer(caretDot),
 189:                              new Integer(dot));
 190:           caretDot = dot;
 191:         }
 192:       if (mark != dot)
 193:         {
 194:           firePropertyChange(ACCESSIBLE_SELECTION_PROPERTY, null,
 195:                              getSelectedText());
 196:         }
 197:     }
 198: 
 199:     /**
 200:      * Retreive the accessible state set of this component.
 201:      *
 202:      * @return the accessible state set of this component
 203:      */
 204:     public AccessibleStateSet getAccessibleStateSet()
 205:     {
 206:       AccessibleStateSet state = super.getAccessibleStateSet();
 207:       if (isEditable())
 208:         state.add(AccessibleState.EDITABLE);
 209:       return state;
 210:     }
 211: 
 212:     /**
 213:      * Retrieve the accessible role of this component.
 214:      *
 215:      * @return the accessible role of this component
 216:      *
 217:      * @see AccessibleRole
 218:      */
 219:     public AccessibleRole getAccessibleRole()
 220:     {
 221:       return AccessibleRole.TEXT;
 222:     }
 223: 
 224:     /**
 225:      * Retrieve an AccessibleEditableText object that controls this
 226:      * text component.
 227:      *
 228:      * @return this
 229:      */
 230:     public AccessibleEditableText getAccessibleEditableText()
 231:     {
 232:       return this;
 233:     }
 234: 
 235:     /**
 236:      * Retrieve an AccessibleText object that controls this text
 237:      * component.
 238:      *
 239:      * @return this
 240:      *
 241:      * @see AccessibleText
 242:      */
 243:     public AccessibleText getAccessibleText()
 244:     {
 245:       return this;
 246:     }
 247:     
 248:     /**
 249:      * Handle a text insertion event and fire an
 250:      * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change
 251:      * event.
 252:      *
 253:      * @param e - the insertion event
 254:      */
 255:     public void insertUpdate(DocumentEvent e)
 256:     {
 257:       firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null,
 258:                          new Integer(e.getOffset()));
 259:     }
 260: 
 261:     /**
 262:      * Handle a text removal event and fire an
 263:      * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change
 264:      * event.
 265:      *
 266:      * @param e - the removal event
 267:      */
 268:     public void removeUpdate(DocumentEvent e)
 269:     {
 270:       firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null,
 271:                          new Integer(e.getOffset()));
 272:     }
 273: 
 274:     /**
 275:      * Handle a text change event and fire an
 276:      * AccessibleContext.ACCESSIBLE_TEXT_PROPERTY property change
 277:      * event.
 278:      *
 279:      * @param e - text change event
 280:      */
 281:     public void changedUpdate(DocumentEvent e)
 282:     {
 283:       firePropertyChange(ACCESSIBLE_TEXT_PROPERTY, null,
 284:                          new Integer(e.getOffset()));
 285:     }
 286: 
 287:     /**
 288:      * Get the index of the character at the given point, in component
 289:      * pixel co-ordinates.  If the point argument is invalid this
 290:      * method returns -1.
 291:      *
 292:      * @param p - a point in component pixel co-ordinates
 293:      *
 294:      * @return a character index, or -1
 295:      */
 296:     public int getIndexAtPoint(Point p)
 297:     {
 298:       return viewToModel(p);
 299:     }
 300: 
 301:     /**
 302:      * Calculate the bounding box of the character at the given index.
 303:      * The returned x and y co-ordinates are relative to this text
 304:      * component's top-left corner.  If the index is invalid this
 305:      * method returns null.
 306:      *
 307:      * @param index - the character index
 308:      *
 309:      * @return a character's bounding box, or null
 310:      */
 311:     public Rectangle getCharacterBounds(int index)
 312:     {
 313:       // This is basically the same as BasicTextUI.modelToView().
 314:       
 315:       Rectangle bounds = null;
 316:       if (index >= 0 && index < doc.getLength() - 1)
 317:         {
 318:           if (doc instanceof AbstractDocument)
 319:             ((AbstractDocument) doc).readLock();
 320:           try
 321:             {
 322:               TextUI ui = getUI();
 323:               if (ui != null)
 324:                 {
 325:                   // Get editor rectangle.
 326:                   Rectangle rect = new Rectangle();
 327:                   Insets insets = getInsets();
 328:                   rect.x = insets.left;
 329:                   rect.y = insets.top;
 330:                   rect.width = getWidth() - insets.left - insets.right;
 331:                   rect.height = getHeight() - insets.top - insets.bottom;
 332:                   View rootView = ui.getRootView(JTextComponent.this);
 333:                   if (rootView != null)
 334:                     {
 335:                       rootView.setSize(rect.width, rect.height);
 336:                       Shape s = rootView.modelToView(index,
 337:                                                      Position.Bias.Forward,
 338:                                                      index + 1,
 339:                                                      Position.Bias.Backward,
 340:                                                      rect);
 341:                       if (s != null)
 342:                         bounds = s.getBounds();
 343:                     }
 344:                 }
 345:             }
 346:           catch (BadLocationException ex)
 347:             {
 348:               // Ignore (return null).
 349:             }
 350:           finally
 351:             {
 352:               if (doc instanceof AbstractDocument)
 353:                 ((AbstractDocument) doc).readUnlock();
 354:             }
 355:         }
 356:       return bounds;
 357:     }
 358: 
 359:     /**
 360:      * Return the length of the text in this text component.
 361:      *
 362:      * @return a character length
 363:      */
 364:     public int getCharCount()
 365:     {
 366:       return JTextComponent.this.getText().length();
 367:     }
 368: 
 369:    /** 
 370:     * Gets the character attributes of the character at index. If
 371:     * the index is out of bounds, null is returned.
 372:     *
 373:     * @param index - index of the character
 374:     * 
 375:     * @return the character's attributes
 376:     */
 377:     public AttributeSet getCharacterAttribute(int index)
 378:     {
 379:       AttributeSet atts;
 380:       if (doc instanceof AbstractDocument)
 381:         ((AbstractDocument) doc).readLock();
 382:       try
 383:         {
 384:           Element el = doc.getDefaultRootElement();
 385:           while (! el.isLeaf())
 386:             {
 387:               int i = el.getElementIndex(index);
 388:               el = el.getElement(i);
 389:             }
 390:           atts = el.getAttributes();
 391:         }
 392:       finally
 393:         {
 394:           if (doc instanceof AbstractDocument)
 395:             ((AbstractDocument) doc).readUnlock();
 396:         }
 397:       return atts;
 398:     }
 399: 
 400:     /**
 401:      * Gets the text located at index. null is returned if the index
 402:      * or part is invalid.
 403:      * 
 404:      * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE}
 405:      * @param index - index of the part
 406:      * 
 407:      * @return the part of text at that index, or null
 408:      */
 409:     public String getAtIndex(int part, int index)
 410:     {
 411:       return getAtIndexImpl(part, index, 0);
 412:     }
 413:     
 414:     /**
 415:      * Gets the text located after index. null is returned if the index
 416:      * or part is invalid.
 417:      * 
 418:      * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE}
 419:      * @param index - index after the part
 420:      * 
 421:      * @return the part of text after that index, or null
 422:      */
 423:     public String getAfterIndex(int part, int index)
 424:     {
 425:       return getAtIndexImpl(part, index, 1);
 426:     }
 427: 
 428:     /**
 429:      * Gets the text located before index. null is returned if the index
 430:      * or part is invalid.
 431:      * 
 432:      * @param part - {@link #CHARACTER}, {@link #WORD}, or {@link #SENTENCE}
 433:      * @param index - index before the part
 434:      * 
 435:      * @return the part of text before that index, or null
 436:      */
 437:     public String getBeforeIndex(int part, int index)
 438:     {
 439:       return getAtIndexImpl(part, index, -1);
 440:     }
 441: 
 442:     /**
 443:      * Implements getAtIndex(), getBeforeIndex() and getAfterIndex().
 444:      *
 445:      * @param part the part to return, either CHARACTER, WORD or SENTENCE
 446:      * @param index the index
 447:      * @param dir the direction, -1 for backwards, 0 for here, +1 for forwards
 448:      *
 449:      * @return the resulting string
 450:      */
 451:     private String getAtIndexImpl(int part, int index, int dir)
 452:     {
 453:       String ret = null;
 454:       if (doc instanceof AbstractDocument)
 455:         ((AbstractDocument) doc).readLock();
 456:       try
 457:         {
 458:           BreakIterator iter = null;
 459:           switch (part)
 460:           {
 461:             case CHARACTER:
 462:               iter = BreakIterator.getCharacterInstance(getLocale());
 463:               break;
 464:             case WORD:
 465:               iter = BreakIterator.getWordInstance(getLocale());
 466:               break;
 467:             case SENTENCE:
 468:               iter = BreakIterator.getSentenceInstance(getLocale());
 469:               break;
 470:             default:
 471:               break;
 472:           }
 473:           String text = doc.getText(0, doc.getLength() - 1);
 474:           iter.setText(text);
 475:           int start = index;
 476:           int end = index;
 477:           switch (dir)
 478:           {
 479:           case 0:
 480:             if (iter.isBoundary(index))
 481:               {
 482:                 start = index;
 483:                 end = iter.following(index);
 484:               }
 485:             else
 486:               {
 487:                 start = iter.preceding(index);
 488:                 end = iter.next();
 489:               }
 490:             break;
 491:           case 1:
 492:             start = iter.following(index);
 493:             end = iter.next();
 494:             break;
 495:           case -1:
 496:             end = iter.preceding(index);
 497:             start = iter.previous();
 498:             break;
 499:           default:
 500:             assert false;
 501:           }
 502:           ret = text.substring(start, end);
 503:         }
 504:       catch (BadLocationException ex)
 505:         {
 506:           // Ignore (return null).
 507:         }
 508:       finally
 509:         {
 510:           if (doc instanceof AbstractDocument)
 511:             ((AbstractDocument) doc).readUnlock();
 512:         }
 513:       return ret;
 514:     }
 515: 
 516:     /**
 517:      * Returns the number of actions for this object. The zero-th
 518:      * object represents the default action.
 519:      * 
 520:      * @return the number of actions (0-based).
 521:      */
 522:     public int getAccessibleActionCount()
 523:     {
 524:       return getActions().length;
 525:     }
 526:     
 527:     /**
 528:      * Returns the description of the i-th action. Null is returned if
 529:      * i is out of bounds.
 530:      * 
 531:      * @param i - the action to get the description for
 532:      * 
 533:      * @return description of the i-th action
 534:      */
 535:     public String getAccessibleActionDescription(int i)
 536:     {
 537:       String desc = null;
 538:       Action[] actions = getActions();
 539:       if (i >= 0 && i < actions.length)
 540:         desc = (String) actions[i].getValue(Action.NAME);
 541:       return desc;
 542:     }
 543:     
 544:     /**
 545:      * Performs the i-th action. Nothing happens if i is 
 546:      * out of bounds.
 547:      *
 548:      * @param i - the action to perform
 549:      * 
 550:      * @return true if the action was performed successfully
 551:      */
 552:     public boolean doAccessibleAction(int i)
 553:     {
 554:       boolean ret = false;
 555:       Action[] actions = getActions();
 556:       if (i >= 0 && i < actions.length)
 557:         {
 558:           ActionEvent ev = new ActionEvent(JTextComponent.this,
 559:                                            ActionEvent.ACTION_PERFORMED, null);
 560:           actions[i].actionPerformed(ev);
 561:           ret = true;
 562:         }
 563:       return ret;
 564:     }
 565:     
 566:     /**
 567:      * Sets the text contents.
 568:      *
 569:      * @param s - the new text contents.
 570:      */
 571:     public void setTextContents(String s)
 572:     {
 573:       setText(s);
 574:     }
 575: 
 576:     /**
 577:      * Inserts the text at the given index.
 578:      *
 579:      * @param index - the index to insert the new text at.
 580:      * @param s - the new text
 581:      */
 582:     public void insertTextAtIndex(int index, String s)
 583:     {
 584:       try
 585:         {
 586:           doc.insertString(index, s, null);
 587:         }
 588:       catch (BadLocationException ex)
 589:         {
 590:           // What should we do with this?
 591:           ex.printStackTrace();
 592:         }
 593:     }
 594: 
 595:     /**
 596:      * Gets the text between two indexes.
 597:      *
 598:      * @param start - the starting index (inclusive)
 599:      * @param end - the ending index (exclusive)
 600:      */
 601:     public String getTextRange(int start, int end)
 602:     {
 603:       try
 604:       {
 605:         return JTextComponent.this.getText(start, end - start);
 606:       }
 607:       catch (BadLocationException ble)
 608:       {
 609:         return "";
 610:       }
 611:     }
 612: 
 613:     /**
 614:      * Deletes the text between two indexes.
 615:      *
 616:      * @param start - the starting index (inclusive)
 617:      * @param end - the ending index (exclusive)
 618:      */
 619:     public void delete(int start, int end)
 620:     {
 621:       replaceText(start, end, "");
 622:     }
 623: 
 624:     /**
 625:      * Cuts the text between two indexes. The text is put
 626:      * into the system clipboard.
 627:      *
 628:      * @param start - the starting index (inclusive)
 629:      * @param end - the ending index (exclusive)
 630:      */
 631:     public void cut(int start, int end)
 632:     {
 633:       JTextComponent.this.select(start, end);
 634:       JTextComponent.this.cut();
 635:     }
 636: 
 637:     /**
 638:      * Pastes the text from the system clipboard to the given index.
 639:      *
 640:      * @param start - the starting index
 641:      */
 642:     public void paste(int start)
 643:     {
 644:       JTextComponent.this.setCaretPosition(start);
 645:       JTextComponent.this.paste();
 646:     }
 647: 
 648:     /**
 649:      * Replaces the text between two indexes with the given text.
 650:      *
 651:      *
 652:      * @param start - the starting index (inclusive)
 653:      * @param end - the ending index (exclusive)
 654:      * @param s - the text to paste
 655:      */
 656:     public void replaceText(int start, int end, String s)
 657:     {
 658:       JTextComponent.this.select(start, end);
 659:       JTextComponent.this.replaceSelection(s);
 660:     }
 661: 
 662:     /**
 663:      * Selects the text between two indexes.
 664:      *
 665:      * @param start - the starting index (inclusive)
 666:      * @param end - the ending index (exclusive)
 667:      */
 668:     public void selectText(int start, int end)
 669:     {
 670:       JTextComponent.this.select(start, end);
 671:     }
 672: 
 673:     /**
 674:      * Sets the attributes of all the text between two indexes.
 675:      *
 676:      * @param start - the starting index (inclusive)
 677:      * @param end - the ending index (exclusive)
 678:      * @param s - the new attribute set for the text in the range
 679:      */
 680:     public void setAttributes(int start, int end, AttributeSet s)
 681:     {
 682:       if (doc instanceof StyledDocument)
 683:         {
 684:           StyledDocument sdoc = (StyledDocument) doc;
 685:           sdoc.setCharacterAttributes(start, end - start, s, true);
 686:         }
 687:     }
 688:   }
 689: 
 690:   public static class KeyBinding
 691:   {
 692:     public KeyStroke key;
 693:     public String actionName;
 694: 
 695:     /**
 696:      * Creates a new <code>KeyBinding</code> instance.
 697:      *
 698:      * @param key a <code>KeyStroke</code> value
 699:      * @param actionName a <code>String</code> value
 700:      */
 701:     public KeyBinding(KeyStroke key, String actionName)
 702:     {
 703:       this.key = key;
 704:       this.actionName = actionName;
 705:     }
 706:   }
 707: 
 708:   /**
 709:    * According to <a
 710:    * href="http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html">this
 711:    * report</a>, a pair of private classes wraps a {@link
 712:    * javax.swing.text.Keymap} in the new {@link InputMap} / {@link
 713:    * ActionMap} interfaces, such that old Keymap-using code can make use of
 714:    * the new framework.
 715:    *
 716:    * <p>A little bit of experimentation with these classes reveals the following
 717:    * structure:
 718:    *
 719:    * <ul>
 720:    *
 721:    * <li>KeymapWrapper extends {@link InputMap} and holds a reference to
 722:    * the underlying {@link Keymap}.</li>
 723:    *
 724:    * <li>KeymapWrapper maps {@link KeyStroke} objects to {@link Action}
 725:    * objects, by delegation to the underlying {@link Keymap}.</li>
 726:    *
 727:    * <li>KeymapActionMap extends {@link ActionMap} also holds a reference to
 728:    * the underlying {@link Keymap} but only appears to use it for listing 
 729:    * its keys. </li>
 730:    *
 731:    * <li>KeymapActionMap maps all {@link Action} objects to
 732:    * <em>themselves</em>, whether they exist in the underlying {@link
 733:    * Keymap} or not, and passes other objects to the parent {@link
 734:    * ActionMap} for resolving.
 735:    *
 736:    * </ul>
 737:    */
 738: 
 739:   private class KeymapWrapper extends InputMap
 740:   {
 741:     Keymap map;
 742: 
 743:     public KeymapWrapper(Keymap k)
 744:     {
 745:       map = k;
 746:     }
 747: 
 748:     public int size()
 749:     {
 750:       return map.getBoundKeyStrokes().length + super.size();
 751:     }
 752: 
 753:     public Object get(KeyStroke ks)
 754:     {
 755:       Action mapped = null;
 756:       Keymap m = map;
 757:       while(mapped == null && m != null)
 758:         {
 759:           mapped = m.getAction(ks);
 760:           if (mapped == null && ks.getKeyEventType() == KeyEvent.KEY_TYPED)
 761:             mapped = m.getDefaultAction();
 762:           if (mapped == null)
 763:             m = m.getResolveParent();
 764:         }
 765: 
 766:       if (mapped == null)
 767:         return super.get(ks);
 768:       else
 769:         return mapped;
 770:     }
 771: 
 772:     public KeyStroke[] keys()
 773:     {
 774:       KeyStroke[] superKeys = super.keys();
 775:       KeyStroke[] mapKeys = map.getBoundKeyStrokes(); 
 776:       KeyStroke[] bothKeys = new KeyStroke[superKeys.length + mapKeys.length];
 777:       for (int i = 0; i < superKeys.length; ++i)
 778:         bothKeys[i] = superKeys[i];
 779:       for (int i = 0; i < mapKeys.length; ++i)
 780:         bothKeys[i + superKeys.length] = mapKeys[i];
 781:       return bothKeys;
 782:     }
 783: 
 784:     public KeyStroke[] allKeys()
 785:     {
 786:       KeyStroke[] superKeys = super.allKeys();
 787:       KeyStroke[] mapKeys = map.getBoundKeyStrokes();
 788:       int skl = 0;
 789:       int mkl = 0;
 790:       if (superKeys != null)
 791:         skl = superKeys.length;
 792:       if (mapKeys != null)
 793:         mkl = mapKeys.length;
 794:       KeyStroke[] bothKeys = new KeyStroke[skl + mkl];
 795:       for (int i = 0; i < skl; ++i)
 796:         bothKeys[i] = superKeys[i];
 797:       for (int i = 0; i < mkl; ++i)
 798:         bothKeys[i + skl] = mapKeys[i];
 799:       return bothKeys;
 800:     }
 801:   }
 802: 
 803:   private class KeymapActionMap extends ActionMap
 804:   {
 805:     Keymap map;
 806: 
 807:     public KeymapActionMap(Keymap k)
 808:     {
 809:       map = k;
 810:     }
 811: 
 812:     public Action get(Object cmd)
 813:     {
 814:       if (cmd instanceof Action)
 815:         return (Action) cmd;
 816:       else
 817:         return super.get(cmd);
 818:     }
 819: 
 820:     public int size()
 821:     {
 822:       return map.getBoundKeyStrokes().length + super.size();
 823:     }
 824: 
 825:     public Object[] keys() 
 826:     {
 827:       Object[] superKeys = super.keys();
 828:       Object[] mapKeys = map.getBoundKeyStrokes(); 
 829:       Object[] bothKeys = new Object[superKeys.length + mapKeys.length];
 830:       for (int i = 0; i < superKeys.length; ++i)
 831:         bothKeys[i] = superKeys[i];
 832:       for (int i = 0; i < mapKeys.length; ++i)
 833:         bothKeys[i + superKeys.length] = mapKeys[i];
 834:       return bothKeys;      
 835:     }
 836: 
 837:     public Object[] allKeys()
 838:     {
 839:       Object[] superKeys = super.allKeys();
 840:       Object[] mapKeys = map.getBoundKeyStrokes(); 
 841:       Object[] bothKeys = new Object[superKeys.length + mapKeys.length];
 842:       for (int i = 0; i < superKeys.length; ++i)
 843:         bothKeys[i] = superKeys[i];
 844:       for (int i = 0; i < mapKeys.length; ++i)
 845:         bothKeys[i + superKeys.length] = mapKeys[i];
 846:       return bothKeys;
 847:     }
 848: 
 849:   }
 850: 
 851:   static class DefaultKeymap implements Keymap
 852:   {
 853:     String name;
 854:     Keymap parent;
 855:     Hashtable map;
 856:     Action defaultAction;
 857: 
 858:     public DefaultKeymap(String name)
 859:     {
 860:       this.name = name;
 861:       this.map = new Hashtable();
 862:     }
 863: 
 864:     public void addActionForKeyStroke(KeyStroke key, Action a)
 865:     {
 866:       map.put(key, a);
 867:     }
 868: 
 869:     /**
 870:      * Looks up a KeyStroke either in the current map or the parent Keymap;
 871:      * does <em>not</em> return the default action if lookup fails.
 872:      *
 873:      * @param key The KeyStroke to look up an Action for.
 874:      *
 875:      * @return The mapping for <code>key</code>, or <code>null</code>
 876:      * if no mapping exists in this Keymap or any of its parents.
 877:      */
 878:     public Action getAction(KeyStroke key)
 879:     {
 880:       if (map.containsKey(key))
 881:         return (Action) map.get(key);
 882:       else if (p