1 /*
   2  * Copyright (c) 2002, 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.synth;
  27 
  28 import javax.swing.*;
  29 import java.awt.*;
  30 import java.beans.*;
  31 import javax.swing.plaf.*;
  32 import javax.swing.plaf.basic.BasicButtonUI;
  33 import javax.swing.plaf.basic.BasicHTML;
  34 import javax.swing.text.View;
  35 
  36 /**
  37  * Provides the Synth L&F UI delegate for
  38  * {@link javax.swing.JButton}.
  39  *
  40  * @author Scott Violet
  41  * @since 1.7
  42  */
  43 public class SynthButtonUI extends BasicButtonUI implements
  44                                  PropertyChangeListener, SynthUI {
  45     private SynthStyle style;
  46 
  47     /**
  48      * Creates a new UI object for the given component.
  49      *
  50      * @param c component to create UI object for
  51      * @return the UI object
  52      */
  53     public static ComponentUI createUI(JComponent c) {
  54         return new SynthButtonUI();
  55     }
  56 
  57     /**
  58      * {@inheritDoc}
  59      */
  60     @Override
  61     protected void installDefaults(AbstractButton b) {
  62         updateStyle(b);
  63 
  64         LookAndFeel.installProperty(b, "rolloverEnabled", Boolean.TRUE);
  65     }
  66 
  67     /**
  68      * {@inheritDoc}
  69      */
  70     @Override
  71     protected void installListeners(AbstractButton b) {
  72         super.installListeners(b);
  73         b.addPropertyChangeListener(this);
  74     }
  75 
  76     void updateStyle(AbstractButton b) {
  77         SynthContext context = getContext(b, SynthConstants.ENABLED);
  78         SynthStyle oldStyle = style;
  79         style = SynthLookAndFeel.updateStyle(context, this);
  80         if (style != oldStyle) {
  81             if (b.getMargin() == null ||
  82                                 (b.getMargin() instanceof UIResource)) {
  83                 Insets margin = (Insets)style.get(context,getPropertyPrefix() +
  84                                                   "margin");
  85 
  86                 if (margin == null) {
  87                     // Some places assume margins are non-null.
  88                     margin = SynthLookAndFeel.EMPTY_UIRESOURCE_INSETS;
  89                 }
  90                 b.setMargin(margin);
  91             }
  92 
  93             Object value = style.get(context, getPropertyPrefix() + "iconTextGap");
  94             if (value != null) {
  95                         LookAndFeel.installProperty(b, "iconTextGap", value);
  96             }
  97 
  98             value = style.get(context, getPropertyPrefix() + "contentAreaFilled");
  99             LookAndFeel.installProperty(b, "contentAreaFilled",
 100                                         value != null? value : Boolean.TRUE);
 101 
 102             if (oldStyle != null) {
 103                 uninstallKeyboardActions(b);
 104                 installKeyboardActions(b);
 105             }
 106 
 107         }
 108         context.dispose();
 109     }
 110 
 111     /**
 112      * {@inheritDoc}
 113      */
 114     @Override
 115     protected void uninstallListeners(AbstractButton b) {
 116         super.uninstallListeners(b);
 117         b.removePropertyChangeListener(this);
 118     }
 119 
 120     /**
 121      * {@inheritDoc}
 122      */
 123     @Override
 124     protected void uninstallDefaults(AbstractButton b) {
 125         SynthContext context = getContext(b, ENABLED);
 126 
 127         style.uninstallDefaults(context);
 128         context.dispose();
 129         style = null;
 130     }
 131 
 132     /**
 133      * {@inheritDoc}
 134      */
 135     @Override
 136     public SynthContext getContext(JComponent c) {
 137         return getContext(c, getComponentState(c));
 138     }
 139 
 140     SynthContext getContext(JComponent c, int state) {
 141         return SynthContext.getContext(c, style, state);
 142     }
 143 
 144     /**
 145      * Returns the current state of the passed in {@code AbstractButton}.
 146      */
 147     private int getComponentState(JComponent c) {
 148         int state = ENABLED;
 149 
 150         if (!c.isEnabled()) {
 151             state = DISABLED;
 152         }
 153         if (SynthLookAndFeel.getSelectedUI() == this) {
 154             return SynthLookAndFeel.getSelectedUIState() | SynthConstants.ENABLED;
 155         }
 156         AbstractButton button = (AbstractButton) c;
 157         ButtonModel model = button.getModel();
 158 
 159         if (model.isPressed()) {
 160             if (model.isArmed()) {
 161                 state = PRESSED;
 162             }
 163             else {
 164                 state = MOUSE_OVER;
 165             }
 166         }
 167         if (model.isRollover()) {
 168             state |= MOUSE_OVER;
 169         }
 170         if (model.isSelected()) {
 171             state |= SELECTED;
 172         }
 173         if (c.isFocusOwner() && button.isFocusPainted()) {
 174             state |= FOCUSED;
 175         }
 176         if ((c instanceof JButton) && ((JButton)c).isDefaultButton()) {
 177             state |= DEFAULT;
 178         }
 179         return state;
 180     }
 181 
 182     /**
 183      * {@inheritDoc}
 184      */
 185     @Override
 186     public int getBaseline(JComponent c, int width, int height) {
 187         if (c == null) {
 188             throw new NullPointerException("Component must be non-null");
 189         }
 190         if (width < 0 || height < 0) {
 191             throw new IllegalArgumentException(
 192                     "Width and height must be >= 0");
 193         }
 194         AbstractButton b = (AbstractButton)c;
 195         String text = b.getText();
 196         if (text == null || "".equals(text)) {
 197             return -1;
 198         }
 199         Insets i = b.getInsets();
 200         Rectangle viewRect = new Rectangle();
 201         Rectangle textRect = new Rectangle();
 202         Rectangle iconRect = new Rectangle();
 203         viewRect.x = i.left;
 204         viewRect.y = i.top;
 205         viewRect.width = width - (i.right + viewRect.x);
 206         viewRect.height = height - (i.bottom + viewRect.y);
 207 
 208         // layout the text and icon
 209         SynthContext context = getContext(b);
 210         FontMetrics fm = context.getComponent().getFontMetrics(
 211             context.getStyle().getFont(context));
 212         context.getStyle().getGraphicsUtils(context).layoutText(
 213             context, fm, b.getText(), b.getIcon(),
 214             b.getHorizontalAlignment(), b.getVerticalAlignment(),
 215             b.getHorizontalTextPosition(), b.getVerticalTextPosition(),
 216             viewRect, iconRect, textRect, b.getIconTextGap());
 217         View view = (View)b.getClientProperty(BasicHTML.propertyKey);
 218         int baseline;
 219         if (view != null) {
 220             baseline = BasicHTML.getHTMLBaseline(view, textRect.width,
 221                                                  textRect.height);
 222             if (baseline >= 0) {
 223                 baseline += textRect.y;
 224             }
 225         }
 226         else {
 227             baseline = textRect.y + fm.getAscent();
 228         }
 229         context.dispose();
 230         return baseline;
 231     }
 232 
 233     // ********************************
 234     //          Paint Methods
 235     // ********************************
 236 
 237     /**
 238      * Notifies this UI delegate to repaint the specified component.
 239      * This method paints the component background, then calls
 240      * the {@link #paint(SynthContext,Graphics)} method.
 241      *
 242      * <p>In general, this method does not need to be overridden by subclasses.
 243      * All Look and Feel rendering code should reside in the {@code paint} method.
 244      *
 245      * @param g the {@code Graphics} object used for painting
 246      * @param c the component being painted
 247      * @see #paint(SynthContext,Graphics)
 248      */
 249     @Override
 250     public void update(Graphics g, JComponent c) {
 251         SynthContext context = getContext(c);
 252 
 253         SynthLookAndFeel.update(context, g);
 254         paintBackground(context, g, c);
 255         paint(context, g);
 256         context.dispose();
 257     }
 258 
 259     /**
 260      * Paints the specified component according to the Look and Feel.
 261      * <p>This method is not used by Synth Look and Feel.
 262      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
 263      *
 264      * @param g the {@code Graphics} object used for painting
 265      * @param c the component being painted
 266      * @see #paint(SynthContext,Graphics)
 267      */
 268     @Override
 269     public void paint(Graphics g, JComponent c) {
 270         SynthContext context = getContext(c);
 271 
 272         paint(context, g);
 273         context.dispose();
 274     }
 275 
 276     /**
 277      * Paints the specified component.
 278      *
 279      * @param context context for the component being painted
 280      * @param g the {@code Graphics} object used for painting
 281      * @see #update(Graphics,JComponent)
 282      */
 283     protected void paint(SynthContext context, Graphics g) {
 284         AbstractButton b = (AbstractButton)context.getComponent();
 285 
 286         g.setColor(context.getStyle().getColor(context,
 287                                                ColorType.TEXT_FOREGROUND));
 288         g.setFont(style.getFont(context));
 289         context.getStyle().getGraphicsUtils(context).paintText(
 290             context, g, b.getText(), getIcon(b),
 291             b.getHorizontalAlignment(), b.getVerticalAlignment(),
 292             b.getHorizontalTextPosition(), b.getVerticalTextPosition(),
 293             b.getIconTextGap(), b.getDisplayedMnemonicIndex(),
 294             getTextShiftOffset(context));
 295     }
 296 
 297     void paintBackground(SynthContext context, Graphics g, JComponent c) {
 298         if (((AbstractButton) c).isContentAreaFilled()) {
 299             context.getPainter().paintButtonBackground(context, g, 0, 0,
 300                                                        c.getWidth(),
 301                                                        c.getHeight());
 302         }
 303     }
 304 
 305     /**
 306      * {@inheritDoc}
 307      */
 308     @Override
 309     public void paintBorder(SynthContext context, Graphics g, int x,
 310                             int y, int w, int h) {
 311         context.getPainter().paintButtonBorder(context, g, x, y, w, h);
 312     }
 313 
 314     /**
 315      * Returns the default icon. This should not callback
 316      * to the JComponent.
 317      *
 318      * @param b button the icon is associated with
 319      * @return default icon
 320      */
 321     protected Icon getDefaultIcon(AbstractButton b) {
 322         SynthContext context = getContext(b);
 323         Icon icon = context.getStyle().getIcon(context, getPropertyPrefix() + "icon");
 324         context.dispose();
 325         return icon;
 326     }
 327 
 328     /**
 329      * Returns the Icon to use for painting the button. The icon is chosen with
 330      * respect to the current state of the button.
 331      *
 332      * @param b button the icon is associated with
 333      * @return an icon
 334      */
 335     protected Icon getIcon(AbstractButton b) {
 336         Icon icon = b.getIcon();
 337         ButtonModel model = b.getModel();
 338 
 339         if (!model.isEnabled()) {
 340             icon = getSynthDisabledIcon(b, icon);
 341         } else if (model.isPressed() && model.isArmed()) {
 342             icon = getPressedIcon(b, getSelectedIcon(b, icon));
 343         } else if (b.isRolloverEnabled() && model.isRollover()) {
 344             icon = getRolloverIcon(b, getSelectedIcon(b, icon));
 345         } else if (model.isSelected()) {
 346             icon = getSelectedIcon(b, icon);
 347         } else {
 348             icon = getEnabledIcon(b, icon);
 349         }
 350         if(icon == null) {
 351             return getDefaultIcon(b);
 352         }
 353         return icon;
 354     }
 355 
 356     /**
 357      * This method will return the icon that should be used for a button.  We
 358      * only want to use the synth icon defined by the style if the specific
 359      * icon has not been defined for the button state and the backup icon is a
 360      * UIResource (we set it, not the developer).
 361      *
 362      * @param b button
 363      * @param specificIcon icon returned from the button for the specific state
 364      * @param defaultIcon fallback icon
 365      * @param state the synth state of the button
 366      */
 367     private Icon getIcon(AbstractButton b, Icon specificIcon, Icon defaultIcon,
 368             int state) {
 369         Icon icon = specificIcon;
 370         if (icon == null) {
 371             if (defaultIcon instanceof UIResource) {
 372                 icon = getSynthIcon(b, state);
 373                 if (icon == null) {
 374                     icon = defaultIcon;
 375                 }
 376             } else {
 377                 icon = defaultIcon;
 378             }
 379         }
 380         return icon;
 381     }
 382 
 383     private Icon getSynthIcon(AbstractButton b, int synthConstant) {
 384         return style.getIcon(getContext(b, synthConstant), getPropertyPrefix() + "icon");
 385     }
 386 
 387     private Icon getEnabledIcon(AbstractButton b, Icon defaultIcon) {
 388         if (defaultIcon == null) {
 389             defaultIcon = getSynthIcon(b, SynthConstants.ENABLED);
 390         }
 391         return defaultIcon;
 392     }
 393 
 394     private Icon getSelectedIcon(AbstractButton b, Icon defaultIcon) {
 395         return getIcon(b, b.getSelectedIcon(), defaultIcon,
 396                 SynthConstants.SELECTED);
 397     }
 398 
 399     private Icon getRolloverIcon(AbstractButton b, Icon defaultIcon) {
 400         ButtonModel model = b.getModel();
 401         Icon icon;
 402         if (model.isSelected()) {
 403             icon = getIcon(b, b.getRolloverSelectedIcon(), defaultIcon,
 404                     SynthConstants.MOUSE_OVER | SynthConstants.SELECTED);
 405         } else {
 406             icon = getIcon(b, b.getRolloverIcon(), defaultIcon,
 407                     SynthConstants.MOUSE_OVER);
 408         }
 409         return icon;
 410     }
 411 
 412     private Icon getPressedIcon(AbstractButton b, Icon defaultIcon) {
 413         return getIcon(b, b.getPressedIcon(), defaultIcon,
 414                 SynthConstants.PRESSED);
 415     }
 416 
 417     private Icon getSynthDisabledIcon(AbstractButton b, Icon defaultIcon) {
 418         ButtonModel model = b.getModel();
 419         Icon icon;
 420         if (model.isSelected()) {
 421             icon = getIcon(b, b.getDisabledSelectedIcon(), defaultIcon,
 422                     SynthConstants.DISABLED | SynthConstants.SELECTED);
 423         } else {
 424             icon = getIcon(b, b.getDisabledIcon(), defaultIcon,
 425                     SynthConstants.DISABLED);
 426         }
 427         return icon;
 428     }
 429 
 430     /**
 431      * Returns the amount to shift the text/icon when painting.
 432      */
 433     private int getTextShiftOffset(SynthContext state) {
 434         AbstractButton button = (AbstractButton)state.getComponent();
 435         ButtonModel model = button.getModel();
 436 
 437         if (model.isArmed() && model.isPressed() &&
 438                                button.getPressedIcon() == null) {
 439             return state.getStyle().getInt(state, getPropertyPrefix() +
 440                                            "textShiftOffset", 0);
 441         }
 442         return 0;
 443     }
 444 
 445     // ********************************
 446     //          Layout Methods
 447     // ********************************
 448 
 449     /**
 450      * {@inheritDoc}
 451      */
 452     @Override
 453     public Dimension getMinimumSize(JComponent c) {
 454         if (c.getComponentCount() > 0 && c.getLayout() != null) {
 455             return null;
 456         }
 457         AbstractButton b = (AbstractButton)c;
 458         SynthContext ss = getContext(c);
 459         Dimension size = ss.getStyle().getGraphicsUtils(ss).getMinimumSize(
 460                ss, ss.getStyle().getFont(ss), b.getText(), getSizingIcon(b),
 461                b.getHorizontalAlignment(), b.getVerticalAlignment(),
 462                b.getHorizontalTextPosition(),
 463                b.getVerticalTextPosition(), b.getIconTextGap(),
 464                b.getDisplayedMnemonicIndex());
 465 
 466         ss.dispose();
 467         return size;
 468     }
 469 
 470     /**
 471      * {@inheritDoc}
 472      */
 473     @Override
 474     public Dimension getPreferredSize(JComponent c) {
 475         if (c.getComponentCount() > 0 && c.getLayout() != null) {
 476             return null;
 477         }
 478         AbstractButton b = (AbstractButton)c;
 479         SynthContext ss = getContext(c);
 480         Dimension size = ss.getStyle().getGraphicsUtils(ss).getPreferredSize(
 481                ss, ss.getStyle().getFont(ss), b.getText(), getSizingIcon(b),
 482                b.getHorizontalAlignment(), b.getVerticalAlignment(),
 483                b.getHorizontalTextPosition(),
 484                b.getVerticalTextPosition(), b.getIconTextGap(),
 485                b.getDisplayedMnemonicIndex());
 486 
 487         ss.dispose();
 488         return size;
 489     }
 490 
 491     /**
 492      * {@inheritDoc}
 493      */
 494     @Override
 495     public Dimension getMaximumSize(JComponent c) {
 496         if (c.getComponentCount() > 0 && c.getLayout() != null) {
 497             return null;
 498         }
 499 
 500         AbstractButton b = (AbstractButton)c;
 501         SynthContext ss = getContext(c);
 502         Dimension size = ss.getStyle().getGraphicsUtils(ss).getMaximumSize(
 503                ss, ss.getStyle().getFont(ss), b.getText(), getSizingIcon(b),
 504                b.getHorizontalAlignment(), b.getVerticalAlignment(),
 505                b.getHorizontalTextPosition(),
 506                b.getVerticalTextPosition(), b.getIconTextGap(),
 507                b.getDisplayedMnemonicIndex());
 508 
 509         ss.dispose();
 510         return size;
 511     }
 512 
 513     /**
 514      * Returns the Icon used in calculating the
 515      * preferred/minimum/maximum size.
 516      *
 517      * @param b specifies the {@code AbstractButton}
 518      * used when calculating the preferred/minimum/maximum
 519      * size.
 520      *
 521      * @return the Icon used in calculating the
 522      * preferred/minimum/maximum size.
 523      */
 524     protected Icon getSizingIcon(AbstractButton b) {
 525         Icon icon = getEnabledIcon(b, b.getIcon());
 526         if (icon == null) {
 527             icon = getDefaultIcon(b);
 528         }
 529         return icon;
 530     }
 531 
 532     /**
 533      * {@inheritDoc}
 534      */
 535     @Override
 536     public void propertyChange(PropertyChangeEvent e) {
 537         if (SynthLookAndFeel.shouldUpdateStyle(e)) {
 538             updateStyle((AbstractButton)e.getSource());
 539         }
 540     }
 541 }