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