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