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