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