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