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