Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/macosx/classes/com/apple/laf/AquaMenuPainter.java
38831 views
/*1* Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package com.apple.laf;2627import java.awt.*;28import java.awt.event.*;2930import javax.swing.*;31import javax.swing.border.Border;32import javax.swing.plaf.basic.BasicHTML;33import javax.swing.text.View;3435import sun.swing.SwingUtilities2;3637import apple.laf.JRSUIConstants.*;3839import com.apple.laf.AquaIcon.InvertableIcon;40import com.apple.laf.AquaUtils.RecyclableSingleton;41import com.apple.laf.AquaUtils.RecyclableSingletonFromDefaultConstructor;4243/**44* AquaMenuPainter, implements paintMenuItem to avoid code duplication45*46* BasicMenuItemUI didn't factor out the various parts of the Menu, and47* we subclass it and its subclasses BasicMenuUI48* Our classes need an implementation of paintMenuItem49* that allows them to paint their own backgrounds50*/5152public class AquaMenuPainter {53// Glyph statics:54// ASCII character codes55static final byte56kShiftGlyph = 0x05,57kOptionGlyph = 0x07,58kControlGlyph = 0x06,59kPencilGlyph = 0x0F,60kCommandMark = 0x11;6162// Unicode character codes63static final char64kUBlackDiamond = 0x25C6,65kUCheckMark = 0x2713,66kUControlGlyph = 0x2303,67kUOptionGlyph = 0x2325,68kUEnterGlyph = 0x2324,69kUCommandGlyph = 0x2318,70kULeftDeleteGlyph = 0x232B,71kURightDeleteGlyph = 0x2326,72kUShiftGlyph = 0x21E7,73kUCapsLockGlyph = 0x21EA;7475static final int ALT_GRAPH_MASK = 1 << 5; // New to Java276static final int sUnsupportedModifiersMask = ~(InputEvent.CTRL_MASK | InputEvent.ALT_MASK | InputEvent.SHIFT_MASK | InputEvent.META_MASK | ALT_GRAPH_MASK);7778interface Client {79public void paintBackground(Graphics g, JComponent c, int menuWidth, int menuHeight);80}8182// Return a string with the proper modifier glyphs83static String getKeyModifiersText(final int modifiers, final boolean isLeftToRight) {84return getKeyModifiersUnicode(modifiers, isLeftToRight);85}8687// Return a string with the proper modifier glyphs88private static String getKeyModifiersUnicode(final int modifiers, final boolean isLeftToRight) {89final StringBuilder buf = new StringBuilder(2);90// Order (from StandardMenuDef.c): control, option(alt), shift, cmd91// reverse for right-to-left92//$ check for substitute key glyphs for localization93if (isLeftToRight) {94if ((modifiers & InputEvent.CTRL_MASK) != 0) {95buf.append(kUControlGlyph);96}97if ((modifiers & (InputEvent.ALT_MASK | ALT_GRAPH_MASK)) != 0) {98buf.append(kUOptionGlyph);99}100if ((modifiers & InputEvent.SHIFT_MASK) != 0) {101buf.append(kUShiftGlyph);102}103if ((modifiers & InputEvent.META_MASK) != 0) {104buf.append(kUCommandGlyph);105}106} else {107if ((modifiers & InputEvent.META_MASK) != 0) {108buf.append(kUCommandGlyph);109}110if ((modifiers & InputEvent.SHIFT_MASK) != 0) {111buf.append(kUShiftGlyph);112}113if ((modifiers & (InputEvent.ALT_MASK | ALT_GRAPH_MASK)) != 0) {114buf.append(kUOptionGlyph);115}116if ((modifiers & InputEvent.CTRL_MASK) != 0) {117buf.append(kUControlGlyph);118}119}120return buf.toString();121}122123static final RecyclableSingleton<AquaMenuPainter> sPainter = new RecyclableSingletonFromDefaultConstructor<AquaMenuPainter>(AquaMenuPainter.class);124static AquaMenuPainter instance() {125return sPainter.get();126}127128static final int defaultMenuItemGap = 2;129static final int kAcceleratorArrowSpace = 16; // Accel space doesn't overlap arrow space, even though items can't have both130131static class RecyclableBorder extends RecyclableSingleton<Border> {132final String borderName;133RecyclableBorder(final String borderName) { this.borderName = borderName; }134protected Border getInstance() { return UIManager.getBorder(borderName); }135}136137protected final RecyclableBorder menuBarPainter = new RecyclableBorder("MenuBar.backgroundPainter");138protected final RecyclableBorder selectedMenuBarItemPainter = new RecyclableBorder("MenuBar.selectedBackgroundPainter");139protected final RecyclableBorder selectedMenuItemPainter = new RecyclableBorder("MenuItem.selectedBackgroundPainter");140141public void paintMenuBarBackground(final Graphics g, final int width, final int height, final JComponent c) {142g.setColor(c == null ? Color.white : c.getBackground());143g.fillRect(0, 0, width, height);144menuBarPainter.get().paintBorder(null, g, 0, 0, width, height);145}146147public void paintSelectedMenuTitleBackground(final Graphics g, final int width, final int height) {148selectedMenuBarItemPainter.get().paintBorder(null, g, -1, 0, width + 2, height);149}150151public void paintSelectedMenuItemBackground(final Graphics g, final int width, final int height) {152selectedMenuItemPainter.get().paintBorder(null, g, 0, 0, width, height);153}154155protected void paintMenuItem(final Client client, final Graphics g, final JComponent c, final Icon checkIcon, final Icon arrowIcon, final Color background, final Color foreground, final Color disabledForeground, final Color selectionForeground, final int defaultTextIconGap, final Font acceleratorFont) {156final JMenuItem b = (JMenuItem)c;157final ButtonModel model = b.getModel();158159// Dimension size = b.getSize();160final int menuWidth = b.getWidth();161final int menuHeight = b.getHeight();162final Insets i = c.getInsets();163164Rectangle viewRect = new Rectangle(0, 0, menuWidth, menuHeight);165166viewRect.x += i.left;167viewRect.y += i.top;168viewRect.width -= (i.right + viewRect.x);169viewRect.height -= (i.bottom + viewRect.y);170171final Font holdf = g.getFont();172final Color holdc = g.getColor();173final Font f = c.getFont();174g.setFont(f);175final FontMetrics fm = g.getFontMetrics(f);176177final FontMetrics fmAccel = g.getFontMetrics(acceleratorFont);178179// Paint background (doesn't touch the Graphics object's color)180if (c.isOpaque()) {181client.paintBackground(g, c, menuWidth, menuHeight);182}183184// get Accelerator text185final KeyStroke accelerator = b.getAccelerator();186String modifiersString = "", keyString = "";187final boolean leftToRight = AquaUtils.isLeftToRight(c);188if (accelerator != null) {189final int modifiers = accelerator.getModifiers();190if (modifiers > 0) {191modifiersString = getKeyModifiersText(modifiers, leftToRight);192}193final int keyCode = accelerator.getKeyCode();194if (keyCode != 0) {195keyString = KeyEvent.getKeyText(keyCode);196} else {197keyString += accelerator.getKeyChar();198}199}200201Rectangle iconRect = new Rectangle();202Rectangle textRect = new Rectangle();203Rectangle acceleratorRect = new Rectangle();204Rectangle checkIconRect = new Rectangle();205Rectangle arrowIconRect = new Rectangle();206207// layout the text and icon208final String text = layoutMenuItem(b, fm, b.getText(), fmAccel, keyString, modifiersString, b.getIcon(), checkIcon, arrowIcon, b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, acceleratorRect, checkIconRect, arrowIconRect, b.getText() == null ? 0 : defaultTextIconGap, defaultTextIconGap);209210// if this is in a AquaScreenMenuBar that's attached to a DialogPeer211// the native menu will be disabled, though the awt Menu won't know about it212// so the JPopupMenu will not have visibility set and the items should draw disabled213// If it's not on a JPopupMenu then it should just use the model's enable state214final Container parent = b.getParent();215final boolean parentIsMenuBar = parent instanceof JMenuBar;216217Container ancestor = parent;218while (ancestor != null && !(ancestor instanceof JPopupMenu)) ancestor = ancestor.getParent();219220boolean isEnabled = model.isEnabled() && (ancestor == null || ancestor.isVisible());221222// Set the accel/normal text color223boolean isSelected = false;224if (!isEnabled) {225// *** paint the text disabled226g.setColor(disabledForeground);227} else {228// *** paint the text normally229if (model.isArmed() || (c instanceof JMenu && model.isSelected())) {230g.setColor(selectionForeground);231isSelected = true;232} else {233g.setColor(parentIsMenuBar ? parent.getForeground() : b.getForeground()); // Which is either MenuItem.foreground or the user's choice234}235}236237// We want to paint the icon after the text color is set since some icon painting depends on the correct238// graphics color being set239// See <rdar://problem/3792383> Menu icons missing in Java2D's Lines.Joins demo240// Paint the Icon241if (b.getIcon() != null) {242paintIcon(g, b, iconRect, isEnabled);243}244245// Paint the Check using the current text color246if (checkIcon != null) {247paintCheck(g, b, checkIcon, checkIconRect);248}249250// Draw the accelerator first in case the HTML renderer changes the color251if (keyString != null && !keyString.equals("")) {252final int yAccel = acceleratorRect.y + fm.getAscent();253if (modifiersString.equals("")) {254// just draw the keyString255SwingUtilities2.drawString(c, g, keyString, acceleratorRect.x, yAccel);256} else {257final int modifiers = accelerator.getModifiers();258int underlinedChar = 0;259if ((modifiers & ALT_GRAPH_MASK) > 0) underlinedChar = kUOptionGlyph; // This is a Java2 thing, we won't be getting kOptionGlyph260// The keyStrings should all line up, so always adjust the width by the same amount261// (if they're multi-char, they won't line up but at least they won't be cut off)262final int emWidth = Math.max(fm.charWidth('M'), SwingUtilities.computeStringWidth(fm, keyString));263264if (leftToRight) {265g.setFont(acceleratorFont);266drawString(g, c, modifiersString, underlinedChar, acceleratorRect.x, yAccel, isEnabled, isSelected);267g.setFont(f);268SwingUtilities2.drawString(c, g, keyString, acceleratorRect.x + acceleratorRect.width - emWidth, yAccel);269} else {270final int xAccel = acceleratorRect.x + emWidth;271g.setFont(acceleratorFont);272drawString(g, c, modifiersString, underlinedChar, xAccel, yAccel, isEnabled, isSelected);273g.setFont(f);274SwingUtilities2.drawString(c, g, keyString, xAccel - fm.stringWidth(keyString), yAccel);275}276}277}278279// Draw the Text280if (text != null && !text.equals("")) {281final View v = (View)c.getClientProperty(BasicHTML.propertyKey);282if (v != null) {283v.paint(g, textRect);284} else {285final int mnemonic = (AquaMnemonicHandler.isMnemonicHidden() ? -1 : model.getMnemonic());286drawString(g, c, text, mnemonic, textRect.x, textRect.y + fm.getAscent(), isEnabled, isSelected);287}288}289290// Paint the Arrow291if (arrowIcon != null) {292paintArrow(g, b, model, arrowIcon, arrowIconRect);293}294295g.setColor(holdc);296g.setFont(holdf);297}298299// All this had to be copied from BasicMenuItemUI, just to get the right keyModifiersText fn300// and a few Mac tweaks301protected Dimension getPreferredMenuItemSize(final JComponent c, final Icon checkIcon, final Icon arrowIcon, final int defaultTextIconGap, final Font acceleratorFont) {302final JMenuItem b = (JMenuItem)c;303final Icon icon = b.getIcon();304final String text = b.getText();305final KeyStroke accelerator = b.getAccelerator();306String keyString = "", modifiersString = "";307308if (accelerator != null) {309final int modifiers = accelerator.getModifiers();310if (modifiers > 0) {311modifiersString = getKeyModifiersText(modifiers, true); // doesn't matter, this is just for metrics312}313final int keyCode = accelerator.getKeyCode();314if (keyCode != 0) {315keyString = KeyEvent.getKeyText(keyCode);316} else {317keyString += accelerator.getKeyChar();318}319}320321final Font font = b.getFont();322final FontMetrics fm = b.getFontMetrics(font);323final FontMetrics fmAccel = b.getFontMetrics(acceleratorFont);324325Rectangle iconRect = new Rectangle();326Rectangle textRect = new Rectangle();327Rectangle acceleratorRect = new Rectangle();328Rectangle checkIconRect = new Rectangle();329Rectangle arrowIconRect = new Rectangle();330Rectangle viewRect = new Rectangle(Short.MAX_VALUE, Short.MAX_VALUE);331332layoutMenuItem(b, fm, text, fmAccel, keyString, modifiersString, icon, checkIcon, arrowIcon, b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, acceleratorRect, checkIconRect, arrowIconRect, text == null ? 0 : defaultTextIconGap, defaultTextIconGap);333// find the union of the icon and text rects334Rectangle r = new Rectangle();335r.setBounds(textRect);336r = SwingUtilities.computeUnion(iconRect.x, iconRect.y, iconRect.width, iconRect.height, r);337// r = iconRect.union(textRect);338339// Add in the accelerator340boolean acceleratorTextIsEmpty = (keyString == null) || keyString.equals("");341342if (!acceleratorTextIsEmpty) {343r.width += acceleratorRect.width;344}345346if (!isTopLevelMenu(b)) {347// Add in the checkIcon348r.width += checkIconRect.width;349r.width += defaultTextIconGap;350351// Add in the arrowIcon space352r.width += defaultTextIconGap;353r.width += arrowIconRect.width;354}355356final Insets insets = b.getInsets();357if (insets != null) {358r.width += insets.left + insets.right;359r.height += insets.top + insets.bottom;360}361362// Tweak for Mac363r.width += 4 + defaultTextIconGap;364r.height = Math.max(r.height, 18);365366return r.getSize();367}368369protected void paintCheck(final Graphics g, final JMenuItem item, Icon checkIcon, Rectangle checkIconRect) {370if (isTopLevelMenu(item) || !item.isSelected()) return;371372if (item.isArmed() && checkIcon instanceof InvertableIcon) {373((InvertableIcon)checkIcon).getInvertedIcon().paintIcon(item, g, checkIconRect.x, checkIconRect.y);374} else {375checkIcon.paintIcon(item, g, checkIconRect.x, checkIconRect.y);376}377}378379protected void paintIcon(final Graphics g, final JMenuItem c, final Rectangle localIconRect, boolean isEnabled) {380final ButtonModel model = c.getModel();381Icon icon;382if (!isEnabled) {383icon = c.getDisabledIcon();384} else if (model.isPressed() && model.isArmed()) {385icon = c.getPressedIcon();386if (icon == null) {387// Use default icon388icon = c.getIcon();389}390} else {391icon = c.getIcon();392}393394if (icon != null) icon.paintIcon(c, g, localIconRect.x, localIconRect.y);395}396397protected void paintArrow(Graphics g, JMenuItem c, ButtonModel model, Icon arrowIcon, Rectangle arrowIconRect) {398if (isTopLevelMenu(c)) return;399400if (c instanceof JMenu && (model.isArmed() || model.isSelected()) && arrowIcon instanceof InvertableIcon) {401((InvertableIcon)arrowIcon).getInvertedIcon().paintIcon(c, g, arrowIconRect.x, arrowIconRect.y);402} else {403arrowIcon.paintIcon(c, g, arrowIconRect.x, arrowIconRect.y);404}405}406407/** Draw a string with the graphics g at location (x,y) just like g.drawString() would.408* The first occurrence of underlineChar in text will be underlined. The matching is409* not case sensitive.410*/411public void drawString(final Graphics g, final JComponent c, final String text, final int underlinedChar, final int x, final int y, final boolean isEnabled, final boolean isSelected) {412char lc, uc;413int index = -1, lci, uci;414415if (underlinedChar != '\0') {416uc = Character.toUpperCase((char)underlinedChar);417lc = Character.toLowerCase((char)underlinedChar);418419uci = text.indexOf(uc);420lci = text.indexOf(lc);421422if (uci == -1) index = lci;423else if (lci == -1) index = uci;424else index = (lci < uci) ? lci : uci;425}426427SwingUtilities2.drawStringUnderlineCharAt(c, g, text, index, x, y);428}429430/*431* Returns false if the component is a JMenu and it is a top432* level menu (on the menubar).433*/434private static boolean isTopLevelMenu(final JMenuItem menuItem) {435return (menuItem instanceof JMenu) && (((JMenu)menuItem).isTopLevelMenu());436}437438private String layoutMenuItem(final JMenuItem menuItem, final FontMetrics fm, final String text, final FontMetrics fmAccel, String keyString, final String modifiersString, final Icon icon, final Icon checkIcon, final Icon arrowIcon, final int verticalAlignment, final int horizontalAlignment, final int verticalTextPosition, final int horizontalTextPosition, final Rectangle viewR, final Rectangle iconR, final Rectangle textR, final Rectangle acceleratorR, final Rectangle checkIconR, final Rectangle arrowIconR, final int textIconGap, final int menuItemGap) {439// Force it to do "LEFT", then flip the rects if we're right-to-left440SwingUtilities.layoutCompoundLabel(menuItem, fm, text, icon, verticalAlignment, SwingConstants.LEFT, verticalTextPosition, horizontalTextPosition, viewR, iconR, textR, textIconGap);441442final boolean acceleratorTextIsEmpty = (keyString == null) || keyString.equals("");443444if (acceleratorTextIsEmpty) {445acceleratorR.width = acceleratorR.height = 0;446keyString = "";447} else {448// Accel space doesn't overlap arrow space, even though items can't have both449acceleratorR.width = SwingUtilities.computeStringWidth(fmAccel, modifiersString);450// The keyStrings should all line up, so always adjust the width by the same amount451// (if they're multi-char, they won't line up but at least they won't be cut off)452acceleratorR.width += Math.max(fm.charWidth('M'), SwingUtilities.computeStringWidth(fm, keyString));453acceleratorR.height = fmAccel.getHeight();454}455456/* Initialize the checkIcon bounds rectangle checkIconR.457*/458459final boolean isTopLevelMenu = isTopLevelMenu(menuItem);460if (!isTopLevelMenu) {461if (checkIcon != null) {462checkIconR.width = checkIcon.getIconWidth();463checkIconR.height = checkIcon.getIconHeight();464} else {465checkIconR.width = checkIconR.height = 16;466}467468/* Initialize the arrowIcon bounds rectangle arrowIconR.469*/470471if (arrowIcon != null) {472arrowIconR.width = arrowIcon.getIconWidth();473arrowIconR.height = arrowIcon.getIconHeight();474} else {475arrowIconR.width = arrowIconR.height = 16;476}477478textR.x += 12;479iconR.x += 12;480}481482final Rectangle labelR = iconR.union(textR);483484// Position the Accelerator text rect485// Menu shortcut text *ought* to have the letters left-justified - look at a menu with an "M" in it486acceleratorR.x += (viewR.width - arrowIconR.width - acceleratorR.width);487acceleratorR.y = viewR.y + (viewR.height / 2) - (acceleratorR.height / 2);488489if (!isTopLevelMenu) {490// if ( GetSysDirection() < 0 ) hierRect.right = hierRect.left + w + 4;491// else hierRect.left = hierRect.right - w - 4;492arrowIconR.x = (viewR.width - arrowIconR.width) + 1;493arrowIconR.y = viewR.y + (labelR.height / 2) - (arrowIconR.height / 2) + 1;494495checkIconR.y = viewR.y + (labelR.height / 2) - (checkIconR.height / 2);496checkIconR.x = 5;497498textR.width += 8;499}500501/*System.out.println("Layout: " +horizontalAlignment+ " v=" +viewR+" c="+checkIconR+" i="+502iconR+" t="+textR+" acc="+acceleratorR+" a="+arrowIconR);*/503504if (!AquaUtils.isLeftToRight(menuItem)) {505// Flip the rectangles so that instead of [check][icon][text][accel/arrow] it's [accel/arrow][text][icon][check]506final int w = viewR.width;507checkIconR.x = w - (checkIconR.x + checkIconR.width);508iconR.x = w - (iconR.x + iconR.width);509textR.x = w - (textR.x + textR.width);510acceleratorR.x = w - (acceleratorR.x + acceleratorR.width);511arrowIconR.x = w - (arrowIconR.x + arrowIconR.width);512}513textR.x += menuItemGap;514iconR.x += menuItemGap;515516return text;517}518519public static Border getMenuBarPainter() {520final AquaBorder border = new AquaBorder.Default();521border.painter.state.set(Widget.MENU_BAR);522return border;523}524525public static Border getSelectedMenuBarItemPainter() {526final AquaBorder border = new AquaBorder.Default();527border.painter.state.set(Widget.MENU_TITLE);528border.painter.state.set(State.PRESSED);529return border;530}531532public static Border getSelectedMenuItemPainter() {533final AquaBorder border = new AquaBorder.Default();534border.painter.state.set(Widget.MENU_ITEM);535border.painter.state.set(State.PRESSED);536return border;537}538}539540541