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