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