Source for javax.swing.plaf.basic.BasicMenuItemUI

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