Source for javax.swing.plaf.basic.BasicMenuItemUI

   1: /* BasicMenuItemUI.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.plaf.basic;
  40: 
  41: import java.awt.Color;
  42: import java.awt.Component;
  43: import java.awt.Container;
  44: import java.awt.Dimension;
  45: import java.awt.Font;
  46: import java.awt.FontMetrics;
  47: import java.awt.Graphics;
  48: import java.awt.Insets;
  49: import java.awt.Rectangle;
  50: import java.awt.event.ActionEvent;
  51: import java.awt.event.ItemEvent;
  52: import java.awt.event.ItemListener;
  53: import java.awt.event.KeyEvent;
  54: import java.awt.event.MouseEvent;
  55: import java.beans.PropertyChangeEvent;
  56: import java.beans.PropertyChangeListener;
  57: import java.util.ArrayList;
  58: 
  59: import javax.swing.AbstractAction;
  60: import javax.swing.ActionMap;
  61: import javax.swing.ButtonModel;
  62: import javax.swing.Icon;
  63: import javax.swing.InputMap;
  64: import javax.swing.JCheckBoxMenuItem;
  65: import javax.swing.JComponent;
  66: import javax.swing.JMenu;
  67: import javax.swing.JMenuItem;
  68: import javax.swing.JPopupMenu;
  69: import javax.swing.KeyStroke;
  70: import javax.swing.LookAndFeel;
  71: import javax.swing.MenuElement;
  72: import javax.swing.MenuSelectionManager;
  73: import javax.swing.SwingConstants;
  74: import javax.swing.SwingUtilities;
  75: import javax.swing.UIDefaults;
  76: import javax.swing.UIManager;
  77: import javax.swing.event.MenuDragMouseEvent;
  78: import javax.swing.event.MenuDragMouseListener;
  79: import javax.swing.event.MenuKeyEvent;
  80: import javax.swing.event.MenuKeyListener;
  81: import javax.swing.event.MouseInputListener;
  82: import javax.swing.plaf.ActionMapUIResource;
  83: import javax.swing.plaf.ComponentInputMapUIResource;
  84: import javax.swing.plaf.ComponentUI;
  85: import javax.swing.plaf.MenuItemUI;
  86: import javax.swing.text.View;
  87: 
  88: /**
  89:  * UI Delegate for JMenuItem.
  90:  */
  91: public class BasicMenuItemUI extends MenuItemUI
  92: {
  93:   /**
  94:    * Font to be used when displaying menu item's accelerator.
  95:    */
  96:   protected Font acceleratorFont;
  97: 
  98:   /**
  99:    * Color to be used when displaying menu item's accelerator.
 100:    */
 101:   protected Color acceleratorForeground;
 102: 
 103:   /**
 104:    * Color to be used when displaying menu item's accelerator when menu item is
 105:    * selected.
 106:    */
 107:   protected Color acceleratorSelectionForeground;
 108: 
 109:   /**
 110:    * Icon that is displayed after the text to indicated that this menu contains
 111:    * submenu.
 112:    */
 113:   protected Icon arrowIcon;
 114: 
 115:   /**
 116:    * Icon that is displayed before the text. This icon is only used in
 117:    * JCheckBoxMenuItem or JRadioBoxMenuItem.
 118:    */
 119:   protected Icon checkIcon;
 120: 
 121:   /**
 122:    * Number of spaces between icon and text.
 123:    */
 124:   protected int defaultTextIconGap = 4;
 125:   
 126:   /**
 127:    * Color of the text when menu item is disabled
 128:    */
 129:   protected Color disabledForeground;
 130: 
 131:   /**
 132:    * The menu Drag mouse listener listening to the menu item.
 133:    */
 134:   protected MenuDragMouseListener menuDragMouseListener;
 135: 
 136:   /**
 137:    * The menu item itself
 138:    */
 139:   protected JMenuItem menuItem;
 140: 
 141:   /**
 142:    * Menu Key listener listening to the menu item.
 143:    */
 144:   protected MenuKeyListener menuKeyListener;
 145: 
 146:   /**
 147:    * mouse input listener listening to menu item.
 148:    */
 149:   protected MouseInputListener mouseInputListener;
 150: 
 151:   /**
 152:    * Indicates if border should be painted
 153:    */
 154:   protected boolean oldBorderPainted;
 155: 
 156:   /**
 157:    * Color of text that is used when menu item is selected
 158:    */
 159:   protected Color selectionBackground;
 160: 
 161:   /**
 162:    * Color of the text that is used when menu item is selected.
 163:    */
 164:   protected Color selectionForeground;
 165: 
 166:   /**
 167:    * String that separates description of the modifiers and the key
 168:    */
 169:   private String acceleratorDelimiter;
 170: 
 171:   /**
 172:    * ItemListener to listen for item changes in the menu item
 173:    */
 174:   private ItemListener itemListener;
 175: 
 176:   /**
 177:    * Number of spaces between accelerator and menu item's label.
 178:    */
 179:   private int defaultAcceleratorLabelGap = 10;
 180: 
 181:   /**
 182:    * The gap between different menus on the MenuBar.
 183:    */
 184:   private int MenuGap = 10;
 185:   
 186:   /** A PropertyChangeListener to make UI updates after property changes **/
 187:   PropertyChangeHandler propertyChangeListener;
 188: 
 189:   /**
 190:    * The view rectangle used for layout of the menu item.
 191:    */
 192:   private Rectangle viewRect;
 193: 
 194:   /**
 195:    * The rectangle that holds the area of the label.
 196:    */
 197:   private Rectangle textRect;
 198: 
 199:   /**
 200:    * The rectangle that holds the area of the accelerator.
 201:    */
 202:   private Rectangle accelRect;
 203: 
 204:   /**
 205:    * The rectangle that holds the area of the icon.
 206:    */
 207:   private Rectangle iconRect;
 208: 
 209:   /**
 210:    * The rectangle that holds the area of the icon.
 211:    */
 212:   private Rectangle arrowIconRect;
 213: 
 214:   /**
 215:    * The rectangle that holds the area of the check icon.
 216:    */
 217:   private Rectangle checkIconRect;
 218: 
 219:   /**
 220:    * A rectangle used for temporary storage to avoid creation of new
 221:    * rectangles.
 222:    */
 223:   private Rectangle cachedRect;
 224: 
 225:   /**
 226:    * A class to handle PropertChangeEvents for the JMenuItem
 227:    * @author Anthony Balkissoon abalkiss at redhat dot com.   
 228:    */
 229:   class PropertyChangeHandler implements PropertyChangeListener
 230:   {
 231:     /**
 232:      * This method is called when a property of the menuItem is changed.
 233:      * Currently it is only used to update the accelerator key bindings.
 234:      * 
 235:      * @param e
 236:      *          the PropertyChangeEvent
 237:      */
 238:     public void propertyChange(PropertyChangeEvent e)
 239:     {
 240:       if (e.getPropertyName() == "accelerator")
 241:         {
 242:           InputMap map = SwingUtilities.getUIInputMap(menuItem, 
 243:               JComponent.WHEN_IN_FOCUSED_WINDOW);
 244:           if (map != null)
 245:             map.remove((KeyStroke) e.getOldValue());
 246:           else
 247:             map = new ComponentInputMapUIResource(menuItem);
 248: 
 249:           KeyStroke accelerator = (KeyStroke) e.getNewValue();
 250:           if (accelerator != null)
 251:             map.put(accelerator, "doClick");
 252:         }
 253:     }
 254:   }
 255:   
 256:   /**
 257:    * A class to handle accelerator keys.  This is the Action we will
 258:    * perform when the accelerator key for this JMenuItem is pressed.
 259:    * @author Anthony Balkissoon abalkiss at redhat dot com
 260:    *
 261:    */
 262:   class ClickAction extends AbstractAction
 263:   {
 264:     /**
 265:      * This is what is done when the accelerator key for the JMenuItem is
 266:      * pressed.
 267:      */
 268:     public void actionPerformed(ActionEvent event)
 269:     {
 270:       doClick(MenuSelectionManager.defaultManager());
 271:     }    
 272:   }
 273:   
 274:   /**
 275:    * Creates a new BasicMenuItemUI object.
 276:    */
 277:   public BasicMenuItemUI()
 278:   {
 279:     mouseInputListener = createMouseInputListener(menuItem);
 280:     menuDragMouseListener = createMenuDragMouseListener(menuItem);
 281:     menuKeyListener = createMenuKeyListener(menuItem);
 282:     itemListener = new ItemHandler();
 283:     propertyChangeListener = new PropertyChangeHandler();
 284: 
 285:     // Initialize rectangles for layout.
 286:     viewRect = new Rectangle();
 287:     textRect = new Rectangle();
 288:     iconRect = new Rectangle();
 289:     arrowIconRect = new Rectangle();
 290:     checkIconRect = new Rectangle();
 291:     accelRect = new Rectangle();
 292:     cachedRect = new Rectangle();
 293:   }
 294: 
 295:   /**
 296:    * Create MenuDragMouseListener to listen for mouse dragged events.
 297:    * 
 298:    * @param c
 299:    *          menu item to listen to
 300:    * @return The MenuDragMouseListener
 301:    */
 302:   protected MenuDragMouseListener createMenuDragMouseListener(JComponent c)
 303:   {
 304:     return new MenuDragMouseHandler();
 305:   }
 306: 
 307:   /**
 308:    * Creates MenuKeyListener to listen to key events occuring when menu item is
 309:    * visible on the screen.
 310:    * 
 311:    * @param c
 312:    *          menu item to listen to
 313:    * @return The MenuKeyListener
 314:    */
 315:   protected MenuKeyListener createMenuKeyListener(JComponent c)
 316:   {
 317:     return new MenuKeyHandler();
 318:   }
 319: 
 320:   /**
 321:    * Handles mouse input events occuring for this menu item
 322:    * 
 323:    * @param c
 324:    *          menu item to listen to
 325:    * @return The MouseInputListener
 326:    */
 327:   protected MouseInputListener createMouseInputListener(JComponent c)
 328:   {
 329:     return new MouseInputHandler();
 330:   }
 331: 
 332:   /**
 333:    * Factory method to create a BasicMenuItemUI for the given {@link
 334:    * JComponent}, which should be a {@link JMenuItem}.
 335:    * 
 336:    * @param c
 337:    *          The {@link JComponent} a UI is being created for.
 338:    * @return A BasicMenuItemUI for the {@link JComponent}.
 339:    */
 340:   public static ComponentUI createUI(JComponent c)
 341:   {
 342:     return new BasicMenuItemUI();
 343:   }
 344: 
 345:   /**
 346:    * Programatically clicks menu item.
 347:    * 
 348:    * @param msm
 349:    *          MenuSelectionManager for the menu hierarchy
 350:    */
 351:   protected void doClick(MenuSelectionManager msm)
 352:   {
 353:     menuItem.doClick();
 354:     msm.clearSelectedPath();
 355:   }
 356: 
 357:   /**
 358:    * Returns maximum size for the specified menu item
 359:    * 
 360:    * @param c
 361:    *          component for which to get maximum size
 362:    * @return Maximum size for the specified menu item.
 363:    */
 364:   public Dimension getMaximumSize(JComponent c)
 365:   {
 366:     return null;
 367:   }
 368: 
 369:   /**
 370:    * Returns minimum size for the specified menu item
 371:    * 
 372:    * @param c
 373:    *          component for which to get minimum size
 374:    * @return Minimum size for the specified menu item.
 375:    */
 376:   public Dimension getMinimumSize(JComponent c)
 377:   {
 378:     return null;
 379:   }
 380: 
 381:   /**
 382:    * Returns path to this menu item.
 383:    * 
 384:    * @return $MenuElement[]$ Returns array of menu elements that constitute a
 385:    *         path to this menu item.
 386:    */
 387:   public MenuElement[] getPath()
 388:   {
 389:     ArrayList path = new ArrayList();
 390: 
 391:     // Path to menu should also include its popup menu.
 392:     if (menuItem instanceof JMenu)
 393:       path.add(((JMenu) menuItem).getPopupMenu());
 394: 
 395:     Component c = menuItem;
 396:     while (c instanceof MenuElement)
 397:       {
 398:         path.add(0, (MenuElement) c);
 399: 
 400:         if (c instanceof JPopupMenu)
 401:           c = ((JPopupMenu) c).getInvoker();
 402:         else
 403:           c = c.getParent();
 404:       }
 405: 
 406:     MenuElement[] pathArray = new MenuElement[path.size()];
 407:     path.toArray(pathArray);
 408:     return pathArray;
 409:   }
 410: 
 411:   /**
 412:    * Returns preferred size for the given menu item.
 413:    * 
 414:    * @param c
 415:    *          menu item for which to get preferred size
 416:    * @param checkIcon
 417:    *          check icon displayed in the given menu item
 418:    * @param arrowIcon
 419:    *          arrow icon displayed in the given menu item
 420:    * @param defaultTextIconGap
 421:    *          space between icon and text in the given menuItem
 422:    * @return $Dimension$ preferred size for the given menu item
 423:    */
 424:   protected Dimension getPreferredMenuItemSize(JComponent c, Icon checkIcon,
 425:                                                Icon arrowIcon,
 426:                                                int defaultTextIconGap)
 427:   {
 428:     JMenuItem m = (JMenuItem) c;
 429:     String accelText = getAcceleratorString(m);
 430: 
 431:     // Layout the menu item. The result gets stored in the rectangle
 432:     // fields of this class.
 433:     layoutMenuItem(m, accelText);
 434: 
 435:     // The union of the text and icon areas is the label area.
 436:     cachedRect.setBounds(textRect);
 437:     Rectangle pref = SwingUtilities.computeUnion(iconRect.x, iconRect.y,
 438:                                                  iconRect.width,
 439:                                                  iconRect.height,
 440:                                                  cachedRect);
 441: 
 442:     // Find the widest menu item text and accelerator and store it in
 443:     // client properties of the parent, so that we can align the accelerators
 444:     // properly. Of course, we only need can do this, if the parent is
 445:     // a JComponent and this menu item is not a toplevel menu.
 446:     Container parent = m.getParent();
 447:     if (parent != null && parent instanceof JComponent
 448:         && !(m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
 449:       {
 450:         JComponent p = (JComponent) parent;
 451: 
 452:         // The widest text so far.
 453:         Integer maxTextWidth = (Integer) p.getClientProperty("maxTextWidth");
 454:         int maxTextValue = maxTextWidth == null ? 0 : maxTextWidth.intValue();
 455:         if (pref.width < maxTextValue)
 456:           pref.width = maxTextValue;
 457:         else
 458:           p.putClientProperty("maxTextWidth", new Integer(pref.width));
 459: 
 460:         // The widest accelerator so far.
 461:         Integer maxAccelWidth = (Integer) p.getClientProperty("maxAccelWidth");
 462:         int maxAccelValue = maxAccelWidth == null ? 0
 463:                                                   : maxAccelWidth.intValue();
 464:         if (accelRect.width > maxAccelValue)
 465:           {
 466:             maxAccelValue = accelRect.width;
 467:             p.putClientProperty("maxAccelWidth", new Integer(accelRect.width));
 468:           }
 469:         pref.width += maxAccelValue;
 470:         pref.width += defaultTextIconGap;
 471:       }
 472: 
 473:     // Add arrow and check size if appropriate.
 474:     if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
 475:       {
 476:         pref.width += checkIconRect.width;
 477:         pref.width += defaultTextIconGap;
 478:         pref.width += arrowIconRect.width;
 479:         pref.width += defaultTextIconGap;
 480:       }
 481: 
 482:     // Add a gap ~2 times as wide as the defaultTextIconGap.
 483:     pref.width += 2 * defaultTextIconGap;
 484: 
 485:     // Respect the insets of the menu item.
 486:     Insets i = m.getInsets();
 487:     pref.width += i.left + i.right;
 488:     pref.height += i.top + i.bottom;
 489: 
 490:     // Return a copy, so that nobody messes with our textRect.
 491:     return pref.getSize();
 492:   }
 493: 
 494:   /**
 495:    * Returns preferred size of the given component
 496:    * 
 497:    * @param c
 498:    *          component for which to return preferred size
 499:    * @return $Dimension$ preferred size for the given component
 500:    */
 501:   public Dimension getPreferredSize(JComponent c)
 502:   {
 503:     return getPreferredMenuItemSize(c, checkIcon, arrowIcon, 
 504:                                     defaultTextIconGap);
 505:   }
 506: 
 507:   /**
 508:    * Returns the prefix for entries in the {@link UIDefaults} table.
 509:    * 
 510:    * @return "MenuItem"
 511:    */
 512:   protected String getPropertyPrefix()
 513:   {
 514:     return "MenuItem";
 515:   }
 516: 
 517:   /**
 518:    * This method installs the components for this {@link JMenuItem}.
 519:    * 
 520:    * @param menuItem
 521:    *          The {@link JMenuItem} to install components for.
 522:    */
 523:   protected void installComponents(JMenuItem menuItem)
 524:   {
 525:     // FIXME: Need to implement
 526:   }
 527: 
 528:   /**
 529:    * This method installs the defaults that are defined in the Basic look and
 530:    * feel for this {@link JMenuItem}.
 531:    */
 532:   protected void installDefaults()
 533:   {
 534:     String prefix = getPropertyPrefix();
 535:     LookAndFeel.installBorder(menuItem, prefix + ".border");
 536:     LookAndFeel.installColorsAndFont(menuItem, prefix + ".background",
 537:                                      prefix + ".foreground", prefix + ".font");
 538:     menuItem.setMargin(UIManager.getInsets(prefix + ".margin"));
 539:     acceleratorFont = UIManager.getFont(prefix + ".acceleratorFont");
 540:     acceleratorForeground = UIManager.getColor(prefix 
 541:         + ".acceleratorForeground");
 542:     acceleratorSelectionForeground = UIManager.getColor(prefix 
 543:         + ".acceleratorSelectionForeground");
 544:     selectionBackground = UIManager.getColor(prefix + ".selectionBackground");
 545:     selectionForeground = UIManager.getColor(prefix + ".selectionForeground");
 546:     acceleratorDelimiter = UIManager.getString(prefix + ".acceleratorDelimiter");
 547:     checkIcon = UIManager.getIcon(prefix + ".checkIcon");
 548:     
 549:     menuItem.setHorizontalTextPosition(SwingConstants.TRAILING);
 550:     menuItem.setHorizontalAlignment(SwingConstants.LEADING);
 551:   }
 552: 
 553:   /**
 554:    * This method installs the keyboard actions for this {@link JMenuItem}.
 555:    */
 556:   protected void installKeyboardActions()
 557:   {
 558:     InputMap focusedWindowMap = SwingUtilities.getUIInputMap(menuItem, 
 559:         JComponent.WHEN_IN_FOCUSED_WINDOW);
 560:     if (focusedWindowMap == null)
 561:       focusedWindowMap = new ComponentInputMapUIResource(menuItem);
 562:     KeyStroke accelerator = menuItem.getAccelerator();
 563:     if (accelerator != null)
 564:       focusedWindowMap.put(accelerator, "doClick");
 565:     SwingUtilities.replaceUIInputMap(menuItem, 
 566:         JComponent.WHEN_IN_FOCUSED_WINDOW, focusedWindowMap);
 567:     
 568:     ActionMap UIActionMap = SwingUtilities.getUIActionMap(menuItem);
 569:     if (UIActionMap == null)
 570:       UIActionMap = new ActionMapUIResource();
 571:     UIActionMap.put("doClick", new ClickAction());
 572:     SwingUtilities.replaceUIActionMap(menuItem, UIActionMap);
 573:   }
 574: 
 575:   /**
 576:    * This method installs the listeners for the {@link JMenuItem}.
 577:    */
 578:   protected void installListeners()
 579:   {
 580:     menuItem.addMouseListener(mouseInputListener);
 581:     menuItem.addMouseMotionListener(mouseInputListener);
 582:     menuItem.addMenuDragMouseListener(menuDragMouseListener);
 583:     menuItem.addMenuKeyListener(menuKeyListener);
 584:     menuItem.addItemListener(itemListener);
 585:     menuItem.addPropertyChangeListener(propertyChangeListener);
 586:   }
 587: 
 588:   /**
 589:    * Installs and initializes all fields for this UI delegate. Any properties of
 590:    * the UI that need to be initialized and/or set to defaults will be done now.
 591:    * It will also install any listeners necessary.
 592:    * 
 593:    * @param c
 594:    *          The {@link JComponent} that is having this UI installed.
 595:    */
 596:   public void installUI(JComponent c)
 597:   {
 598:     super.installUI(c);
 599:     menuItem = (JMenuItem) c;
 600:     installDefaults();
 601:     installComponents(menuItem);
 602:     installListeners();
 603:     installKeyboardActions();
 604:   }
 605: 
 606:   /**
 607:    * Paints given menu item using specified graphics context
 608:    * 
 609:    * @param g
 610:    *          The graphics context used to paint this menu item
 611:    * @param c
 612:    *          Menu Item to paint
 613:    */
 614:   public void paint(Graphics g, JComponent c)
 615:   {
 616:     paintMenuItem(g, c, checkIcon, arrowIcon, selectionBackground,
 617:                   c.getForeground(), defaultTextIconGap);
 618:   }
 619: 
 620:   /**
 621:    * Paints background of the menu item
 622:    * 
 623:    * @param g
 624:    *          The graphics context used to paint this menu item
 625:    * @param menuItem
 626:    *          menu item to paint
 627:    * @param bgColor
 628:    *          Background color to use when painting menu item
 629:    */
 630:   protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor)
 631:   {
 632:     // Menu item is considered to be highlighted when it is selected.
 633:     // But we don't want to paint the background of JCheckBoxMenuItems
 634:     ButtonModel mod = menuItem.getModel();
 635:     Color saved = g.getColor();
 636:     if (mod.isArmed() || ((menuItem instanceof JMenu) && mod.isSelected()))
 637:       {
 638:         g.setColor(bgColor);
 639:         g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
 640:       }
 641:     else if (menuItem.isOpaque())
 642:       {
 643:         g.setColor(menuItem.getBackground());
 644:         g.fillRect(0, 0, menuItem.getWidth(), menuItem.getHeight());
 645:       }
 646:     g.setColor(saved);
 647:   }
 648: 
 649:   /**
 650:    * Paints specified menu item
 651:    * 
 652:    * @param g
 653:    *          The graphics context used to paint this menu item
 654:    * @param c
 655:    *          menu item to paint
 656:    * @param checkIcon
 657:    *          check icon to use when painting menu item
 658:    * @param arrowIcon
 659:    *          arrow icon to use when painting menu item
 660:    * @param background
 661:    *          Background color of the menu item
 662:    * @param foreground
 663:    *          Foreground color of the menu item
 664:    * @param defaultTextIconGap
 665:    *          space to use between icon and text when painting menu item
 666:    */
 667:   protected void paintMenuItem(Graphics g, JComponent c, Icon checkIcon,
 668:                                Icon arrowIcon, Color background,
 669:                                Color foreground, int defaultTextIconGap)
 670:   {
 671:     JMenuItem m = (JMenuItem) c;
 672: 
 673:     // Fetch fonts.
 674:     Font oldFont = g.getFont();
 675:     Font font = c.getFont();
 676:     g.setFont(font);
 677:     FontMetrics accelFm = m.getFontMetrics(acceleratorFont);
 678: 
 679:     // Create accelerator string.
 680:     String accelText = getAcceleratorString(m);
 681: 
 682:     // Layout menu item. The result gets stored in the rectangle fields
 683:     // of this class.
 684:     layoutMenuItem(m, accelText);
 685: 
 686:     // Paint the background.
 687:     paintBackground(g, m, background);
 688: 
 689:     Color oldColor = g.getColor();
 690: 
 691:     // Paint the check icon.
 692:     if (checkIcon != null)
 693:       {
 694:         checkIcon.paintIcon(m, g, checkIconRect.x, checkIconRect.y);
 695:       }
 696: 
 697:     // Paint the icon.
 698:     ButtonModel model = m.getModel();
 699:     if (m.getIcon() != null)
 700:       {
 701:         // Determine icon depending on the menu item
 702:         // state (normal/disabled/pressed).
 703:         Icon icon;
 704:         if (! m.isEnabled())
 705:           {
 706:             icon = m.getDisabledIcon();
 707:           }
 708:         else if (model.isPressed() && model.isArmed())
 709:           {
 710:             icon = m.getPressedIcon();
 711:             if (icon == null)
 712:               {
 713:                 icon = m.getIcon();
 714:               }
 715:           }
 716:         else
 717:           {
 718:             icon = m.getIcon();
 719:           }
 720: 
 721:         if (icon != null)
 722:           {
 723:             icon.paintIcon(m, g, iconRect.x, iconRect.y);
 724:           }
 725:       }
 726: 
 727:     // Paint the text.
 728:     String text = m.getText();
 729:     if (text != null)
 730:       {
 731:         // Handle HTML.
 732:         View html = (View) m.getClientProperty(BasicHTML.propertyKey);
 733:         if (html != null)
 734:           {
 735:             html.paint(g, textRect);
 736:           }
 737:         else
 738:           {
 739:             paintText(g, m, textRect, text);
 740:           }
 741:       }
 742: 
 743:     // Paint accelerator text.
 744:     if (! accelText.equals(""))
 745:       {
 746:         // Align the accelerator text. In getPreferredMenuItemSize() we
 747:         // store a client property 'maxAccelWidth' in the parent which holds
 748:         // the maximum accelerator width for the children of this parent.
 749:         // We use this here to align the accelerators properly.
 750:         int accelOffset = 0;
 751:         Container parent = m.getParent();
 752:         if (parent != null && parent instanceof JComponent)
 753:           {
 754:             JComponent p = (JComponent) parent;
 755:             Integer maxAccelWidth =
 756:               (Integer) p.getClientProperty("maxAccelWidth");
 757:             int maxAccelValue = maxAccelWidth == null ? 0
 758:                                                     : maxAccelWidth.intValue();
 759:             accelOffset = maxAccelValue - accelRect.width;
 760:           }
 761: 
 762:         g.setFont(acceleratorFont);
 763:         if (! m.isEnabled())
 764:           {
 765:             // Paint accelerator disabled.
 766:             g.setColor(disabledForeground);
 767:           }
 768:         else
 769:           {
 770:             if (m.isArmed() || (m instanceof JMenu && m.isSelected()))
 771:               g.setColor(acceleratorSelectionForeground);
 772:             else
 773:               g.setColor(acceleratorForeground);
 774:           }
 775:         g.drawString(accelText, accelRect.x - accelOffset,
 776:                      accelRect.y + accelFm.getAscent());
 777:       }
 778: 
 779:     // Paint arrow.
 780:     if (arrowIcon != null
 781:         && ! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
 782:       {
 783:         arrowIcon.paintIcon(m, g, arrowIconRect.x, arrowIconRect.y);
 784:       }
 785: 
 786:     g.setFont(oldFont);
 787:     g.setColor(oldColor);
 788: 
 789:   }
 790: 
 791:   /**
 792:    * Paints label for the given menu item
 793:    * 
 794:    * @param g
 795:    *          The graphics context used to paint this menu item
 796:    * @param menuItem
 797:    *          menu item for which to draw its label
 798:    * @param textRect
 799:    *          rectangle specifiying position of the text relative to the given
 800:    *          menu item
 801:    * @param text
 802:    *          label of the menu item
 803:    */
 804:   protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect,
 805:                            String text)
 806:   {
 807:     Font f = menuItem.getFont();
 808:     g.setFont(f);
 809:     FontMetrics fm = g.getFontMetrics(f);
 810: 
 811:     if (text != null && !text.equals(""))
 812:       {
 813:         if (menuItem.isEnabled())
 814:           {
 815:             // Menu item is considered to be highlighted when it is selected.
 816:             // But not if it's a JCheckBoxMenuItem
 817:             ButtonModel mod = menuItem.getModel();
 818:             if ((menuItem.isSelected() && checkIcon == null)
 819:                 || (mod != null && mod.isArmed())
 820:                 && (menuItem.getParent() instanceof MenuElement))
 821:               g.setColor(selectionForeground);
 822:             else
 823:               g.setColor(menuItem.getForeground());
 824:           }
 825:         else
 826:           // FIXME: should fix this to use 'disabledForeground', but its
 827:           // default value in BasicLookAndFeel is null.
 828: 
 829:           // FIXME: should there be different foreground colours for selected
 830:           // or deselected, when disabled?
 831:           g.setColor(Color.gray);
 832: 
 833:         int mnemonicIndex = menuItem.getDisplayedMnemonicIndex();
 834: 
 835:         if (mnemonicIndex != -1)
 836:           BasicGraphicsUtils.drawStringUnderlineCharAt(g, text, mnemonicIndex,
 837:                                                        textRect.x,
 838:                                                        textRect.y
 839:                                                            + fm.getAscent());
 840:         else
 841:           BasicGraphicsUtils.drawString(g, text, 0, textRect.x,
 842:                                         textRect.y + fm.getAscent());
 843:       }
 844:   }
 845: 
 846:   /**
 847:    * This method uninstalls the components for this {@link JMenuItem}.
 848:    * 
 849:    * @param menuItem
 850:    *          The {@link JMenuItem} to uninstall components for.
 851:    */
 852:   protected void uninstallComponents(JMenuItem menuItem)
 853:   {
 854:     // FIXME: need to implement
 855:   }
 856: 
 857:   /**
 858:    * This method uninstalls the defaults and sets any objects created during
 859:    * install to null
 860:    */
 861:   protected void uninstallDefaults()
 862:   {
 863:     menuItem.setForeground(null);
 864:     menuItem.setBackground(null);
 865:     menuItem.setBorder(null);
 866:     menuItem.setMargin(null);
 867:     menuItem.setBackground(null);
 868:     menuItem.setBorder(null);
 869:     menuItem.setFont(null);
 870:     menuItem.setForeground(null);
 871:     menuItem.setMargin(null);
 872:     acceleratorFont = null;
 873:     acceleratorForeground = null;
 874:     acceleratorSelectionForeground = null;
 875:     arrowIcon = null;
 876:     selectionBackground = null;
 877:     selectionForeground = null;
 878:     acceleratorDelimiter = null;
 879:   }
 880: 
 881:   /**
 882:    * Uninstalls any keyboard actions.
 883:    */
 884:   protected void uninstallKeyboardActions()
 885:   {   
 886:     SwingUtilities.replaceUIInputMap(menuItem,
 887:                                      JComponent.WHEN_IN_FOCUSED_WINDOW, null);
 888:   }
 889: 
 890:   /**
 891:    * Unregisters all the listeners that this UI delegate was using.
 892:    */
 893:   protected void uninstallListeners()
 894:   {
 895:     menuItem.removeMouseListener(mouseInputListener);
 896:     menuItem.removeMenuDragMouseListener(menuDragMouseListener);
 897:     menuItem.removeMenuKeyListener(menuKeyListener);
 898:     menuItem.removeItemListener(itemListener);
 899:     menuItem.removePropertyChangeListener(propertyChangeListener);
 900:   }
 901: 
 902:   /**
 903:    * Performs the opposite of installUI. Any properties or resources that need
 904:    * to be cleaned up will be done now. It will also uninstall any listeners it
 905:    * has. In addition, any properties of this UI will be nulled.
 906:    * 
 907:    * @param c
 908:    *          The {@link JComponent} that is having this UI uninstalled.
 909:    */
 910:   public void uninstallUI(JComponent c)
 911:   {
 912:     uninstallListeners();
 913:     uninstallDefaults();
 914:     uninstallComponents(menuItem);
 915:     menuItem = null;
 916:   }
 917: 
 918:   /**
 919:    * This method calls paint.
 920:    * 
 921:    * @param g
 922:    *          The graphics context used to paint this menu item
 923:    * @param c
 924:    *          The menu item to paint
 925:    */
 926:   public void update(Graphics g, JComponent c)
 927:   {
 928:     paint(g, c);
 929:   }
 930: 
 931:   /**
 932:    * Return text representation of the specified accelerator
 933:    * 
 934:    * @param accelerator
 935:    *          Accelerator for which to return string representation
 936:    * @return $String$ Text representation of the given accelerator
 937:    */
 938:   private String getAcceleratorText(KeyStroke accelerator)
 939:   {
 940:     // convert keystroke into string format
 941:     String modifiersText = "";
 942:     int modifiers = accelerator.getModifiers();
 943:     char keyChar = accelerator.getKeyChar();
 944:     int keyCode = accelerator.getKeyCode();
 945: 
 946:     if (modifiers != 0)
 947:       modifiersText = KeyEvent.getKeyModifiersText(modifiers)
 948:                       + acceleratorDelimiter;
 949: 
 950:     if (keyCode == KeyEvent.VK_UNDEFINED)
 951:       return modifiersText + keyChar;
 952:     else
 953:       return modifiersText + KeyEvent.getKeyText(keyCode);
 954:   }
 955: 
 956:   /**
 957:    * Calculates and return rectange in which accelerator should be displayed
 958:    * 
 959:    * @param accelerator
 960:    *          accelerator for which to return the display rectangle
 961:    * @param fm
 962:    *          The font metrics used to measure the text
 963:    * @return $Rectangle$ reactangle which will be used to display accelerator
 964:    */
 965:   private Rectangle getAcceleratorRect(KeyStroke accelerator, FontMetrics fm)
 966:   {
 967:     int width = fm.stringWidth(getAcceleratorText(accelerator));
 968:     int height = fm.getHeight();
 969:     return new Rectangle(0, 0, width, height);
 970:   }
 971: 
 972:   /**
 973:    * This class handles mouse events occuring inside the menu item. Most of the
 974:    * events are forwarded for processing to MenuSelectionManager of the current
 975:    * menu hierarchy.
 976:    */
 977:   protected class MouseInputHandler implements MouseInputListener
 978:   {
 979:     /**
 980:      * Creates a new MouseInputHandler object.
 981:      */
 982:     protected MouseInputHandler()
 983:     {
 984:       // Nothing to do here.
 985:     }
 986: 
 987:     /**
 988:      * This method is called when mouse is clicked on the menu item. It forwards
 989:      * this event to MenuSelectionManager.
 990:      * 
 991:      * @param e
 992:      *          A {@link MouseEvent}.
 993:      */
 994:     public void mouseClicked(MouseEvent e)
 995:     {
 996:       MenuSelectionManager manager = MenuSelectionManager.defaultManager();
 997:       manager.processMouseEvent(e);
 998:     }
 999: 
1000:     /**
1001:      * This method is called when mouse is dragged inside the menu item. It
1002:      * forwards this event to MenuSelectionManager.
1003:      * 
1004:      * @param e
1005:      *          A {@link MouseEvent}.
1006:      */
1007:     public void mouseDragged(MouseEvent e)
1008:     {
1009:       MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1010:       manager.processMouseEvent(e);
1011:     }
1012: 
1013:     /**
1014:      * This method is called when mouse enters menu item. When this happens menu
1015:      * item is considered to be selected and selection path in
1016:      * MenuSelectionManager is set. This event is also forwarded to
1017:      * MenuSelection Manager for further processing.
1018:      * 
1019:      * @param e
1020:      *          A {@link MouseEvent}.
1021:      */
1022:     public void mouseEntered(MouseEvent e)
1023:     {
1024:       Component source = (Component) e.getSource();
1025:       if (source.getParent() instanceof MenuElement)
1026:         {
1027:           MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1028:           manager.setSelectedPath(getPath());
1029:           manager.processMouseEvent(e);
1030:         }
1031:     }
1032: 
1033:     /**
1034:      * This method is called when mouse exits menu item. The event is forwarded
1035:      * to MenuSelectionManager for processing.
1036:      * 
1037:      * @param e
1038:      *          A {@link MouseEvent}.
1039:      */
1040:     public void mouseExited(MouseEvent e)
1041:     {
1042:       MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1043:       manager.processMouseEvent(e);
1044:     }
1045: 
1046:     /**
1047:      * This method is called when mouse is inside the menu item. This event is
1048:      * forwarder to MenuSelectionManager for further processing.
1049:      * 
1050:      * @param e
1051:      *          A {@link MouseEvent}.
1052:      */
1053:     public void mouseMoved(MouseEvent e)
1054:     {
1055:       MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1056:       manager.processMouseEvent(e);
1057:     }
1058: 
1059:     /**
1060:      * This method is called when mouse is pressed. This event is forwarded to
1061:      * MenuSelectionManager for further processing.
1062:      * 
1063:      * @param e
1064:      *          A {@link MouseEvent}.
1065:      */
1066:     public void mousePressed(MouseEvent e)
1067:     {
1068:       MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1069:       manager.processMouseEvent(e);
1070:     }
1071: 
1072:     /**
1073:      * This method is called when mouse is released. If the mouse is released
1074:      * inside this menuItem, then this menu item is considered to be chosen and
1075:      * the menu hierarchy should be closed.
1076:      * 
1077:      * @param e
1078:      *          A {@link MouseEvent}.
1079:      */
1080:     public void mouseReleased(MouseEvent e)
1081:     {
1082:       Rectangle size = menuItem.getBounds();
1083:       MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1084:       if (e.getX() > 0 && e.getX() < size.width && e.getY() > 0
1085:           && e.getY() < size.height)
1086:         {
1087:           manager.clearSelectedPath();
1088:           menuItem.doClick();
1089:         }
1090: 
1091:       else
1092:         manager.processMouseEvent(e);
1093:     }
1094:   }
1095: 
1096:   /**
1097:    * This class handles mouse dragged events.
1098:    */
1099:   private class MenuDragMouseHandler implements MenuDragMouseListener
1100:   {
1101:     /**
1102:      * Tbis method is invoked when mouse is dragged over the menu item.
1103:      * 
1104:      * @param e
1105:      *          The MenuDragMouseEvent
1106:      */
1107:     public void menuDragMouseDragged(MenuDragMouseEvent e)
1108:     {
1109:       MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1110:       manager.setSelectedPath(e.getPath());
1111:     }
1112: 
1113:     /**
1114:      * Tbis method is invoked when mouse enters the menu item while it is being
1115:      * dragged.
1116:      * 
1117:      * @param e
1118:      *          The MenuDragMouseEvent
1119:      */
1120:     public void menuDragMouseEntered(MenuDragMouseEvent e)
1121:     {
1122:       MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1123:       manager.setSelectedPath(e.getPath());
1124:     }
1125: 
1126:     /**
1127:      * Tbis method is invoked when mouse exits the menu item while it is being
1128:      * dragged
1129:      * 
1130:      * @param e the MenuDragMouseEvent
1131:      */
1132:     public void menuDragMouseExited(MenuDragMouseEvent e)
1133:     {
1134:       // TODO: What should be done here, if anything?
1135:     }
1136: 
1137:     /**
1138:      * Tbis method is invoked when mouse was dragged and released inside the
1139:      * menu item.
1140:      * 
1141:      * @param e
1142:      *          The MenuDragMouseEvent
1143:      */
1144:     public void menuDragMouseReleased(MenuDragMouseEvent e)
1145:     {
1146:       MenuElement[] path = e.getPath();
1147: 
1148:       if (path[path.length - 1] instanceof JMenuItem)
1149:         ((JMenuItem) path[path.length - 1]).doClick();
1150: 
1151:       MenuSelectionManager manager = MenuSelectionManager.defaultManager();
1152:       manager.clearSelectedPath();
1153:     }
1154:   }
1155: 
1156:   /**
1157:    * This class handles key events occuring when menu item is visible on the
1158:    * screen.
1159:    */
1160:   private class MenuKeyHandler implements MenuKeyListener
1161:   {
1162:     /**
1163:      * This method is invoked when key has been pressed
1164:      * 
1165:      * @param e
1166:      *          A {@link MenuKeyEvent}.
1167:      */
1168:     public void menuKeyPressed(MenuKeyEvent e)
1169:     {
1170:       // TODO: What should be done here, if anything?
1171:     }
1172: 
1173:     /**
1174:      * This method is invoked when key has been pressed
1175:      * 
1176:      * @param e
1177:      *          A {@link MenuKeyEvent}.
1178:      */
1179:     public void menuKeyReleased(MenuKeyEvent e)
1180:     {
1181:       // TODO: What should be done here, if anything?
1182:     }
1183: 
1184:     /**
1185:      * This method is invoked when key has been typed It handles the mnemonic
1186:      * key for the menu item.
1187:      * 
1188:      * @param e
1189:      *          A {@link MenuKeyEvent}.
1190:      */
1191:     public void menuKeyTyped(MenuKeyEvent e)
1192:     {
1193:       // TODO: What should be done here, if anything?
1194:     }
1195:   }
1196:   
1197:   /**
1198:    * Helper class that listens for item changes to the properties of the {@link
1199:    * JMenuItem}.
1200:    */
1201:   private class ItemHandler implements ItemListener
1202:   {
1203:     /**
1204:      * This method is called when one of the menu item changes.
1205:      *
1206:      * @param evt A {@link ItemEvent}.
1207:      */
1208:     public void itemStateChanged(ItemEvent evt)
1209:     {
1210:       boolean state = false;
1211:       if (menuItem instanceof JCheckBoxMenuItem)
1212:         {
1213:           if (evt.getStateChange() == ItemEvent.SELECTED)
1214:             state = true;
1215:           ((JCheckBoxMenuItem) menuItem).setState(state);
1216:         }
1217:       menuItem.revalidate();
1218:       menuItem.repaint();
1219:     }
1220:   }
1221: 
1222:   /**
1223:    * A helper method to create the accelerator string from the menu item's
1224:    * accelerator property. The returned string is empty if there is
1225:    * no accelerator defined.
1226:    *
1227:    * @param m the menu item
1228:    *
1229:    * @return the accelerator string, not null
1230:    */
1231:   private String getAcceleratorString(JMenuItem m)
1232:   {
1233:     // Create accelerator string.
1234:     KeyStroke accel = m.getAccelerator();
1235:     String accelText = "";
1236:     if (accel != null)
1237:       {
1238:         int mods = accel.getModifiers();
1239:         if (mods > 0)
1240:           {
1241:             accelText = KeyEvent.getKeyModifiersText(mods);
1242:             accelText += acceleratorDelimiter;
1243:           }
1244:         int keycode = accel.getKeyCode();
1245:         if (keycode != 0)
1246:           accelText += KeyEvent.getKeyText(keycode);
1247:         else
1248:           accelText += accel.getKeyChar();
1249:       }
1250:     return accelText;
1251:   }
1252: 
1253:   /**
1254:    * A helper method that lays out the menu item. The layout is stored
1255:    * in the fields of this class.
1256:    *
1257:    * @param m the menu item to layout
1258:    * @param accelText the accelerator text
1259:    */
1260:   private void layoutMenuItem(JMenuItem m, String accelText)
1261:   {
1262:     int width = m.getWidth();
1263:     int height = m.getHeight();
1264: 
1265:     // Reset rectangles.
1266:     iconRect.setBounds(0, 0, 0, 0);
1267:     textRect.setBounds(0, 0, 0, 0);
1268:     accelRect.setBounds(0, 0, 0, 0);
1269:     checkIconRect.setBounds(0, 0, 0, 0);
1270:     arrowIconRect.setBounds(0, 0, 0, 0);
1271:     viewRect.setBounds(0, 0, width, height);
1272: 
1273:     // Substract insets to the view rect.
1274:     Insets insets = m.getInsets();
1275:     viewRect.x += insets.left;
1276:     viewRect.y += insets.top;
1277:     viewRect.width -= insets.left + insets.right;
1278:     viewRect.height -= insets.top + insets.bottom;
1279: 
1280:     // Fetch the fonts.
1281:     Font font = m.getFont();
1282:     FontMetrics fm = m.getFontMetrics(font);
1283:     FontMetrics accelFm = m.getFontMetrics(acceleratorFont);
1284: 
1285:     String text = m.getText();
1286:     SwingUtilities.layoutCompoundLabel(m, fm, text, m.getIcon(),
1287:                                        m.getVerticalAlignment(),
1288:                                        m.getHorizontalAlignment(),
1289:                                        m.getVerticalTextPosition(),
1290:                                        m.getHorizontalTextPosition(),
1291:                                        viewRect, iconRect, textRect,
1292:                                        defaultTextIconGap);
1293: 
1294:     // Initialize accelerator width and height.
1295:     if (! accelText.equals(""))
1296:       {
1297:         accelRect.width = accelFm.stringWidth(accelText);
1298:         accelRect.height = accelFm.getHeight();
1299:       }
1300: 
1301:     // Initialize check and arrow icon width and height.
1302:     if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
1303:       {
1304:         if (checkIcon != null)
1305:           {
1306:             checkIconRect.width = checkIcon.getIconWidth();
1307:             checkIconRect.height = checkIcon.getIconHeight();
1308:           }
1309:         if (arrowIcon != null)
1310:           {
1311:             arrowIconRect.width = arrowIcon.getIconWidth();
1312:             arrowIconRect.height = arrowIcon.getIconHeight();
1313:           }
1314:       }
1315: 
1316:     // The union of the icon and text of the menu item is the 'label area'.
1317:     cachedRect.setBounds(textRect);
1318:     Rectangle labelRect = SwingUtilities.computeUnion(iconRect.x,
1319:                                                       iconRect.y,
1320:                                                       iconRect.width,
1321:                                                       iconRect.height,
1322:                                                       cachedRect);
1323:     textRect.x += defaultTextIconGap;
1324:     iconRect.x += defaultTextIconGap;
1325: 
1326:     // Layout accelerator rect.
1327:     accelRect.x = viewRect.x + viewRect.width - arrowIconRect.width
1328:       - defaultTextIconGap - accelRect.width;
1329:     // Layout check and arrow icons only when not in toplevel menu.
1330:     if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
1331:       {
1332:         checkIconRect.x = viewRect.x + defaultTextIconGap;
1333:         textRect.x += defaultTextIconGap + checkIconRect.width;
1334:         iconRect.x += defaultTextIconGap + checkIconRect.width;
1335:         arrowIconRect.x = viewRect.x + viewRect.width - defaultTextIconGap
1336:           - arrowIconRect.width;
1337:       }
1338: 
1339:     // Align the accelerator text and all the icons vertically centered to
1340:     // the menu text.
1341:     accelRect.y = labelRect.y + (labelRect.height / 2)
1342:       - (accelRect.height / 2);
1343:     if (! (m instanceof JMenu && ((JMenu) m).isTopLevelMenu()))
1344:       {
1345:         arrowIconRect.y = labelRect.y + (labelRect.height / 2)
1346:           - (arrowIconRect.height / 2);
1347:         checkIconRect.y = labelRect.y + (labelRect.height / 2)
1348:           - (checkIconRect.height / 2);
1349:       }
1350:   }
1351: }