1 /* 2 * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javax.swing.plaf.basic; 27 28 import javax.swing.plaf.TextUIDrawing; 29 import java.awt.*; 30 import java.awt.event.*; 31 import java.beans.PropertyChangeEvent; 32 import java.beans.PropertyChangeListener; 33 34 import javax.swing.*; 35 import javax.swing.event.*; 36 import javax.swing.plaf.*; 37 import javax.swing.text.View; 38 39 import sun.swing.*; 40 41 /** 42 * BasicMenuItem implementation 43 * 44 * @author Georges Saab 45 * @author David Karlton 46 * @author Arnaud Weber 47 * @author Fredrik Lagerblad 48 */ 49 public class BasicMenuItemUI extends MenuItemUI 50 { 51 /** 52 * The instance of {@code JMenuItem}. 53 */ 54 protected JMenuItem menuItem = null; 55 /** 56 * The color of the selection background. 57 */ 58 protected Color selectionBackground; 59 /** 60 * The color of the selection foreground. 61 */ 62 protected Color selectionForeground; 63 /** 64 * The color of the disabled foreground. 65 */ 66 protected Color disabledForeground; 67 /** 68 * The color of the accelerator foreground. 69 */ 70 protected Color acceleratorForeground; 71 /** 72 * The color of the accelerator selection. 73 */ 74 protected Color acceleratorSelectionForeground; 75 76 /** 77 * Accelerator delimiter string, such as {@code '+'} in {@code 'Ctrl+C'}. 78 * @since 1.7 79 */ 80 protected String acceleratorDelimiter; 81 82 /** 83 * The gap between the text and the icon. 84 */ 85 protected int defaultTextIconGap; 86 /** 87 * The accelerator font. 88 */ 89 protected Font acceleratorFont; 90 91 /** 92 * The instance of {@code MouseInputListener}. 93 */ 94 protected MouseInputListener mouseInputListener; 95 /** 96 * The instance of {@code MenuDragMouseListener}. 97 */ 98 protected MenuDragMouseListener menuDragMouseListener; 99 /** 100 * The instance of {@code MenuKeyListener}. 101 */ 102 protected MenuKeyListener menuKeyListener; 103 /** 104 * {@code PropertyChangeListener} returned from 105 * {@code createPropertyChangeListener}. You should not 106 * need to access this field, rather if you want to customize the 107 * {@code PropertyChangeListener} override 108 * {@code createPropertyChangeListener}. 109 * 110 * @since 1.6 111 * @see #createPropertyChangeListener 112 */ 113 protected PropertyChangeListener propertyChangeListener; 114 // BasicMenuUI also uses this. 115 Handler handler; 116 /** 117 * The arrow icon. 118 */ 119 protected Icon arrowIcon = null; 120 /** 121 * The check icon. 122 */ 123 protected Icon checkIcon = null; 124 /** 125 * The value represents if the old border is painted. 126 */ 127 protected boolean oldBorderPainted; 128 129 /* diagnostic aids -- should be false for production builds. */ 130 private static final boolean TRACE = false; // trace creates and disposes 131 132 private static final boolean VERBOSE = false; // show reuse hits/misses 133 private static final boolean DEBUG = false; // show bad params, misc. 134 135 static void loadActionMap(LazyActionMap map) { 136 // NOTE: BasicMenuUI also calls into this method. 137 map.put(new Actions(Actions.CLICK)); 138 BasicLookAndFeel.installAudioActionMap(map); 139 } 140 141 /** 142 * Returns a new instance of {@code BasicMenuItemUI}. 143 * 144 * @param c a component 145 * @return a new instance of {@code BasicMenuItemUI} 146 */ 147 public static ComponentUI createUI(JComponent c) { 148 return new BasicMenuItemUI(); 149 } 150 151 public void installUI(JComponent c) { 152 super.installUI(c); 153 menuItem = (JMenuItem) c; 154 155 installDefaults(); 156 installComponents(menuItem); 157 installListeners(); 158 installKeyboardActions(); 159 } 160 161 /** 162 * Installs default properties. 163 */ 164 protected void installDefaults() { 165 String prefix = getPropertyPrefix(); 166 167 acceleratorFont = UIManager.getFont("MenuItem.acceleratorFont"); 168 // use default if missing so that BasicMenuItemUI can be used in other 169 // LAFs like Nimbus 170 if (acceleratorFont == null) { 171 acceleratorFont = UIManager.getFont("MenuItem.font"); 172 } 173 174 Object opaque = UIManager.get(getPropertyPrefix() + ".opaque"); 175 if (opaque != null) { 176 LookAndFeel.installProperty(menuItem, "opaque", opaque); 177 } 178 else { 179 LookAndFeel.installProperty(menuItem, "opaque", Boolean.TRUE); 180 } 181 if(menuItem.getMargin() == null || 182 (menuItem.getMargin() instanceof UIResource)) { 183 menuItem.setMargin(UIManager.getInsets(prefix + ".margin")); 184 } 185 186 LookAndFeel.installProperty(menuItem, "iconTextGap", Integer.valueOf(4)); 187 defaultTextIconGap = menuItem.getIconTextGap(); 188 189 LookAndFeel.installBorder(menuItem, prefix + ".border"); 190 oldBorderPainted = menuItem.isBorderPainted(); 191 LookAndFeel.installProperty(menuItem, "borderPainted", 192 UIManager.getBoolean(prefix + ".borderPainted")); 193 LookAndFeel.installColorsAndFont(menuItem, 194 prefix + ".background", 195 prefix + ".foreground", 196 prefix + ".font"); 197 198 // MenuItem specific defaults 199 if (selectionBackground == null || 200 selectionBackground instanceof UIResource) { 201 selectionBackground = 202 UIManager.getColor(prefix + ".selectionBackground"); 203 } 204 if (selectionForeground == null || 205 selectionForeground instanceof UIResource) { 206 selectionForeground = 207 UIManager.getColor(prefix + ".selectionForeground"); 208 } 209 if (disabledForeground == null || 210 disabledForeground instanceof UIResource) { 211 disabledForeground = 212 UIManager.getColor(prefix + ".disabledForeground"); 213 } 214 if (acceleratorForeground == null || 215 acceleratorForeground instanceof UIResource) { 216 acceleratorForeground = 217 UIManager.getColor(prefix + ".acceleratorForeground"); 218 } 219 if (acceleratorSelectionForeground == null || 220 acceleratorSelectionForeground instanceof UIResource) { 221 acceleratorSelectionForeground = 222 UIManager.getColor(prefix + ".acceleratorSelectionForeground"); 223 } 224 // Get accelerator delimiter 225 acceleratorDelimiter = 226 UIManager.getString("MenuItem.acceleratorDelimiter"); 227 if (acceleratorDelimiter == null) { acceleratorDelimiter = "+"; } 228 // Icons 229 if (arrowIcon == null || 230 arrowIcon instanceof UIResource) { 231 arrowIcon = UIManager.getIcon(prefix + ".arrowIcon"); 232 } 233 if (checkIcon == null || 234 checkIcon instanceof UIResource) { 235 checkIcon = UIManager.getIcon(prefix + ".checkIcon"); 236 //In case of column layout, .checkIconFactory is defined for this UI, 237 //the icon is compatible with it and useCheckAndArrow() is true, 238 //then the icon is handled by the checkIcon. 239 boolean isColumnLayout = MenuItemLayoutHelper.isColumnLayout( 240 BasicGraphicsUtils.isLeftToRight(menuItem), menuItem); 241 if (isColumnLayout) { 242 MenuItemCheckIconFactory iconFactory = 243 (MenuItemCheckIconFactory) UIManager.get(prefix 244 + ".checkIconFactory"); 245 if (iconFactory != null 246 && MenuItemLayoutHelper.useCheckAndArrow(menuItem) 247 && iconFactory.isCompatible(checkIcon, prefix)) { 248 checkIcon = iconFactory.getIcon(menuItem); 249 } 250 } 251 } 252 } 253 254 /** 255 * 256 * @param menuItem a menu item 257 * @since 1.3 258 */ 259 protected void installComponents(JMenuItem menuItem){ 260 BasicHTML.updateRenderer(menuItem, menuItem.getText()); 261 } 262 263 /** 264 * Returns a property prefix. 265 * 266 * @return a property prefix 267 */ 268 protected String getPropertyPrefix() { 269 return "MenuItem"; 270 } 271 272 /** 273 * Registers listeners. 274 */ 275 protected void installListeners() { 276 if ((mouseInputListener = createMouseInputListener(menuItem)) != null) { 277 menuItem.addMouseListener(mouseInputListener); 278 menuItem.addMouseMotionListener(mouseInputListener); 279 } 280 if ((menuDragMouseListener = createMenuDragMouseListener(menuItem)) != null) { 281 menuItem.addMenuDragMouseListener(menuDragMouseListener); 282 } 283 if ((menuKeyListener = createMenuKeyListener(menuItem)) != null) { 284 menuItem.addMenuKeyListener(menuKeyListener); 285 } 286 if ((propertyChangeListener = createPropertyChangeListener(menuItem)) != null) { 287 menuItem.addPropertyChangeListener(propertyChangeListener); 288 } 289 } 290 291 /** 292 * Registers keyboard action. 293 */ 294 protected void installKeyboardActions() { 295 installLazyActionMap(); 296 updateAcceleratorBinding(); 297 } 298 299 void installLazyActionMap() { 300 LazyActionMap.installLazyActionMap(menuItem, BasicMenuItemUI.class, 301 getPropertyPrefix() + ".actionMap"); 302 } 303 304 public void uninstallUI(JComponent c) { 305 menuItem = (JMenuItem)c; 306 uninstallDefaults(); 307 uninstallComponents(menuItem); 308 uninstallListeners(); 309 uninstallKeyboardActions(); 310 MenuItemLayoutHelper.clearUsedParentClientProperties(menuItem); 311 menuItem = null; 312 } 313 314 /** 315 * Uninstalls default properties. 316 */ 317 protected void uninstallDefaults() { 318 LookAndFeel.uninstallBorder(menuItem); 319 LookAndFeel.installProperty(menuItem, "borderPainted", oldBorderPainted); 320 if (menuItem.getMargin() instanceof UIResource) 321 menuItem.setMargin(null); 322 if (arrowIcon instanceof UIResource) 323 arrowIcon = null; 324 if (checkIcon instanceof UIResource) 325 checkIcon = null; 326 } 327 328 /** 329 * Unregisters components. 330 * 331 * @param menuItem a menu item 332 * @since 1.3 333 */ 334 protected void uninstallComponents(JMenuItem menuItem){ 335 BasicHTML.updateRenderer(menuItem, ""); 336 } 337 338 /** 339 * Unregisters listeners. 340 */ 341 protected void uninstallListeners() { 342 if (mouseInputListener != null) { 343 menuItem.removeMouseListener(mouseInputListener); 344 menuItem.removeMouseMotionListener(mouseInputListener); 345 } 346 if (menuDragMouseListener != null) { 347 menuItem.removeMenuDragMouseListener(menuDragMouseListener); 348 } 349 if (menuKeyListener != null) { 350 menuItem.removeMenuKeyListener(menuKeyListener); 351 } 352 if (propertyChangeListener != null) { 353 menuItem.removePropertyChangeListener(propertyChangeListener); 354 } 355 356 mouseInputListener = null; 357 menuDragMouseListener = null; 358 menuKeyListener = null; 359 propertyChangeListener = null; 360 handler = null; 361 } 362 363 /** 364 * Unregisters keyboard actions. 365 */ 366 protected void uninstallKeyboardActions() { 367 SwingUtilities.replaceUIActionMap(menuItem, null); 368 SwingUtilities.replaceUIInputMap(menuItem, JComponent. 369 WHEN_IN_FOCUSED_WINDOW, null); 370 } 371 372 /** 373 * Returns an instance of {@code MouseInputListener}. 374 * 375 * @param c a component 376 * @return an instance of {@code MouseInputListener} 377 */ 378 protected MouseInputListener createMouseInputListener(JComponent c) { 379 return getHandler(); 380 } 381 382 /** 383 * Returns an instance of {@code MenuDragMouseListener}. 384 * 385 * @param c a component 386 * @return an instance of {@code MenuDragMouseListener} 387 */ 388 protected MenuDragMouseListener createMenuDragMouseListener(JComponent c) { 389 return getHandler(); 390 } 391 392 /** 393 * Returns an instance of {@code MenuKeyListener}. 394 * 395 * @param c a component 396 * @return an instance of {@code MenuKeyListener} 397 */ 398 protected MenuKeyListener createMenuKeyListener(JComponent c) { 399 return null; 400 } 401 402 /** 403 * Creates a {@code PropertyChangeListener} which will be added to 404 * the menu item. 405 * If this method returns null then it will not be added to the menu item. 406 * 407 * @param c a component 408 * @return an instance of a {@code PropertyChangeListener} or null 409 * @since 1.6 410 */ 411 protected PropertyChangeListener 412 createPropertyChangeListener(JComponent c) { 413 return getHandler(); 414 } 415 416 Handler getHandler() { 417 if (handler == null) { 418 handler = new Handler(); 419 } 420 return handler; 421 } 422 423 InputMap createInputMap(int condition) { 424 if (condition == JComponent.WHEN_IN_FOCUSED_WINDOW) { 425 return new ComponentInputMapUIResource(menuItem); 426 } 427 return null; 428 } 429 430 void updateAcceleratorBinding() { 431 KeyStroke accelerator = menuItem.getAccelerator(); 432 InputMap windowInputMap = SwingUtilities.getUIInputMap( 433 menuItem, JComponent.WHEN_IN_FOCUSED_WINDOW); 434 435 if (windowInputMap != null) { 436 windowInputMap.clear(); 437 } 438 if (accelerator != null) { 439 if (windowInputMap == null) { 440 windowInputMap = createInputMap(JComponent. 441 WHEN_IN_FOCUSED_WINDOW); 442 SwingUtilities.replaceUIInputMap(menuItem, 443 JComponent.WHEN_IN_FOCUSED_WINDOW, windowInputMap); 444 } 445 windowInputMap.put(accelerator, "doClick"); 446 } 447 } 448 449 public Dimension getMinimumSize(JComponent c) { 450 Dimension d = null; 451 View v = (View) c.getClientProperty(BasicHTML.propertyKey); 452 if (v != null) { 453 d = getPreferredSize(c); 454 d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS); 455 } 456 return d; 457 } 458 459 public Dimension getPreferredSize(JComponent c) { 460 return getPreferredMenuItemSize(c, 461 checkIcon, 462 arrowIcon, 463 defaultTextIconGap); 464 } 465 466 public Dimension getMaximumSize(JComponent c) { 467 Dimension d = null; 468 View v = (View) c.getClientProperty(BasicHTML.propertyKey); 469 if (v != null) { 470 d = getPreferredSize(c); 471 d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS); 472 } 473 return d; 474 } 475 476 /** 477 * Returns the preferred size of a menu item. 478 * 479 * @param c a component 480 * @param checkIcon a check icon 481 * @param arrowIcon an arrow icon 482 * @param defaultTextIconGap a gap between a text and an icon 483 * @return the preferred size of a menu item 484 */ 485 protected Dimension getPreferredMenuItemSize(JComponent c, 486 Icon checkIcon, 487 Icon arrowIcon, 488 int defaultTextIconGap) { 489 490 // The method also determines the preferred width of the 491 // parent popup menu (through DefaultMenuLayout class). 492 // The menu width equals to the maximal width 493 // among child menu items. 494 495 // Menu item width will be a sum of the widest check icon, label, 496 // arrow icon and accelerator text among neighbor menu items. 497 // For the latest menu item we will know the maximal widths exactly. 498 // It will be the widest menu item and it will determine 499 // the width of the parent popup menu. 500 501 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 502 // There is a conceptual problem: if user sets preferred size manually 503 // for a menu item, this method won't be called for it 504 // (see JComponent.getPreferredSize()), 505 // maximal widths won't be calculated, other menu items won't be able 506 // to take them into account and will be layouted in such a way, 507 // as there is no the item with manual preferred size. 508 // But after the first paint() method call, all maximal widths 509 // will be correctly calculated and layout of some menu items 510 // can be changed. For example, it can cause a shift of 511 // the icon and text when user points a menu item by mouse. 512 513 JMenuItem mi = (JMenuItem) c; 514 MenuItemLayoutHelper lh = new MenuItemLayoutHelper(mi, checkIcon, 515 arrowIcon, MenuItemLayoutHelper.createMaxRect(), defaultTextIconGap, 516 acceleratorDelimiter, BasicGraphicsUtils.isLeftToRight(mi), 517 mi.getFont(), acceleratorFont, 518 MenuItemLayoutHelper.useCheckAndArrow(menuItem), 519 getPropertyPrefix()); 520 521 Dimension result = new Dimension(); 522 523 // Calculate the result width 524 result.width = lh.getLeadingGap(); 525 MenuItemLayoutHelper.addMaxWidth(lh.getCheckSize(), 526 lh.getAfterCheckIconGap(), result); 527 // Take into account mimimal text offset. 528 if ((!lh.isTopLevelMenu()) 529 && (lh.getMinTextOffset() > 0) 530 && (result.width < lh.getMinTextOffset())) { 531 result.width = lh.getMinTextOffset(); 532 } 533 MenuItemLayoutHelper.addMaxWidth(lh.getLabelSize(), lh.getGap(), result); 534 MenuItemLayoutHelper.addMaxWidth(lh.getAccSize(), lh.getGap(), result); 535 MenuItemLayoutHelper.addMaxWidth(lh.getArrowSize(), lh.getGap(), result); 536 537 // Calculate the result height 538 result.height = MenuItemLayoutHelper.max(lh.getCheckSize().getHeight(), 539 lh.getLabelSize().getHeight(), lh.getAccSize().getHeight(), 540 lh.getArrowSize().getHeight()); 541 542 // Take into account menu item insets 543 Insets insets = lh.getMenuItem().getInsets(); 544 if(insets != null) { 545 result.width += insets.left + insets.right; 546 result.height += insets.top + insets.bottom; 547 } 548 549 // if the width is even, bump it up one. This is critical 550 // for the focus dash line to draw properly 551 if(result.width%2 == 0) { 552 result.width++; 553 } 554 555 // if the height is even, bump it up one. This is critical 556 // for the text to center properly 557 if(result.height%2 == 0 558 && Boolean.TRUE != 559 UIManager.get(getPropertyPrefix() + ".evenHeight")) { 560 result.height++; 561 } 562 563 return result; 564 } 565 566 /** 567 * We draw the background in paintMenuItem() 568 * so override update (which fills the background of opaque 569 * components by default) to just call paint(). 570 * 571 */ 572 public void update(Graphics g, JComponent c) { 573 paint(g, c); 574 } 575 576 public void paint(Graphics g, JComponent c) { 577 paintMenuItem(g, c, checkIcon, arrowIcon, 578 selectionBackground, selectionForeground, 579 defaultTextIconGap); 580 } 581 582 /** 583 * Paints a menu item. 584 * 585 * @param g an instance of {@code Graphics} 586 * @param c a component 587 * @param checkIcon a check icon 588 * @param arrowIcon an arrow icon 589 * @param background a background color 590 * @param foreground a foreground color 591 * @param defaultTextIconGap a gap between a text and an icon 592 */ 593 protected void paintMenuItem(Graphics g, JComponent c, 594 Icon checkIcon, Icon arrowIcon, 595 Color background, Color foreground, 596 int defaultTextIconGap) { 597 // Save original graphics font and color 598 Font holdf = g.getFont(); 599 Color holdc = g.getColor(); 600 601 JMenuItem mi = (JMenuItem) c; 602 g.setFont(mi.getFont()); 603 604 Rectangle viewRect = new Rectangle(0, 0, mi.getWidth(), mi.getHeight()); 605 applyInsets(viewRect, mi.getInsets()); 606 607 MenuItemLayoutHelper lh = new MenuItemLayoutHelper(mi, checkIcon, 608 arrowIcon, viewRect, defaultTextIconGap, acceleratorDelimiter, 609 BasicGraphicsUtils.isLeftToRight(mi), mi.getFont(), 610 acceleratorFont, MenuItemLayoutHelper.useCheckAndArrow(menuItem), 611 getPropertyPrefix()); 612 MenuItemLayoutHelper.LayoutResult lr = lh.layoutMenuItem(); 613 614 paintBackground(g, mi, background); 615 paintCheckIcon(g, lh, lr, holdc, foreground); 616 paintIcon(g, lh, lr, holdc); 617 paintText(g, lh, lr); 618 paintAccText(g, lh, lr); 619 paintArrowIcon(g, lh, lr, foreground); 620 621 // Restore original graphics font and color 622 g.setColor(holdc); 623 g.setFont(holdf); 624 } 625 626 private void paintIcon(Graphics g, MenuItemLayoutHelper lh, 627 MenuItemLayoutHelper.LayoutResult lr, Color holdc) { 628 if (lh.getIcon() != null) { 629 Icon icon; 630 ButtonModel model = lh.getMenuItem().getModel(); 631 if (!model.isEnabled()) { 632 icon = lh.getMenuItem().getDisabledIcon(); 633 } else if (model.isPressed() && model.isArmed()) { 634 icon = lh.getMenuItem().getPressedIcon(); 635 if (icon == null) { 636 // Use default icon 637 icon = lh.getMenuItem().getIcon(); 638 } 639 } else { 640 icon = lh.getMenuItem().getIcon(); 641 } 642 643 if (icon != null) { 644 icon.paintIcon(lh.getMenuItem(), g, lr.getIconRect().x, 645 lr.getIconRect().y); 646 g.setColor(holdc); 647 } 648 } 649 } 650 651 private void paintCheckIcon(Graphics g, MenuItemLayoutHelper lh, 652 MenuItemLayoutHelper.LayoutResult lr, 653 Color holdc, Color foreground) { 654 if (lh.getCheckIcon() != null) { 655 ButtonModel model = lh.getMenuItem().getModel(); 656 if (model.isArmed() || (lh.getMenuItem() instanceof JMenu 657 && model.isSelected())) { 658 g.setColor(foreground); 659 } else { 660 g.setColor(holdc); 661 } 662 if (lh.useCheckAndArrow()) { 663 lh.getCheckIcon().paintIcon(lh.getMenuItem(), g, 664 lr.getCheckRect().x, lr.getCheckRect().y); 665 } 666 g.setColor(holdc); 667 } 668 } 669 670 private void paintAccText(Graphics g, MenuItemLayoutHelper lh, 671 MenuItemLayoutHelper.LayoutResult lr) { 672 if (!lh.getAccText().equals("")) { 673 ButtonModel model = lh.getMenuItem().getModel(); 674 g.setFont(lh.getAccFontMetrics().getFont()); 675 if (!model.isEnabled()) { 676 // *** paint the accText disabled 677 if (disabledForeground != null) { 678 g.setColor(disabledForeground); 679 getTextUIDrawing().drawString(lh.getMenuItem(), g, 680 lh.getAccText(), lr.getAccRect().x, 681 lr.getAccRect().y + lh.getAccFontMetrics().getAscent()); 682 } else { 683 g.setColor(lh.getMenuItem().getBackground().brighter()); 684 getTextUIDrawing().drawString(lh.getMenuItem(), g, 685 lh.getAccText(), lr.getAccRect().x, 686 lr.getAccRect().y + lh.getAccFontMetrics().getAscent()); 687 g.setColor(lh.getMenuItem().getBackground().darker()); 688 getTextUIDrawing().drawString(lh.getMenuItem(), g, 689 lh.getAccText(), lr.getAccRect().x - 1, 690 lr.getAccRect().y + lh.getFontMetrics().getAscent() - 1); 691 } 692 } else { 693 // *** paint the accText normally 694 if (model.isArmed() 695 || (lh.getMenuItem() instanceof JMenu 696 && model.isSelected())) { 697 g.setColor(acceleratorSelectionForeground); 698 } else { 699 g.setColor(acceleratorForeground); 700 } 701 getTextUIDrawing().drawString(lh.getMenuItem(), g, lh.getAccText(), 702 lr.getAccRect().x, lr.getAccRect().y + 703 lh.getAccFontMetrics().getAscent()); 704 } 705 } 706 } 707 708 private void paintText(Graphics g, MenuItemLayoutHelper lh, 709 MenuItemLayoutHelper.LayoutResult lr) { 710 if (!lh.getText().equals("")) { 711 if (lh.getHtmlView() != null) { 712 // Text is HTML 713 lh.getHtmlView().paint(g, lr.getTextRect()); 714 } else { 715 // Text isn't HTML 716 paintText(g, lh.getMenuItem(), lr.getTextRect(), lh.getText()); 717 } 718 } 719 } 720 721 private void paintArrowIcon(Graphics g, MenuItemLayoutHelper lh, 722 MenuItemLayoutHelper.LayoutResult lr, 723 Color foreground) { 724 if (lh.getArrowIcon() != null) { 725 ButtonModel model = lh.getMenuItem().getModel(); 726 if (model.isArmed() || (lh.getMenuItem() instanceof JMenu 727 && model.isSelected())) { 728 g.setColor(foreground); 729 } 730 if (lh.useCheckAndArrow()) { 731 lh.getArrowIcon().paintIcon(lh.getMenuItem(), g, 732 lr.getArrowRect().x, lr.getArrowRect().y); 733 } 734 } 735 } 736 737 private void applyInsets(Rectangle rect, Insets insets) { 738 if(insets != null) { 739 rect.x += insets.left; 740 rect.y += insets.top; 741 rect.width -= (insets.right + rect.x); 742 rect.height -= (insets.bottom + rect.y); 743 } 744 } 745 746 /** 747 * Draws the background of the menu item. 748 * 749 * @param g the paint graphics 750 * @param menuItem menu item to be painted 751 * @param bgColor selection background color 752 * @since 1.4 753 */ 754 protected void paintBackground(Graphics g, JMenuItem menuItem, Color bgColor) { 755 ButtonModel model = menuItem.getModel(); 756 Color oldColor = g.getColor(); 757 int menuWidth = menuItem.getWidth(); 758 int menuHeight = menuItem.getHeight(); 759 760 if(menuItem.isOpaque()) { 761 if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) { 762 g.setColor(bgColor); 763 g.fillRect(0,0, menuWidth, menuHeight); 764 } else { 765 g.setColor(menuItem.getBackground()); 766 g.fillRect(0,0, menuWidth, menuHeight); 767 } 768 g.setColor(oldColor); 769 } 770 else if (model.isArmed() || (menuItem instanceof JMenu && 771 model.isSelected())) { 772 g.setColor(bgColor); 773 g.fillRect(0,0, menuWidth, menuHeight); 774 g.setColor(oldColor); 775 } 776 } 777 778 /** 779 * Renders the text of the current menu item. 780 * 781 * @param g graphics context 782 * @param menuItem menu item to render 783 * @param textRect bounding rectangle for rendering the text 784 * @param text string to render 785 * @since 1.4 786 */ 787 protected void paintText(Graphics g, JMenuItem menuItem, Rectangle textRect, String text) { 788 ButtonModel model = menuItem.getModel(); 789 FontMetrics fm = SwingUtilities2.getFontMetrics(menuItem, g); 790 int mnemIndex = menuItem.getDisplayedMnemonicIndex(); 791 792 if(!model.isEnabled()) { 793 // *** paint the text disabled 794 if ( UIManager.get("MenuItem.disabledForeground") instanceof Color ) { 795 g.setColor( UIManager.getColor("MenuItem.disabledForeground") ); 796 getTextUIDrawing().drawStringUnderlineCharAt(menuItem, g,text, 797 mnemIndex, textRect.x, textRect.y + fm.getAscent()); 798 } else { 799 g.setColor(menuItem.getBackground().brighter()); 800 getTextUIDrawing().drawStringUnderlineCharAt(menuItem, g, text, 801 mnemIndex, textRect.x, textRect.y + fm.getAscent()); 802 g.setColor(menuItem.getBackground().darker()); 803 getTextUIDrawing().drawStringUnderlineCharAt(menuItem, g,text, 804 mnemIndex, textRect.x - 1, textRect.y + 805 fm.getAscent() - 1); 806 } 807 } else { 808 // *** paint the text normally 809 if (model.isArmed()|| (menuItem instanceof JMenu && model.isSelected())) { 810 g.setColor(selectionForeground); // Uses protected field. 811 } 812 getTextUIDrawing().drawStringUnderlineCharAt(menuItem, g,text, 813 mnemIndex, textRect.x, textRect.y + fm.getAscent()); 814 } 815 } 816 817 /** 818 * Returns a menu element path. 819 * 820 * @return a menu element path 821 */ 822 public MenuElement[] getPath() { 823 MenuSelectionManager m = MenuSelectionManager.defaultManager(); 824 MenuElement oldPath[] = m.getSelectedPath(); 825 MenuElement newPath[]; 826 int i = oldPath.length; 827 if (i == 0) 828 return new MenuElement[0]; 829 Component parent = menuItem.getParent(); 830 if (oldPath[i-1].getComponent() == parent) { 831 // The parent popup menu is the last so far 832 newPath = new MenuElement[i+1]; 833 System.arraycopy(oldPath, 0, newPath, 0, i); 834 newPath[i] = menuItem; 835 } else { 836 // A sibling menuitem is the current selection 837 // 838 // This probably needs to handle 'exit submenu into 839 // a menu item. Search backwards along the current 840 // selection until you find the parent popup menu, 841 // then copy up to that and add yourself... 842 int j; 843 for (j = oldPath.length-1; j >= 0; j--) { 844 if (oldPath[j].getComponent() == parent) 845 break; 846 } 847 newPath = new MenuElement[j+2]; 848 System.arraycopy(oldPath, 0, newPath, 0, j+1); 849 newPath[j+1] = menuItem; 850 /* 851 System.out.println("Sibling condition -- "); 852 System.out.println("Old array : "); 853 printMenuElementArray(oldPath, false); 854 System.out.println("New array : "); 855 printMenuElementArray(newPath, false); 856 */ 857 } 858 return newPath; 859 } 860 861 void printMenuElementArray(MenuElement path[], boolean dumpStack) { 862 System.out.println("Path is("); 863 int i, j; 864 for(i=0,j=path.length; i<j ;i++){ 865 for (int k=0; k<=i; k++) 866 System.out.print(" "); 867 MenuElement me = path[i]; 868 if(me instanceof JMenuItem) 869 System.out.println(((JMenuItem)me).getText() + ", "); 870 else if (me == null) 871 System.out.println("NULL , "); 872 else 873 System.out.println("" + me + ", "); 874 } 875 System.out.println(")"); 876 877 if (dumpStack == true) 878 Thread.dumpStack(); 879 } 880 /** Mouse input handler */ 881 protected class MouseInputHandler implements MouseInputListener { 882 // NOTE: This class exists only for backward compatibility. All 883 // its functionality has been moved into Handler. If you need to add 884 // new functionality add it to the Handler, but make sure this 885 // class calls into the Handler. 886 887 /** {@inheritDoc} */ 888 public void mouseClicked(MouseEvent e) { 889 getHandler().mouseClicked(e); 890 } 891 /** {@inheritDoc} */ 892 public void mousePressed(MouseEvent e) { 893 getHandler().mousePressed(e); 894 } 895 /** {@inheritDoc} */ 896 public void mouseReleased(MouseEvent e) { 897 getHandler().mouseReleased(e); 898 } 899 /** {@inheritDoc} */ 900 public void mouseEntered(MouseEvent e) { 901 getHandler().mouseEntered(e); 902 } 903 /** {@inheritDoc} */ 904 public void mouseExited(MouseEvent e) { 905 getHandler().mouseExited(e); 906 } 907 /** {@inheritDoc} */ 908 public void mouseDragged(MouseEvent e) { 909 getHandler().mouseDragged(e); 910 } 911 /** {@inheritDoc} */ 912 public void mouseMoved(MouseEvent e) { 913 getHandler().mouseMoved(e); 914 } 915 } 916 917 918 private static class Actions extends UIAction { 919 private static final String CLICK = "doClick"; 920 921 Actions(String key) { 922 super(key); 923 } 924 925 public void actionPerformed(ActionEvent e) { 926 JMenuItem mi = (JMenuItem)e.getSource(); 927 MenuSelectionManager.defaultManager().clearSelectedPath(); 928 mi.doClick(); 929 } 930 } 931 932 /** 933 * Call this method when a menu item is to be activated. 934 * This method handles some of the details of menu item activation 935 * such as clearing the selected path and messaging the 936 * JMenuItem's doClick() method. 937 * 938 * @param msm A MenuSelectionManager. The visual feedback and 939 * internal bookkeeping tasks are delegated to 940 * this MenuSelectionManager. If <code>null</code> is 941 * passed as this argument, the 942 * <code>MenuSelectionManager.defaultManager</code> is 943 * used. 944 * @see MenuSelectionManager 945 * @see JMenuItem#doClick(int) 946 * @since 1.4 947 */ 948 protected void doClick(MenuSelectionManager msm) { 949 // Auditory cue 950 if (! isInternalFrameSystemMenu()) { 951 BasicLookAndFeel.playSound(menuItem, getPropertyPrefix() + 952 ".commandSound"); 953 } 954 // Visual feedback 955 if (msm == null) { 956 msm = MenuSelectionManager.defaultManager(); 957 } 958 msm.clearSelectedPath(); 959 menuItem.doClick(0); 960 } 961 962 /** 963 * This is to see if the menu item in question is part of the 964 * system menu on an internal frame. 965 * The Strings that are being checked can be found in 966 * MetalInternalFrameTitlePaneUI.java, 967 * WindowsInternalFrameTitlePaneUI.java, and 968 * MotifInternalFrameTitlePaneUI.java. 969 * 970 * @since 1.4 971 */ 972 private boolean isInternalFrameSystemMenu() { 973 String actionCommand = menuItem.getActionCommand(); 974 if ((actionCommand == "Close") || 975 (actionCommand == "Minimize") || 976 (actionCommand == "Restore") || 977 (actionCommand == "Maximize")) { 978 return true; 979 } else { 980 return false; 981 } 982 } 983 984 985 // BasicMenuUI subclasses this. 986 class Handler implements MenuDragMouseListener, 987 MouseInputListener, PropertyChangeListener { 988 // 989 // MouseInputListener 990 // 991 public void mouseClicked(MouseEvent e) {} 992 public void mousePressed(MouseEvent e) { 993 } 994 public void mouseReleased(MouseEvent e) { 995 if (!menuItem.isEnabled()) { 996 return; 997 } 998 MenuSelectionManager manager = 999 MenuSelectionManager.defaultManager(); 1000 Point p = e.getPoint(); 1001 if(p.x >= 0 && p.x < menuItem.getWidth() && 1002 p.y >= 0 && p.y < menuItem.getHeight()) { 1003 doClick(manager); 1004 } else { 1005 manager.processMouseEvent(e); 1006 } 1007 } 1008 public void mouseEntered(MouseEvent e) { 1009 MenuSelectionManager manager = MenuSelectionManager.defaultManager(); 1010 int modifiers = e.getModifiers(); 1011 // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2 1012 if ((modifiers & (InputEvent.BUTTON1_MASK | 1013 InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) { 1014 MenuSelectionManager.defaultManager().processMouseEvent(e); 1015 } else { 1016 manager.setSelectedPath(getPath()); 1017 } 1018 } 1019 public void mouseExited(MouseEvent e) { 1020 MenuSelectionManager manager = MenuSelectionManager.defaultManager(); 1021 1022 int modifiers = e.getModifiers(); 1023 // 4188027: drag enter/exit added in JDK 1.1.7A, JDK1.2 1024 if ((modifiers & (InputEvent.BUTTON1_MASK | 1025 InputEvent.BUTTON2_MASK | InputEvent.BUTTON3_MASK)) !=0 ) { 1026 MenuSelectionManager.defaultManager().processMouseEvent(e); 1027 } else { 1028 1029 MenuElement path[] = manager.getSelectedPath(); 1030 if (path.length > 1 && path[path.length-1] == menuItem) { 1031 MenuElement newPath[] = new MenuElement[path.length-1]; 1032 int i,c; 1033 for(i=0,c=path.length-1;i<c;i++) 1034 newPath[i] = path[i]; 1035 manager.setSelectedPath(newPath); 1036 } 1037 } 1038 } 1039 1040 public void mouseDragged(MouseEvent e) { 1041 MenuSelectionManager.defaultManager().processMouseEvent(e); 1042 } 1043 public void mouseMoved(MouseEvent e) { 1044 } 1045 1046 // 1047 // MenuDragListener 1048 // 1049 public void menuDragMouseEntered(MenuDragMouseEvent e) { 1050 MenuSelectionManager manager = e.getMenuSelectionManager(); 1051 MenuElement path[] = e.getPath(); 1052 manager.setSelectedPath(path); 1053 } 1054 public void menuDragMouseDragged(MenuDragMouseEvent e) { 1055 MenuSelectionManager manager = e.getMenuSelectionManager(); 1056 MenuElement path[] = e.getPath(); 1057 manager.setSelectedPath(path); 1058 } 1059 public void menuDragMouseExited(MenuDragMouseEvent e) {} 1060 public void menuDragMouseReleased(MenuDragMouseEvent e) { 1061 if (!menuItem.isEnabled()) { 1062 return; 1063 } 1064 MenuSelectionManager manager = e.getMenuSelectionManager(); 1065 MenuElement path[] = e.getPath(); 1066 Point p = e.getPoint(); 1067 if (p.x >= 0 && p.x < menuItem.getWidth() && 1068 p.y >= 0 && p.y < menuItem.getHeight()) { 1069 doClick(manager); 1070 } else { 1071 manager.clearSelectedPath(); 1072 } 1073 } 1074 1075 1076 // 1077 // PropertyChangeListener 1078 // 1079 public void propertyChange(PropertyChangeEvent e) { 1080 String name = e.getPropertyName(); 1081 1082 if (name == "labelFor" || name == "displayedMnemonic" || 1083 name == "accelerator") { 1084 updateAcceleratorBinding(); 1085 } else if (name == "text" || "font" == name || 1086 "foreground" == name) { 1087 // remove the old html view client property if one 1088 // existed, and install a new one if the text installed 1089 // into the JLabel is html source. 1090 JMenuItem lbl = ((JMenuItem) e.getSource()); 1091 String text = lbl.getText(); 1092 BasicHTML.updateRenderer(lbl, text); 1093 } else if (name == "iconTextGap") { 1094 defaultTextIconGap = ((Number)e.getNewValue()).intValue(); 1095 } 1096 } 1097 } 1098 }