1 /*
   2  * Copyright (c) 1997, 2014, 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 sun.swing.SwingUtilities2;
  29 import sun.awt.AppContext;
  30 
  31 import java.awt.*;
  32 import java.awt.event.*;
  33 import java.io.Serializable;
  34 import javax.swing.*;
  35 import javax.swing.border.*;
  36 import java.awt.*;
  37 import java.awt.event.*;
  38 import javax.swing.plaf.ButtonUI;
  39 import javax.swing.plaf.UIResource;
  40 import javax.swing.plaf.ComponentUI;
  41 import javax.swing.plaf.TextUIDrawing;
  42 import javax.swing.text.View;
  43 
  44 /**
  45  * BasicButton implementation
  46  *
  47  * @author Jeff Dinkins
  48  */
  49 public class BasicButtonUI extends ButtonUI{
  50     // Visual constants
  51     // NOTE: This is not used or set any where. Were we allowed to remove
  52     // fields, this would be removed.
  53     /**
  54      * The default gap between a text and an icon.
  55      */
  56     protected int defaultTextIconGap;
  57 
  58     // Amount to offset text, the value of this comes from
  59     // defaultTextShiftOffset once setTextShiftOffset has been invoked.
  60     private int shiftOffset = 0;
  61     // Value that is set in shiftOffset once setTextShiftOffset has been
  62     // invoked. The value of this comes from the defaults table.
  63     /**
  64      * The default offset of a text.
  65      */
  66     protected int defaultTextShiftOffset;
  67 
  68     private TextUIDrawing textUIDrawing;
  69 
  70     private static final String propertyPrefix = "Button" + ".";
  71 
  72     private static final Object BASIC_BUTTON_UI_KEY = new Object();
  73 
  74     // ********************************
  75     //          Create PLAF
  76     // ********************************
  77     /**
  78      * Returns an instance of {@code BasicButtonUI}.
  79      *
  80      * @param c a component
  81      * @return an instance of {@code BasicButtonUI}
  82      */
  83     public static ComponentUI createUI(JComponent c) {
  84         AppContext appContext = AppContext.getAppContext();
  85         BasicButtonUI buttonUI =
  86                 (BasicButtonUI) appContext.get(BASIC_BUTTON_UI_KEY);
  87         if (buttonUI == null) {
  88             buttonUI = new BasicButtonUI();
  89             appContext.put(BASIC_BUTTON_UI_KEY, buttonUI);
  90         }
  91         return buttonUI;
  92     }
  93 
  94     /**
  95      * Returns the property prefix.
  96      *
  97      * @return the property prefix
  98      */
  99     protected String getPropertyPrefix() {
 100         return propertyPrefix;
 101     }
 102 
 103 
 104     // ********************************
 105     //          Install PLAF
 106     // ********************************
 107     public void installUI(JComponent c) {
 108         installDefaults((AbstractButton) c);
 109         installListeners((AbstractButton) c);
 110         installKeyboardActions((AbstractButton) c);
 111         BasicHTML.updateRenderer(c, ((AbstractButton) c).getText());
 112     }
 113 
 114     /**
 115      * Installs default properties.
 116      *
 117      * @param b an abstract button
 118      */
 119     protected void installDefaults(AbstractButton b) {
 120         // load shared instance defaults
 121         String pp = getPropertyPrefix();
 122 
 123         defaultTextShiftOffset = UIManager.getInt(pp + "textShiftOffset");
 124 
 125         // set the following defaults on the button
 126         if (b.isContentAreaFilled()) {
 127             LookAndFeel.installProperty(b, "opaque", Boolean.TRUE);
 128         } else {
 129             LookAndFeel.installProperty(b, "opaque", Boolean.FALSE);
 130         }
 131 
 132         if(b.getMargin() == null || (b.getMargin() instanceof UIResource)) {
 133             b.setMargin(UIManager.getInsets(pp + "margin"));
 134         }
 135 
 136         LookAndFeel.installColorsAndFont(b, pp + "background",
 137                                          pp + "foreground", pp + "font");
 138         LookAndFeel.installBorder(b, pp + "border");
 139 
 140         Object rollover = UIManager.get(pp + "rollover");
 141         if (rollover != null) {
 142             LookAndFeel.installProperty(b, "rolloverEnabled", rollover);
 143         }
 144 
 145         LookAndFeel.installProperty(b, "iconTextGap", Integer.valueOf(4));
 146         textUIDrawing = SwingUtilities2.getTextUIDrawing(textUIDrawing);
 147     }
 148 
 149     /**
 150      * Registers listeners.
 151      *
 152      * @param b an abstract button
 153      */
 154     protected void installListeners(AbstractButton b) {
 155         BasicButtonListener listener = createButtonListener(b);
 156         if(listener != null) {
 157             b.addMouseListener(listener);
 158             b.addMouseMotionListener(listener);
 159             b.addFocusListener(listener);
 160             b.addPropertyChangeListener(listener);
 161             b.addChangeListener(listener);
 162         }
 163     }
 164 
 165     /**
 166      * Registers keyboard actions.
 167      *
 168      * @param b an abstract button
 169      */
 170     protected void installKeyboardActions(AbstractButton b){
 171         BasicButtonListener listener = getButtonListener(b);
 172 
 173         if(listener != null) {
 174             listener.installKeyboardActions(b);
 175         }
 176     }
 177 
 178 
 179     // ********************************
 180     //         Uninstall PLAF
 181     // ********************************
 182     public void uninstallUI(JComponent c) {
 183         uninstallKeyboardActions((AbstractButton) c);
 184         uninstallListeners((AbstractButton) c);
 185         uninstallDefaults((AbstractButton) c);
 186         BasicHTML.updateRenderer(c, "");
 187     }
 188 
 189     /**
 190      * Unregisters keyboard actions.
 191      *
 192      * @param b an abstract button
 193      */
 194     protected void uninstallKeyboardActions(AbstractButton b) {
 195         BasicButtonListener listener = getButtonListener(b);
 196         if(listener != null) {
 197             listener.uninstallKeyboardActions(b);
 198         }
 199     }
 200 
 201     /**
 202      * Unregisters listeners.
 203      *
 204      * @param b an abstract button
 205      */
 206     protected void uninstallListeners(AbstractButton b) {
 207         BasicButtonListener listener = getButtonListener(b);
 208         if(listener != null) {
 209             b.removeMouseListener(listener);
 210             b.removeMouseMotionListener(listener);
 211             b.removeFocusListener(listener);
 212             b.removeChangeListener(listener);
 213             b.removePropertyChangeListener(listener);
 214         }
 215     }
 216 
 217     /**
 218      * Uninstalls default properties.
 219      *
 220      * @param b an abstract button
 221      */
 222     protected void uninstallDefaults(AbstractButton b) {
 223         LookAndFeel.uninstallBorder(b);
 224         if (textUIDrawing != SwingUtilities2.DEFAULT_UI_TEXT_DRAWING
 225                 && textUIDrawing instanceof UIResource) {
 226             textUIDrawing = SwingUtilities2.DEFAULT_UI_TEXT_DRAWING;
 227         }
 228     }
 229 
 230     // ********************************
 231     //        Create Listeners
 232     // ********************************
 233     /**
 234      * Returns a new instance of {@code BasicButtonListener}.
 235      *
 236      * @param b an abstract button
 237      * @return a new instance of {@code BasicButtonListener}
 238      */
 239     protected BasicButtonListener createButtonListener(AbstractButton b) {
 240         return new BasicButtonListener(b);
 241     }
 242 
 243     /**
 244      * Returns the default gap between a text and an icon.
 245      *
 246      * @param b an abstract button
 247      * @return the default gap between text and an icon
 248      */
 249     public int getDefaultTextIconGap(AbstractButton b) {
 250         return defaultTextIconGap;
 251     }
 252 
 253     /* These rectangles/insets are allocated once for all
 254      * ButtonUI.paint() calls.  Re-using rectangles rather than
 255      * allocating them in each paint call substantially reduced the time
 256      * it took paint to run.  Obviously, this method can't be re-entered.
 257      */
 258     private static Rectangle viewRect = new Rectangle();
 259     private static Rectangle textRect = new Rectangle();
 260     private static Rectangle iconRect = new Rectangle();
 261 
 262     // ********************************
 263     //          Paint Methods
 264     // ********************************
 265 
 266     public void paint(Graphics g, JComponent c)
 267     {
 268         AbstractButton b = (AbstractButton) c;
 269         ButtonModel model = b.getModel();
 270 
 271         String text = layout(b, SwingUtilities2.getFontMetrics(b, g),
 272                b.getWidth(), b.getHeight());
 273 
 274         clearTextShiftOffset();
 275 
 276         // perform UI specific press action, e.g. Windows L&F shifts text
 277         if (model.isArmed() && model.isPressed()) {
 278             paintButtonPressed(g,b);
 279         }
 280 
 281         // Paint the Icon
 282         if(b.getIcon() != null) {
 283             paintIcon(g,c,iconRect);
 284         }
 285 
 286         if (text != null && !text.equals("")){
 287             View v = (View) c.getClientProperty(BasicHTML.propertyKey);
 288             if (v != null) {
 289                 v.paint(g, textRect);
 290             } else {
 291                 paintText(g, b, textRect, text);
 292             }
 293         }
 294 
 295         if (b.isFocusPainted() && b.hasFocus()) {
 296             // paint UI specific focus
 297             paintFocus(g,b,viewRect,textRect,iconRect);
 298         }
 299     }
 300 
 301     /**
 302      * Paints an icon of the current button.
 303      *
 304      * @param g an instance of {@code Graphics}
 305      * @param c a component
 306      * @param iconRect a bounding rectangle to render the icon
 307      */
 308     protected void paintIcon(Graphics g, JComponent c, Rectangle iconRect){
 309             AbstractButton b = (AbstractButton) c;
 310             ButtonModel model = b.getModel();
 311             Icon icon = b.getIcon();
 312             Icon tmpIcon = null;
 313 
 314             if(icon == null) {
 315                return;
 316             }
 317 
 318             Icon selectedIcon = null;
 319 
 320             /* the fallback icon should be based on the selected state */
 321             if (model.isSelected()) {
 322                 selectedIcon = b.getSelectedIcon();
 323                 if (selectedIcon != null) {
 324                     icon = selectedIcon;
 325                 }
 326             }
 327 
 328             if(!model.isEnabled()) {
 329                 if(model.isSelected()) {
 330                    tmpIcon = b.getDisabledSelectedIcon();
 331                    if (tmpIcon == null) {
 332                        tmpIcon = selectedIcon;
 333                    }
 334                 }
 335 
 336                 if (tmpIcon == null) {
 337                     tmpIcon = b.getDisabledIcon();
 338                 }
 339             } else if(model.isPressed() && model.isArmed()) {
 340                 tmpIcon = b.getPressedIcon();
 341                 if(tmpIcon != null) {
 342                     // revert back to 0 offset
 343                     clearTextShiftOffset();
 344                 }
 345             } else if(b.isRolloverEnabled() && model.isRollover()) {
 346                 if(model.isSelected()) {
 347                    tmpIcon = b.getRolloverSelectedIcon();
 348                    if (tmpIcon == null) {
 349                        tmpIcon = selectedIcon;
 350                    }
 351                 }
 352 
 353                 if (tmpIcon == null) {
 354                     tmpIcon = b.getRolloverIcon();
 355                 }
 356             }
 357 
 358             if(tmpIcon != null) {
 359                 icon = tmpIcon;
 360             }
 361 
 362             if(model.isPressed() && model.isArmed()) {
 363                 icon.paintIcon(c, g, iconRect.x + getTextShiftOffset(),
 364                         iconRect.y + getTextShiftOffset());
 365             } else {
 366                 icon.paintIcon(c, g, iconRect.x, iconRect.y);
 367             }
 368 
 369     }
 370 
 371     /**
 372      * Method which renders the text of the current button.
 373      *
 374      * As of Java 2 platform v 1.4 this method should not be used or overriden.
 375      * Use the paintText method which takes the AbstractButton argument.
 376      *
 377      * @param g an instance of {@code Graphics}
 378      * @param c a component
 379      * @param textRect a bounding rectangle to render the text
 380      * @param text a string to render
 381      */
 382     protected void paintText(Graphics g, JComponent c, Rectangle textRect, String text) {
 383         AbstractButton b = (AbstractButton) c;
 384         ButtonModel model = b.getModel();
 385         FontMetrics fm = SwingUtilities2.getFontMetrics(c, g);
 386         int mnemonicIndex = b.getDisplayedMnemonicIndex();
 387 
 388         /* Draw the Text */
 389         if(model.isEnabled()) {
 390             /*** paint the text normally */
 391             g.setColor(b.getForeground());
 392             textUIDrawing.drawStringUnderlineCharAt(c, g,text, mnemonicIndex,
 393                                           textRect.x + getTextShiftOffset(),
 394                                           textRect.y + fm.getAscent() + getTextShiftOffset());
 395         }
 396         else {
 397             /*** paint the text disabled ***/
 398             g.setColor(b.getBackground().brighter());
 399             textUIDrawing.drawStringUnderlineCharAt(c, g,text, mnemonicIndex,
 400                                           textRect.x, textRect.y + fm.getAscent());
 401             g.setColor(b.getBackground().darker());
 402             textUIDrawing.drawStringUnderlineCharAt(c, g,text, mnemonicIndex,
 403                                           textRect.x - 1, textRect.y + fm.getAscent() - 1);
 404         }
 405     }
 406 
 407     /**
 408      * Method which renders the text of the current button.
 409      *
 410      * @param g Graphics context
 411      * @param b Current button to render
 412      * @param textRect Bounding rectangle to render the text
 413      * @param text String to render
 414      * @since 1.4
 415      */
 416     protected void paintText(Graphics g, AbstractButton b, Rectangle textRect, String text) {
 417         paintText(g, (JComponent)b, textRect, text);
 418     }
 419 
 420     // Method signature defined here overriden in subclasses.
 421     // Perhaps this class should be abstract?
 422     /**
 423      * Paints a focused button.
 424      *
 425      * @param g an instance of {@code Graphics}
 426      * @param b an abstract button
 427      * @param viewRect a bounding rectangle to render the button
 428      * @param textRect a bounding rectangle to render the text
 429      * @param iconRect a bounding rectangle to render the icon
 430      */
 431     protected void paintFocus(Graphics g, AbstractButton b,
 432                               Rectangle viewRect, Rectangle textRect, Rectangle iconRect){
 433     }
 434 
 435 
 436     /**
 437      * Paints a pressed button.
 438      *
 439      * @param g an instance of {@code Graphics}
 440      * @param b an abstract button
 441      */
 442     protected void paintButtonPressed(Graphics g, AbstractButton b){
 443     }
 444 
 445     /**
 446      * Clears the offset of the text.
 447      */
 448     protected void clearTextShiftOffset(){
 449         this.shiftOffset = 0;
 450     }
 451 
 452     /**
 453      * Sets the offset of the text.
 454      */
 455     protected void setTextShiftOffset(){
 456         this.shiftOffset = defaultTextShiftOffset;
 457     }
 458 
 459     /**
 460      * Returns the offset of the text.
 461      *
 462      * @return the offset of the text
 463      */
 464     protected int getTextShiftOffset() {
 465         return shiftOffset;
 466     }
 467 
 468     // ********************************
 469     //          Layout Methods
 470     // ********************************
 471     public Dimension getMinimumSize(JComponent c) {
 472         Dimension d = getPreferredSize(c);
 473         View v = (View) c.getClientProperty(BasicHTML.propertyKey);
 474         if (v != null) {
 475             d.width -= v.getPreferredSpan(View.X_AXIS) - v.getMinimumSpan(View.X_AXIS);
 476         }
 477         return d;
 478     }
 479 
 480     public Dimension getPreferredSize(JComponent c) {
 481         AbstractButton b = (AbstractButton)c;
 482         return BasicGraphicsUtils.getPreferredButtonSize(b, b.getIconTextGap());
 483     }
 484 
 485     public Dimension getMaximumSize(JComponent c) {
 486         Dimension d = getPreferredSize(c);
 487         View v = (View) c.getClientProperty(BasicHTML.propertyKey);
 488         if (v != null) {
 489             d.width += v.getMaximumSpan(View.X_AXIS) - v.getPreferredSpan(View.X_AXIS);
 490         }
 491         return d;
 492     }
 493 
 494     /**
 495      * Returns the baseline.
 496      *
 497      * @throws NullPointerException {@inheritDoc}
 498      * @throws IllegalArgumentException {@inheritDoc}
 499      * @see javax.swing.JComponent#getBaseline(int, int)
 500      * @since 1.6
 501      */
 502     public int getBaseline(JComponent c, int width, int height) {
 503         super.getBaseline(c, width, height);
 504         AbstractButton b = (AbstractButton)c;
 505         String text = b.getText();
 506         if (text == null || "".equals(text)) {
 507             return -1;
 508         }
 509         FontMetrics fm = b.getFontMetrics(b.getFont());
 510         layout(b, fm, width, height);
 511         return BasicHTML.getBaseline(b, textRect.y, fm.getAscent(),
 512                                      textRect.width, textRect.height);
 513     }
 514 
 515     /**
 516      * Returns an enum indicating how the baseline of the component
 517      * changes as the size changes.
 518      *
 519      * @throws NullPointerException {@inheritDoc}
 520      * @see javax.swing.JComponent#getBaseline(int, int)
 521      * @since 1.6
 522      */
 523     public Component.BaselineResizeBehavior getBaselineResizeBehavior(
 524             JComponent c) {
 525         super.getBaselineResizeBehavior(c);
 526         if (c.getClientProperty(BasicHTML.propertyKey) != null) {
 527             return Component.BaselineResizeBehavior.OTHER;
 528         }
 529         switch(((AbstractButton)c).getVerticalAlignment()) {
 530         case AbstractButton.TOP:
 531             return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
 532         case AbstractButton.BOTTOM:
 533             return Component.BaselineResizeBehavior.CONSTANT_DESCENT;
 534         case AbstractButton.CENTER:
 535             return Component.BaselineResizeBehavior.CENTER_OFFSET;
 536         }
 537         return Component.BaselineResizeBehavior.OTHER;
 538     }
 539 
 540     private String layout(AbstractButton b, FontMetrics fm,
 541                           int width, int height) {
 542         Insets i = b.getInsets();
 543         viewRect.x = i.left;
 544         viewRect.y = i.top;
 545         viewRect.width = width - (i.right + viewRect.x);
 546         viewRect.height = height - (i.bottom + viewRect.y);
 547 
 548         textRect.x = textRect.y = textRect.width = textRect.height = 0;
 549         iconRect.x = iconRect.y = iconRect.width = iconRect.height = 0;
 550 
 551         // layout the text and icon
 552         return SwingUtilities.layoutCompoundLabel(
 553             b, fm, b.getText(), b.getIcon(),
 554             b.getVerticalAlignment(), b.getHorizontalAlignment(),
 555             b.getVerticalTextPosition(), b.getHorizontalTextPosition(),
 556             viewRect, iconRect, textRect,
 557             b.getText() == null ? 0 : b.getIconTextGap());
 558     }
 559 
 560     /**
 561      * Returns the ButtonListener for the passed in Button, or null if one
 562      * could not be found.
 563      */
 564     private BasicButtonListener getButtonListener(AbstractButton b) {
 565         MouseMotionListener[] listeners = b.getMouseMotionListeners();
 566 
 567         if (listeners != null) {
 568             for (MouseMotionListener listener : listeners) {
 569                 if (listener instanceof BasicButtonListener) {
 570                     return (BasicButtonListener) listener;
 571                 }
 572             }
 573         }
 574         return null;
 575     }
 576 
 577 }