Path: blob/aarch64-shenandoah-jdk8u272-b10/jdk/src/macosx/classes/com/apple/laf/AquaButtonUI.java
38831 views
/*1* Copyright (c) 2011, 2012, 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";4950protected 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) {187final AquaButtonListener listener = createButtonListener(b);188if (listener != null) {189// put the listener in the button's client properties so that190// we can get at it later191b.putClientProperty(this, listener);192193b.addMouseListener(listener);194b.addMouseMotionListener(listener);195b.addFocusListener(listener);196b.addPropertyChangeListener(listener);197b.addChangeListener(listener);198b.addAncestorListener(listener);199}200installHierListener(b);201AquaUtilControlSize.addSizePropertyListener(b);202}203204protected void installKeyboardActions(final AbstractButton b) {205final BasicButtonListener listener = (BasicButtonListener)b.getClientProperty(this);206if (listener != null) listener.installKeyboardActions(b);207}208209// Uninstall PLAF210public void uninstallUI(final JComponent c) {211uninstallKeyboardActions((AbstractButton)c);212uninstallListeners((AbstractButton)c);213uninstallDefaults((AbstractButton)c);214//BasicHTML.updateRenderer(c, "");215}216217protected void uninstallKeyboardActions(final AbstractButton b) {218final BasicButtonListener listener = (BasicButtonListener)b.getClientProperty(this);219if (listener != null) listener.uninstallKeyboardActions(b);220}221222protected void uninstallListeners(final AbstractButton b) {223final AquaButtonListener listener = (AquaButtonListener)b.getClientProperty(this);224b.putClientProperty(this, null);225if (listener != null) {226b.removeMouseListener(listener);227b.removeMouseListener(listener);228b.removeMouseMotionListener(listener);229b.removeFocusListener(listener);230b.removeChangeListener(listener);231b.removePropertyChangeListener(listener);232b.removeAncestorListener(listener);233}234uninstallHierListener(b);235AquaUtilControlSize.addSizePropertyListener(b);236}237238protected void uninstallDefaults(final AbstractButton b) {239LookAndFeel.uninstallBorder(b);240defaults_initialized = false;241}242243// Create Listeners244protected AquaButtonListener createButtonListener(final AbstractButton b) {245return new AquaButtonListener(b);246}247248// Paint Methods249public void paint(final Graphics g, final JComponent c) {250final AbstractButton b = (AbstractButton)c;251final ButtonModel model = b.getModel();252253final Insets i = c.getInsets();254255Rectangle viewRect = new Rectangle(b.getWidth(), b.getHeight());256Rectangle iconRect = new Rectangle();257Rectangle textRect = new Rectangle();258259// we are overdrawing here with translucent colors so we get260// a darkening effect. How can we avoid it. Try clear rect?261if (b.isOpaque()) {262g.setColor(c.getBackground());263g.fillRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);264}265266AquaButtonBorder aquaBorder = null;267if (((AbstractButton)c).isBorderPainted()) {268final Border border = c.getBorder();269270if (border instanceof AquaButtonBorder) {271// only do this if borders are on!272// this also takes care of focus painting.273aquaBorder = (AquaButtonBorder)border;274aquaBorder.paintButton(c, g, viewRect.x, viewRect.y, viewRect.width, viewRect.height);275}276} else {277if (b.isOpaque()) {278viewRect.x = i.left - 2;279viewRect.y = i.top - 2;280viewRect.width = b.getWidth() - (i.right + viewRect.x) + 4;281viewRect.height = b.getHeight() - (i.bottom + viewRect.y) + 4;282if (b.isContentAreaFilled() || model.isSelected()) {283if (model.isSelected()) // Toggle buttons284g.setColor(c.getBackground().darker());285else g.setColor(c.getBackground());286g.fillRect(viewRect.x, viewRect.y, viewRect.width, viewRect.height);287}288}289290// needs focus to be painted291// for now we don't know exactly what to do...we'll see!292if (b.isFocusPainted() && b.hasFocus()) {293// paint UI specific focus294paintFocus(g, b, viewRect, textRect, iconRect);295}296}297298// performs icon and text rect calculations299final String text = layoutAndGetText(g, b, aquaBorder, i, viewRect, iconRect, textRect);300301// Paint the Icon302if (b.getIcon() != null) {303paintIcon(g, b, iconRect);304}305306if (textRect.width == 0) {307textRect.width = 50;308}309310if (text != null && !text.equals("")) {311final View v = (View)c.getClientProperty(BasicHTML.propertyKey);312if (v != null) {313v.paint(g, textRect);314} else {315paintText(g, b, textRect, text);316}317}318}319320protected String layoutAndGetText(final Graphics g, final AbstractButton b, final AquaButtonBorder aquaBorder, final Insets i, Rectangle viewRect, Rectangle iconRect, Rectangle textRect) {321// re-initialize the view rect to the selected insets322viewRect.x = i.left;323viewRect.y = i.top;324viewRect.width = b.getWidth() - (i.right + viewRect.x);325viewRect.height = b.getHeight() - (i.bottom + viewRect.y);326327// reset the text and icon rects328textRect.x = textRect.y = textRect.width = textRect.height = 0;329iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;330331// setup the font332g.setFont(b.getFont());333final FontMetrics fm = g.getFontMetrics();334335// layout the text and icon336final String originalText = b.getText();337final 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());338if (text == originalText || aquaBorder == null) return text; // everything fits339340// if the text didn't fit - check if the aqua border has alternate Insets that are more adhering341final Insets alternateContentInsets = aquaBorder.getContentInsets(b, b.getWidth(), b.getHeight());342if (alternateContentInsets != null) {343// recursively call and don't pass AquaBorder344return layoutAndGetText(g, b, null, alternateContentInsets, viewRect, iconRect, textRect);345}346347// there is no Aqua border, go with what we've got348return text;349}350351protected void paintIcon(final Graphics g, final AbstractButton b, final Rectangle localIconRect) {352final ButtonModel model = b.getModel();353Icon icon = b.getIcon();354Icon tmpIcon = null;355356if (icon == null) return;357358if (!model.isEnabled()) {359if (model.isSelected()) {360tmpIcon = b.getDisabledSelectedIcon();361} else {362tmpIcon = b.getDisabledIcon();363}364} else if (model.isPressed() && model.isArmed()) {365tmpIcon = b.getPressedIcon();366if (tmpIcon == null) {367if (icon instanceof ImageIcon) {368tmpIcon = new ImageIcon(AquaUtils.generateSelectedDarkImage(((ImageIcon)icon).getImage()));369}370}371} else if (b.isRolloverEnabled() && model.isRollover()) {372if (model.isSelected()) {373tmpIcon = b.getRolloverSelectedIcon();374} else {375tmpIcon = b.getRolloverIcon();376}377} else if (model.isSelected()) {378tmpIcon = b.getSelectedIcon();379}380381if (model.isEnabled() && b.isFocusOwner() && b.getBorder() instanceof AquaButtonBorder.Toolbar) {382if (tmpIcon == null) tmpIcon = icon;383if (tmpIcon instanceof ImageIcon) {384tmpIcon = AquaFocus.createFocusedIcon(tmpIcon, b, 3);385tmpIcon.paintIcon(b, g, localIconRect.x - 3, localIconRect.y - 3);386return;387}388}389390if (tmpIcon != null) {391icon = tmpIcon;392}393394icon.paintIcon(b, g, localIconRect.x, localIconRect.y);395}396397/**398* As of Java 2 platform v 1.4 this method should not be used or overriden.399* Use the paintText method which takes the AbstractButton argument.400*/401protected void paintText(final Graphics g, final JComponent c, final Rectangle localTextRect, final String text) {402final Graphics2D g2d = g instanceof Graphics2D ? (Graphics2D)g : null;403404final AbstractButton b = (AbstractButton)c;405final ButtonModel model = b.getModel();406final FontMetrics fm = g.getFontMetrics();407final int mnemonicIndex = AquaMnemonicHandler.isMnemonicHidden() ? -1 : b.getDisplayedMnemonicIndex();408409/* Draw the Text */410if (model.isEnabled()) {411/*** paint the text normally */412g.setColor(b.getForeground());413} else {414/*** paint the text disabled ***/415g.setColor(defaultDisabledTextColor);416}417SwingUtilities2.drawStringUnderlineCharAt(c, g, text, mnemonicIndex, localTextRect.x, localTextRect.y + fm.getAscent());418}419420protected void paintText(final Graphics g, final AbstractButton b, final Rectangle localTextRect, final String text) {421paintText(g, (JComponent)b, localTextRect, text);422}423424protected void paintButtonPressed(final Graphics g, final AbstractButton b) {425paint(g, b);426}427428// Layout Methods429public Dimension getMinimumSize(final JComponent c) {430final Dimension d = getPreferredSize(c);431final View v = (View)c.getClientProperty(BasicHTML.propertyKey);432if (v != null) {433d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS);434}435return d;436}437438public Dimension getPreferredSize(final JComponent c) {439final AbstractButton b = (AbstractButton)c;440441// fix for Radar #3134273442final Dimension d = BasicGraphicsUtils.getPreferredButtonSize(b, b.getIconTextGap());443if (d == null) return null;444445final Border border = b.getBorder();446if (border instanceof AquaButtonBorder) {447((AquaButtonBorder)border).alterPreferredSize(d);448}449450return d;451}452453public Dimension getMaximumSize(final JComponent c) {454final Dimension d = getPreferredSize(c);455456final View v = (View)c.getClientProperty(BasicHTML.propertyKey);457if (v != null) {458d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS);459}460461return d;462}463464final static RecyclableSingleton<AquaHierarchyButtonListener> fHierListener = new RecyclableSingletonFromDefaultConstructor<AquaHierarchyButtonListener>(AquaHierarchyButtonListener.class);465static AquaHierarchyButtonListener getAquaHierarchyButtonListener() {466return fHierListener.get();467}468469// We need to know when ordinary JButtons are put on JToolbars, but not JComboBoxButtons470// JToggleButtons always have the same border471472private boolean shouldInstallHierListener(final AbstractButton b) {473return (b instanceof JButton || b instanceof JToggleButton && !(b instanceof AquaComboBoxButton) && !(b instanceof JCheckBox) && !(b instanceof JRadioButton));474}475476protected void installHierListener(final AbstractButton b) {477if (shouldInstallHierListener(b)) {478// super put the listener in the button's client properties479b.addHierarchyListener(getAquaHierarchyButtonListener());480}481}482483protected void uninstallHierListener(final AbstractButton b) {484if (shouldInstallHierListener(b)) {485b.removeHierarchyListener(getAquaHierarchyButtonListener());486}487}488489static class AquaHierarchyButtonListener implements HierarchyListener {490// Everytime a hierarchy is change we need to check if the button if moved on or from491// a toolbar. If that is the case, we need to re-set the border of the button.492public void hierarchyChanged(final HierarchyEvent e) {493if ((e.getChangeFlags() & HierarchyEvent.PARENT_CHANGED) == 0) return;494495final Object o = e.getSource();496if (!(o instanceof AbstractButton)) return;497498final AbstractButton b = (AbstractButton)o;499final ButtonUI ui = b.getUI();500if (!(ui instanceof AquaButtonUI)) return;501502if (!(b.getBorder() instanceof UIResource)) return; // if the border is not one of ours, or null503((AquaButtonUI)ui).setThemeBorder(b);504}505}506507class AquaButtonListener extends BasicButtonListener implements AncestorListener {508protected final AbstractButton b;509510public AquaButtonListener(final AbstractButton b) {511super(b);512this.b = b;513}514515public void focusGained(final FocusEvent e) {516((Component)e.getSource()).repaint();517}518519public void focusLost(final FocusEvent e) {520// 10-06-03 VL: [Radar 3187049]521// If focusLost arrives while the button has been left-clicked this would disarm the button,522// causing actionPerformed not to fire on mouse release!523//b.getModel().setArmed(false);524((Component)e.getSource()).repaint();525}526527public void propertyChange(final PropertyChangeEvent e) {528super.propertyChange(e);529530final String propertyName = e.getPropertyName();531532// Repaint the button, since its border needs to handle the new state.533if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(propertyName)) {534b.repaint();535return;536}537538if ("icon".equals(propertyName) || "text".equals(propertyName)) {539setThemeBorder(b);540return;541}542543if (BUTTON_TYPE.equals(propertyName)) {544// Forced border types545final String value = (String)e.getNewValue();546547final Border border = AquaButtonExtendedTypes.getBorderForPosition(b, value, b.getClientProperty(SEGMENTED_BUTTON_POSITION));548if (border != null) {549b.setBorder(border);550}551552return;553}554555if (SEGMENTED_BUTTON_POSITION.equals(propertyName)) {556final Border border = b.getBorder();557if (!(border instanceof AquaBorder)) return;558559b.setBorder(AquaButtonExtendedTypes.getBorderForPosition(b, b.getClientProperty(BUTTON_TYPE), e.getNewValue()));560}561562if ("componentOrientation".equals(propertyName)) {563final Border border = b.getBorder();564if (!(border instanceof AquaBorder)) return;565566Object buttonType = b.getClientProperty(BUTTON_TYPE);567Object buttonPosition = b.getClientProperty(SEGMENTED_BUTTON_POSITION);568if (buttonType != null && buttonPosition != null) {569b.setBorder(AquaButtonExtendedTypes.getBorderForPosition(b, buttonType, buttonPosition));570}571}572}573574public void ancestorMoved(final AncestorEvent e) {}575576public void ancestorAdded(final AncestorEvent e) {577updateDefaultButton();578}579580public void ancestorRemoved(final AncestorEvent e) {581updateDefaultButton();582}583584protected void updateDefaultButton() {585if (!(b instanceof JButton)) return;586if (!((JButton)b).isDefaultButton()) return;587588final JRootPane rootPane = b.getRootPane();589if (rootPane == null) return;590591final RootPaneUI ui = rootPane.getUI();592if (!(ui instanceof AquaRootPaneUI)) return;593((AquaRootPaneUI)ui).updateDefaultButton(rootPane);594}595}596}597598599