Path: blob/master/src/java.desktop/macosx/classes/com/apple/laf/AquaButtonUI.java
41154 views
/*1* Copyright (c) 2011, 2020, 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.*;29import java.beans.PropertyChangeEvent;3031import javax.swing.*;32import javax.swing.border.Border;33import javax.swing.event.*;34import javax.swing.plaf.*;35import javax.swing.plaf.basic.*;36import javax.swing.text.View;3738import sun.swing.SwingUtilities2;3940import apple.laf.JRSUIConstants.Size;4142import com.apple.laf.AquaButtonExtendedTypes.TypeSpecifier;43import com.apple.laf.AquaUtilControlSize.Sizeable;44import com.apple.laf.AquaUtils.*;4546public class AquaButtonUI extends BasicButtonUI implements Sizeable {47private static final String BUTTON_TYPE = "JButton.buttonType";48private static final String SEGMENTED_BUTTON_POSITION = "JButton.segmentPosition";4950private static final RecyclableSingleton<AquaButtonUI> buttonUI = new RecyclableSingletonFromDefaultConstructor<AquaButtonUI>(AquaButtonUI.class);51public static ComponentUI createUI(final JComponent c) {52return buttonUI.get();53}5455// Has the shared instance defaults been initialized?56private boolean defaults_initialized = false;57private Color defaultDisabledTextColor = null;5859protected void installDefaults(final AbstractButton b) {60// load shared instance defaults61final String pp = getPropertyPrefix();6263if (!defaults_initialized) {64defaultDisabledTextColor = UIManager.getColor(pp + "disabledText");65defaults_initialized = true;66}6768setButtonMarginIfNeeded(b, UIManager.getInsets(pp + "margin"));6970LookAndFeel.installColorsAndFont(b, pp + "background", pp + "foreground", pp + "font");71LookAndFeel.installProperty(b, "opaque", UIManager.getBoolean(pp + "opaque"));7273final Object borderProp = b.getClientProperty(BUTTON_TYPE);74boolean hasBorder = false;7576if (borderProp != null) {77hasBorder = setButtonType(b, borderProp);78}79if (!hasBorder) setThemeBorder(b);8081final Object segmentProp = b.getClientProperty(SEGMENTED_BUTTON_POSITION);82if (segmentProp != null) {83final Border border = b.getBorder();84if (!(border instanceof AquaBorder)) return;8586b.setBorder(AquaButtonExtendedTypes.getBorderForPosition(b, b.getClientProperty(BUTTON_TYPE), segmentProp));87}88}8990public void applySizeFor(final JComponent c, final Size size) {91// this space intentionally left blank92// (subclasses need to do work here)93}9495protected void setThemeBorder(final AbstractButton b) {96// Set the correct border97final ButtonUI genericUI = b.getUI();98if (!(genericUI instanceof AquaButtonUI)) return;99final AquaButtonUI ui = (AquaButtonUI)genericUI;100101Border border = b.getBorder();102if (!ui.isBorderFromProperty(b) && (border == null || border instanceof UIResource || border instanceof AquaButtonBorder)) {103// See BasicGraphicsUtils.getPreferredButtonSize - it returns null for preferred size,104// causing it to use the subcomponent's size, which doesn't allow space for Aqua pushbuttons105boolean iconFont = true;106if (isOnToolbar(b)) {107if (b instanceof JToggleButton) {108border = AquaButtonBorder.getToolBarButtonBorder();109} else {110border = AquaButtonBorder.getBevelButtonBorder();111}112} else if (b.getIcon() != null || b.getComponentCount() > 0) {113// radar 3308129 && (b.getText() == null || b.getText().equals("")))114// we used to only do this for buttons that had images and no text115// now we do it for all buttons that have any images - they cannot116// be a default button.117border = AquaButtonBorder.getToggleButtonBorder();118} else {119border = UIManager.getBorder(getPropertyPrefix() + "border");120iconFont = false;121}122123b.setBorder(border);124125final Font currentFont = b.getFont();126if (iconFont && (currentFont == null || currentFont instanceof UIResource)) {127b.setFont(UIManager.getFont("IconButton.font"));128}129}130}131132protected static boolean isOnToolbar(final AbstractButton b) {133Component parent = b.getParent();134while (parent != null) {135if (parent instanceof JToolBar) return true;136parent = parent.getParent();137}138return false;139}140141// A state that affects border has changed. Make sure we have the right one142protected static void updateBorder(final AbstractButton b) {143// See if the button has overridden the automatic button type144final Object prop = b.getClientProperty(BUTTON_TYPE);145if (prop != null) return;146147final ButtonUI ui = b.getUI();148if (!(ui instanceof AquaButtonUI)) return;149if (b.getBorder() != null) ((AquaButtonUI)ui).setThemeBorder(b);150}151152protected void setButtonMarginIfNeeded(final AbstractButton b, final Insets insets) {153final Insets margin = b.getMargin();154if (margin == null || (margin instanceof UIResource)) {155b.setMargin(insets);156}157}158159public boolean isBorderFromProperty(final AbstractButton button) {160return button.getClientProperty(BUTTON_TYPE) != null;161}162163protected boolean setButtonType(final AbstractButton b, final Object prop) {164if (!(prop instanceof String)) {165b.putClientProperty(BUTTON_TYPE, null); // so we know to use the automatic button type166return false;167}168169final String buttonType = (String)prop;170boolean iconFont = true;171172final TypeSpecifier specifier = AquaButtonExtendedTypes.getSpecifierByName(buttonType);173if (specifier != null) {174b.setBorder(specifier.getBorder());175iconFont = specifier.setIconFont;176}177178final Font currentFont = b.getFont();179if (currentFont == null || currentFont instanceof UIResource) {180b.setFont(UIManager.getFont(iconFont ? "IconButton.font" : "Button.font"));181}182183return true;184}185186protected void installListeners(final AbstractButton b) {187super.installListeners(b);188AquaButtonListener listener = getAquaButtonListener(b);189if (listener != null) {190// put the listener in the button's client properties so that191// we can get at it later192b.putClientProperty(this, listener);193194b.addAncestorListener(listener);195}196installHierListener(b);197AquaUtilControlSize.addSizePropertyListener(b);198}199200protected void installKeyboardActions(final AbstractButton b) {201final BasicButtonListener listener = (BasicButtonListener)b.getClientProperty(this);202if (listener != null) listener.installKeyboardActions(b);203}204205// Uninstall PLAF206public void uninstallUI(final JComponent c) {207uninstallKeyboardActions((AbstractButton)c);208uninstallListeners((AbstractButton)c);209uninstallDefaults((AbstractButton)c);210//BasicHTML.updateRenderer(c, "");211}212213protected void uninstallKeyboardActions(final AbstractButton b) {214final BasicButtonListener listener = (BasicButtonListener)b.getClientProperty(this);215if (listener != null) listener.uninstallKeyboardActions(b);216}217218protected void uninstallListeners(final AbstractButton b) {219super.uninstallListeners(b);220final AquaButtonListener listener = (AquaButtonListener)b.getClientProperty(this);221b.putClientProperty(this, null);222if (listener != null) {223b.removeAncestorListener(listener);224}225uninstallHierListener(b);226AquaUtilControlSize.removeSizePropertyListener(b);227}228229protected void uninstallDefaults(final AbstractButton b) {230LookAndFeel.uninstallBorder(b);231defaults_initialized = false;232}233234// Create Listeners235protected AquaButtonListener createButtonListener(final AbstractButton b) {236return new AquaButtonListener(b);237}238239/**240* Returns the AquaButtonListener for the passed in Button, or null if one241* could not be found.242*/243private AquaButtonListener getAquaButtonListener(AbstractButton b) {244MouseMotionListener[] listeners = b.getMouseMotionListeners();245246if (listeners != null) {247for (MouseMotionListener listener : listeners) {248if (listener instanceof AquaButtonListener) {249return (AquaButtonListener) listener;250}251}252}253return null;254}255256// Paint Methods257public void paint(final Graphics g, final JComponent c) {258final AbstractButton b = (AbstractButton)c;259final ButtonModel model = b.getModel();260261final Insets i = c.getInsets();262263Rectangle viewRect = new Rectangle(b.getWidth(), b.getHeight());264Rectangle iconRect = new Rectangle();265Rectangle textRect = new Rectangle();266267// we are overdrawing here with translucent colors so we get268// a darkening effect. How can we avoid it. Try clear rect?269if (b.isOpaque()) {270g.setColor(c.getBackground());271g.fillRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);272}273274AquaButtonBorder aquaBorder = null;275if (((AbstractButton)c).isBorderPainted()) {276final Border border = c.getBorder();277278if (border instanceof AquaButtonBorder) {279// only do this if borders are on!280// this also takes care of focus painting.281aquaBorder = (AquaButtonBorder)border;282aquaBorder.paintButton(c, g, viewRect.x, viewRect.y, viewRect.width, viewRect.height);283}284} else {285if (b.isOpaque()) {286viewRect.x = i.left - 2;287viewRect.y = i.top - 2;288viewRect.width = b.getWidth() - (i.right + viewRect.x) + 4;289viewRect.height = b.getHeight() - (i.bottom + viewRect.y) + 4;290if (b.isContentAreaFilled() || model.isSelected()) {291if (model.isSelected()) // Toggle buttons292g.setColor(c.getBackground().darker());293else g.setColor(c.getBackground());294g.fillRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);295}296}297298// needs focus to be painted299// for now we don't know exactly what to do...we'll see!300if (b.isFocusPainted() && b.hasFocus()) {301// paint UI specific focus302paintFocus(g, b, viewRect, textRect, iconRect);303}304}305306// performs icon and text rect calculations307final String text = layoutAndGetText(g, b, aquaBorder, i, viewRect, iconRect, textRect);308309// Paint the Icon310if (b.getIcon() != null) {311paintIcon(g, b, iconRect);312}313314if (textRect.width == 0) {315textRect.width = 50;316}317318if (text != null && !text.isEmpty()) {319final View v = (View)c.getClientProperty(BasicHTML.propertyKey);320if (v != null) {321v.paint(g, textRect);322} else {323paintText(g, b, textRect, text);324}325}326}327328protected String layoutAndGetText(final Graphics g, final AbstractButton b, final AquaButtonBorder aquaBorder, final Insets i, Rectangle viewRect, Rectangle iconRect, Rectangle textRect) {329// re-initialize the view rect to the selected insets330viewRect.x = i.left;331viewRect.y = i.top;332viewRect.width = b.getWidth() - (i.right + viewRect.x);333viewRect.height = b.getHeight() - (i.bottom + viewRect.y);334335// reset the text and icon rects336textRect.x = textRect.y = textRect.width = textRect.height = 0;337iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;338339// setup the font340g.setFont(b.getFont());341final FontMetrics fm = g.getFontMetrics();342343// layout the text and icon344final String originalText = b.getText();345final String text = SwingUtilities.layoutCompoundLabel(b, fm, originalText, b.getIcon(), b.getVerticalAlignment(), b.getHorizontalAlignment(), b.getVerticalTextPosition(), b.getHorizontalTextPosition(), viewRect, iconRect, textRect, originalText == null ? 0 : b.getIconTextGap());346if (text == originalText || aquaBorder == null) return text; // everything fits347348// if the text didn't fit - check if the aqua border has alternate Insets that are more adhering349final Insets alternateContentInsets = aquaBorder.getContentInsets(b, b.getWidth(), b.getHeight());350if (alternateContentInsets != null) {351// recursively call and don't pass AquaBorder352return layoutAndGetText(g, b, null, alternateContentInsets, viewRect, iconRect, textRect);353}354355// there is no Aqua border, go with what we've got356return text;357}358359protected void paintIcon(final Graphics g, final AbstractButton b, final Rectangle localIconRect) {360final ButtonModel model = b.getModel();361Icon icon = b.getIcon();362Icon tmpIcon = null;363364if (icon == null) return;365366if (!model.isEnabled()) {367if (model.isSelected()) {368tmpIcon = b.getDisabledSelectedIcon();369} else {370tmpIcon = b.getDisabledIcon();371}372} else if (model.isPressed() && model.isArmed()) {373tmpIcon = b.getPressedIcon();374if (tmpIcon == null) {375if (icon instanceof ImageIcon) {376tmpIcon = new ImageIcon(AquaUtils.generateSelectedDarkImage(((ImageIcon)icon).getImage()));377}378}379} else if (b.isRolloverEnabled() && model.isRollover()) {380if (model.isSelected()) {381tmpIcon = b.getRolloverSelectedIcon();382} else {383tmpIcon = b.getRolloverIcon();384}385} else if (model.isSelected()) {386tmpIcon = b.getSelectedIcon();387}388389if (model.isEnabled() && b.isFocusOwner() && b.getBorder() instanceof AquaButtonBorder.Toolbar) {390if (tmpIcon == null) tmpIcon = icon;391if (tmpIcon instanceof ImageIcon) {392tmpIcon = AquaFocus.createFocusedIcon(tmpIcon, b, 3);393tmpIcon.paintIcon(b, g, localIconRect.x - 3, localIconRect.y - 3);394return;395}396}397398if (tmpIcon != null) {399icon = tmpIcon;400}401402icon.paintIcon(b, g, localIconRect.x, localIconRect.y);403}404405/**406* As of Java 2 platform v 1.4 this method should not be used or overriden.407* Use the paintText method which takes the AbstractButton argument.408*/409protected void paintText(final Graphics g, final JComponent c, final Rectangle localTextRect, final String text) {410final Graphics2D g2d = g instanceof Graphics2D ? (Graphics2D)g : null;411412final AbstractButton b = (AbstractButton)c;413final ButtonModel model = b.getModel();414final FontMetrics fm = g.getFontMetrics();415final int mnemonicIndex = AquaMnemonicHandler.isMnemonicHidden() ? -1 : b.getDisplayedMnemonicIndex();416417/* Draw the Text */418if (model.isEnabled()) {419/*** paint the text normally */420g.setColor(b.getForeground());421} else {422/*** paint the text disabled ***/423g.setColor(defaultDisabledTextColor);424}425SwingUtilities2.drawStringUnderlineCharAt(c, g, text, mnemonicIndex, localTextRect.x, localTextRect.y + fm.getAscent());426}427428protected void paintText(final Graphics g, final AbstractButton b, final Rectangle localTextRect, final String text) {429paintText(g, (JComponent)b, localTextRect, text);430}431432protected void paintButtonPressed(final Graphics g, final AbstractButton b) {433paint(g, b);434}435436// Layout Methods437public Dimension getMinimumSize(final JComponent c) {438final Dimension d = getPreferredSize(c);439final View v = (View)c.getClientProperty(BasicHTML.propertyKey);440if (v != null) {441d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS);442}443return d;444}445446public Dimension getPreferredSize(final JComponent c) {447final AbstractButton b = (AbstractButton)c;448449// fix for Radar #3134273450final Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b, b.getIconTextGap());451if (d == null) return null;452453final Border border = b.getBorder();454if (border instanceof AquaButtonBorder) {455((AquaButtonBorder)border).alterPreferredSize(d);456}457458return d;459}460461public Dimension getMaximumSize(final JComponent c) {462final Dimension d = getPreferredSize(c);463464final View v = (View)c.getClientProperty(BasicHTML.propertyKey);465if (v != null) {466d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS);467}468469return d;470}471472private static final RecyclableSingleton<AquaHierarchyButtonListener> fHierListener = new RecyclableSingletonFromDefaultConstructor<AquaHierarchyButtonListener>(AquaHierarchyButtonListener.class);473static AquaHierarchyButtonListener getAquaHierarchyButtonListener() {474return fHierListener.get();475}476477// We need to know when ordinary JButtons are put on JToolbars, but not JComboBoxButtons478// JToggleButtons always have the same border479480private boolean shouldInstallHierListener(final AbstractButton b) {481return (b instanceof JButton || b instanceof JToggleButton && !(b instanceof AquaComboBoxButton) && !(b instanceof JCheckBox) && !(b instanceof JRadioButton));482}483484protected void installHierListener(final AbstractButton b) {485if (shouldInstallHierListener(b)) {486// super put the listener in the button's client properties487b.addHierarchyListener(getAquaHierarchyButtonListener());488}489}490491protected void uninstallHierListener(final AbstractButton b) {492if (shouldInstallHierListener(b)) {493b.removeHierarchyListener(getAquaHierarchyButtonListener());494}495}496497static class AquaHierarchyButtonListener implements HierarchyListener {498// Everytime a hierarchy is change we need to check if the button if moved on or from499// a toolbar. If that is the case, we need to re-set the border of the button.500public void hierarchyChanged(final HierarchyEvent e) {501if ((e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) == 0) return;502503final Object o = e.getSource();504if (!(o instanceof AbstractButton)) return;505506final AbstractButton b = (AbstractButton)o;507final ButtonUI ui = b.getUI();508if (!(ui instanceof AquaButtonUI)) return;509510if (!(b.getBorder() instanceof UIResource)) return; // if the border is not one of ours, or null511((AquaButtonUI)ui).setThemeBorder(b);512}513}514515class AquaButtonListener extends BasicButtonListener implements AncestorListener {516protected final AbstractButton b;517518public AquaButtonListener(final AbstractButton b) {519super(b);520this.b = b;521}522523public void focusGained(final FocusEvent e) {524((Component)e.getSource()).repaint();525}526527public void focusLost(final FocusEvent e) {528// 10-06-03 VL: [Radar 3187049]529// If focusLost arrives while the button has been left-clicked this would disarm the button,530// causing actionPerformed not to fire on mouse release!531//b.getModel().setArmed(false);532b.getModel().setPressed(false);533((Component)e.getSource()).repaint();534}535536public void propertyChange(final PropertyChangeEvent e) {537super.propertyChange(e);538539final String propertyName = e.getPropertyName();540541// Repaint the button, since its border needs to handle the new state.542if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(propertyName)) {543b.repaint();544return;545}546547if ("icon".equals(propertyName) || "text".equals(propertyName)) {548setThemeBorder(b);549return;550}551552if (BUTTON_TYPE.equals(propertyName)) {553// Forced border types554final String value = (String)e.getNewValue();555556final Border border = AquaButtonExtendedTypes.getBorderForPosition(b, value, b.getClientProperty(SEGMENTED_BUTTON_POSITION));557if (border != null) {558b.setBorder(border);559}560561return;562}563564if (SEGMENTED_BUTTON_POSITION.equals(propertyName)) {565final Border border = b.getBorder();566if (!(border instanceof AquaBorder)) return;567568b.setBorder(AquaButtonExtendedTypes.getBorderForPosition(b, b.getClientProperty(BUTTON_TYPE), e.getNewValue()));569}570571if ("componentOrientation".equals(propertyName)) {572final Border border = b.getBorder();573if (!(border instanceof AquaBorder)) return;574575Object buttonType = b.getClientProperty(BUTTON_TYPE);576Object buttonPosition = b.getClientProperty(SEGMENTED_BUTTON_POSITION);577if (buttonType != null && buttonPosition != null) {578b.setBorder(AquaButtonExtendedTypes.getBorderForPosition(b, buttonType, buttonPosition));579}580}581}582583public void ancestorMoved(final AncestorEvent e) {}584585public void ancestorAdded(final AncestorEvent e) {586updateDefaultButton();587}588589public void ancestorRemoved(final AncestorEvent e) {590updateDefaultButton();591}592593protected void updateDefaultButton() {594if (!(b instanceof JButton)) return;595if (!((JButton)b).isDefaultButton()) return;596597final JRootPane rootPane = b.getRootPane();598if (rootPane == null) return;599600final RootPaneUI ui = rootPane.getUI();601if (!(ui instanceof AquaRootPaneUI)) return;602((AquaRootPaneUI)ui).updateDefaultButton(rootPane);603}604}605}606607608