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