com.smartgwt.mobile.client.widgets.BaseButton.java Source code

Java tutorial

Introduction

Here is the source code for com.smartgwt.mobile.client.widgets.BaseButton.java

Source

/*
 * SmartGWT Mobile
 * Copyright 2008 and beyond, Isomorphic Software, Inc.
 *
 * SmartGWT Mobile is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 3
 * as published by the Free Software Foundation.  SmartGWT Mobile is also
 * available under typical commercial license terms - see
 * http://smartclient.com/license
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 */

package com.smartgwt.mobile.client.widgets;

import java.util.List;

import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.dom.client.SpanElement;
import com.google.gwt.dom.client.Style;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Image;
import com.smartgwt.mobile.SGWTInternal;
import com.smartgwt.mobile.client.internal.test.GetAttributeConfiguration;
import com.smartgwt.mobile.client.internal.types.AttributeType;
import com.smartgwt.mobile.client.internal.util.ElementUtil;
import com.smartgwt.mobile.client.theme.ButtonsCssResource;
import com.smartgwt.mobile.client.theme.ThemeResources;
import com.smartgwt.mobile.client.types.IconAlign;
import com.smartgwt.mobile.client.types.State;
import com.smartgwt.mobile.client.widgets.icons.IconResources;

/**
 * Base abstract class for button types.
 */
@SGWTInternal
public abstract class BaseButton extends StatefulCanvas {

    @SGWTInternal
    public static final ButtonsCssResource _CSS = ThemeResources.INSTANCE.buttonsCSS();

    public static enum ButtonType {

        BORDERED("bordered"), BORDERED_IMPORTANT("bordered important"), BORDERED_WARNING("bordered warning"), PLAIN(
                "plain-icon"),

        /**
         * A rounded rectangle style button.
         */
        ROUNDED_RECTANGLE("rounded-rectangle"),

        /**
         * A rounded rectangle style button. Used to denote buttons that are more "important"
         */
        ROUNDED_RECTANGLE_IMPORTANT("rounded-rectangle important"),

        /**
         * A default "action" button.
         */
        ACTION_DEFAULT("action default"),

        /**
         * Action button used for "cancel" events.
         */
        ACTION_CANCEL("action cancel"),

        /**
         * Action button used for "delete" events.
         */
        ACTION_DELETE("action delete"),

        /**
         * Action button used for "important" events.
         */
        ACTION_IMPORTANT("action important"),

        /**
         * A "green" action button.
         */
        ACTION_GREEN("action green");

        private ButtonType(String value) {
        }

        @SGWTInternal
        public final String _getClassNames() {
            switch (this) {
            case BORDERED:
                return _CSS.borderedButtonClass();
            case BORDERED_IMPORTANT:
                return _CSS.borderedButtonClass() + " " + _CSS.importantButtonClass();
            case BORDERED_WARNING:
                return _CSS.borderedButtonClass() + " " + _CSS.warningButtonClass();
            case PLAIN:
                return _CSS.plainIconButtonClass();
            case ROUNDED_RECTANGLE:
                return _CSS.roundedRectangleButtonClass();
            case ROUNDED_RECTANGLE_IMPORTANT:
                return _CSS.roundedRectangleButtonClass() + " " + _CSS.importantButtonClass();
            case ACTION_DEFAULT:
                return _CSS.actionButtonClass() + " " + _CSS.defaultButtonClass();
            case ACTION_CANCEL:
                return _CSS.actionButtonClass() + " " + _CSS.cancelButtonClass();
            case ACTION_DELETE:
                return _CSS.actionButtonClass() + " " + _CSS.deleteButtonClass();
            case ACTION_IMPORTANT:
                return _CSS.actionButtonClass() + " " + _CSS.importantButtonClass();
            case ACTION_GREEN:
                return _CSS.actionButtonClass() + " " + _CSS.greenButtonClass();
            }
            assert false : "ButtonType._getClassNames() needs to handle ButtonType." + this;
            throw new RuntimeException();
        }
    }

    private transient DivElement contentElem;
    private transient Image iconImage;
    private transient HandlerRegistration iconLoadRegistration;
    private transient SpanElement labelElem;

    private ImageResource icon;
    private boolean masked;

    /**
     * The alignment of the icon with respect to its title
     */
    private IconAlign iconAlign = IconAlign.LEFT;

    /**
     * The color of the icon can be set programmatically. The result will be the icon being displayed
     * in solid "iconColor"
     */
    private String iconColor;

    /**
     * Buttons support being tinted dynamically.
     */
    protected String tintColor;

    private boolean stretch = false;

    /**
     * The default constructor that sets up the basic button structure. A button should have a
     * {@link #setTitle(String) title} and / or an {@link #setIcon(com.google.gwt.user.client.ui.Image, boolean) icon}
     * to have a meaningful appearance.
     */
    public BaseButton() {
        createElements();
        super.setShowDown(true);
    }

    /**
     * BaseButton constructor.
     *
     * @param title the title label of the button
     */
    public BaseButton(String title) {
        this();
        setTitle(title);
    }

    public BaseButton(BaseButton button) {
        createElements();
        super.setShowDown(true);

        setTitle(button.getTitle(), false);
        setIcon(button.icon, button.masked, false);
        setIconAlign(button.getIconAlign());
        setIconColor(button.getIconColor());
        setTintColor(button.getTintColor());
        setStretch(button.stretch);
    }

    @Override
    public Object _getInnerAttributeFromSplitLocator(List<String> locatorArray,
            GetAttributeConfiguration configuration) {
        if (configuration.getAttribute() == AttributeType.VALUE) {
            if (locatorArray.size() == 1) {
                final String valueName = locatorArray.get(0);
                if ("title".equals(valueName))
                    return getTitle();
            }
        }
        return super._getInnerAttributeFromSplitLocator(locatorArray, configuration);
    }

    private void createElements() {
        DivElement buttonElem = Document.get().createDivElement();
        buttonElem.addClassName(_CSS.buttonClass());
        buttonElem.addClassName(_CSS.iconOnlyButtonClass());
        contentElem = Document.get().createDivElement();
        contentElem.addClassName(_CSS.buttonContentClass());
        labelElem = Document.get().createSpanElement();
        labelElem.addClassName(_CSS.buttonLabelClass());
        contentElem.appendChild(labelElem);
        buttonElem.appendChild(contentElem);
        setElement(buttonElem);
    }

    @Override
    public void destroy() {
        if (iconLoadRegistration != null) {
            iconLoadRegistration.removeHandler();
        }
    }

    /**
     * Sets the title of the button.
     * 
     * @deprecated Use {@link #setTitle(String)} instead.
     */
    @Override
    @Deprecated
    public void setContents(String html) {
        // Note: The reason why this method does not set the innerHTML of `contentElem` is
        // that it is impossible to get the "actual" (unclipped) width of the contents in
        // _getContentWidth().  This is because `contentElem` is display:block.
        // display:inline-block would work, but then text-overflow:ellipsis on the label
        // element no longer works.  Also, overflow:hidden would have to be added to
        // getElement(), but this can change the appearance of the button considerably
        // (for example, NavigationButton places the arrows of "back" and "next" buttons
        // outside of the border box and these are clipped by overflow:hidden on getElement()).
        setTitle(html);
    }

    @SGWTInternal
    public final float _getContentWidth() {
        float ret = labelElem.getOffsetWidth();
        if (icon != null && iconImage.isVisible()) {
            ret += ElementUtil.getOuterWidth(iconImage.getElement(), true);
        }
        return ret;
    }

    @SGWTInternal
    public final SpanElement _getLabelElement() {
        return labelElem;
    }

    @Override
    public String _getStateName() {
        // FIXME Consolidate the CSS selectors in buttons.gwt.css.
        return _CSS.buttonClass();
    }

    /**
     * Return the title label.
     *
     * @return the title label
     */
    public final String getTitle() {
        return labelElem.getInnerHTML();
    }

    /**
     * Sets the title label of the button.
     *
     * @param title the button title
     */
    public void setTitle(String title) {
        setTitle(title, true);
    }

    private void setTitle(String title, boolean fireContentChangedEvent) {
        labelElem.setInnerHTML(title);
        if (title == null || title.isEmpty()) {
            getElement().addClassName(_CSS.iconOnlyButtonClass());
        } else {
            getElement().removeClassName(_CSS.iconOnlyButtonClass());
        }
        if (fireContentChangedEvent)
            _fireContentChangedEvent();
    }

    /**
     * Return the button icon, or null if not set.
     *
     * @return the button icon
     */
    public final ImageResource getIcon() {
        return icon;
    }

    /**
     * Set the button icon from an {@link com.google.gwt.resources.client.ImageResource}.
     * <p>
     * <b>Note:</b> When using masking, the <code>ImageResource</code> must be a separate resource
     * at runtime. If using GWT {@link com.google.gwt.resources.client.ClientBundle}s, it is
     * sufficient to set the <code>preventInlining</code> image option to <code>true</code>.
     *
     * @param icon         the icon image resource.
     * @param mask         true to mask the button. Masking is typically applied to black-only icons causing its final
     *                     appearance to be white
     */
    public void setIcon(ImageResource icon, boolean mask) {
        setIcon(icon, mask, true);
    }

    /**
     * Set the button icon from an {@link com.google.gwt.resources.client.ImageResource}.
     * <p>
     * <b>Note:</b> When using masking, the <code>ImageResource</code> must be a separate resource
     * at runtime. If using GWT {@link com.google.gwt.resources.client.ClientBundle}s, it is
     * sufficient to set the <code>preventInlining</code> image option to <code>true</code>.
     *
     * @param icon         the icon image resource.
     * @param mask         true to mask the button. Masking is typically applied to black-only icons causing its final
     *                     appearance to be white
     * @param iconAlign    the alignment of the icon
     */
    public void setIcon(ImageResource icon, boolean mask, IconAlign iconAlign) {
        setIcon(icon, mask, true);
        setIconAlign(iconAlign);
    }

    private void setIcon(final ImageResource icon, boolean mask, boolean fireContentChangedEvent) {
        assert !mask || icon == null || (icon.getTop() == 0 && icon
                .getLeft() == 0) : "When using masking, the ImageResource must be a separate resource at runtime.";
        if (iconLoadRegistration != null) {
            iconLoadRegistration.removeHandler();
        }
        if (iconImage != null) {
            remove(iconImage);
            iconImage = null;
        }
        this.icon = icon;
        this.masked = mask;

        if (icon != null) {
            iconImage = new Image(icon);
            getElement().addClassName(_CSS.buttonHasIconClass());
            if (icon.getWidth() > 0) {
                doSetIcon(false);
            } else {
                iconImage.getElement().getStyle().setVisibility(Style.Visibility.HIDDEN);
                iconLoadRegistration = iconImage.addLoadHandler(new com.google.gwt.event.dom.client.LoadHandler() {
                    @Override
                    public void onLoad(com.google.gwt.event.dom.client.LoadEvent event) {
                        iconLoadRegistration.removeHandler();
                        doSetIcon(true);
                    }
                });
            }
            if (iconAlign != IconAlign.RIGHT) {
                insert(iconImage, contentElem.<com.google.gwt.user.client.Element>cast(), 0, true);
            } else {
                add(iconImage, contentElem.<com.google.gwt.user.client.Element>cast());
            }
        } else {
            getElement().removeClassName(_CSS.buttonHasIconClass());
        }
        if (fireContentChangedEvent)
            _fireContentChangedEvent();
    }

    private void doSetIcon(boolean fireContentChangedEvent) {
        assert icon != null && iconImage != null;
        final Element img = iconImage.getElement();
        final Style imgStyle = img.getStyle();
        if (masked) {
            iconImage.setResource(IconResources.INSTANCE.blank());
            if ("IMG".equals(img.getTagName())) {
                final ImageElement iconImageImgElem = ImageElement.as(img);
                iconImageImgElem.removeAttribute("width");
                iconImageImgElem.removeAttribute("height");
            }
            imgStyle.clearWidth();
            imgStyle.clearHeight();
            img.setClassName(_CSS.maskedButtonIconClass());
            imgStyle.clearBackgroundColor();
            imgStyle.clearBackgroundImage();
            // Note: Mobile WebKit automatically scales down the mask image to fit the element
            // to which the mask image is applied.
            imgStyle.setProperty("WebkitMaskBoxImage", "url(" + icon.getSafeUri().asString() + ")");
        } else {
            if ("IMG".equals(img.getTagName())) {
                final ImageElement iconImageImgElem = ImageElement.as(img);
                iconImageImgElem.removeAttribute("width");
                iconImageImgElem.removeAttribute("height");
            }
            imgStyle.clearWidth();
            imgStyle.clearHeight();
            imgStyle.clearProperty("MozBackgroundSize");
            imgStyle.clearProperty("OBackgroundSize");
            imgStyle.clearProperty("WebkitBackgroundSize");
            imgStyle.clearProperty("backgroundSize");
            img.removeClassName(_CSS.maskedButtonIconClass());
        }

        setIconAlign(iconAlign);
        if (iconColor != null) {
            setIconColor(iconColor);
        }

        imgStyle.clearVisibility();
        if (fireContentChangedEvent)
            _fireContentChangedEvent();
    }

    /**
     * Return the icon alignment.
     *
     * @return the icon alignment. Defaults to left
     */
    public IconAlign getIconAlign() {
        return iconAlign;
    }

    /**
     * Set the icon alignment. Default is left.
     *
     * @param iconAlign the icon alignment
     */
    public void setIconAlign(IconAlign iconAlign) {
        if (this.iconAlign != iconAlign) {
            if (this.iconAlign != null && this.iconAlign != IconAlign.LEFT) {
                getElement().removeClassName(iconAlign._getClassName());
            }
            if (icon != null) {
                if (this.iconAlign == IconAlign.RIGHT) {
                    remove(iconImage);
                    insert(iconImage, contentElem.<com.google.gwt.user.client.Element>cast(), 0, true);
                } else if (iconAlign == IconAlign.RIGHT) {
                    remove(iconImage);
                    add(iconImage, contentElem.<com.google.gwt.user.client.Element>cast());
                }
            }
            this.iconAlign = iconAlign;
            if (icon != null && iconAlign != null && iconAlign != IconAlign.LEFT) {
                getElement().addClassName(iconAlign._getClassName());
            }
        }
    }

    /**
     * Return the tint color. Default is null and the theme's default color is used.
     *
     * @return the tint color
     */
    public String getTintColor() {
        return tintColor;
    }

    /**
     * Set the button tintColor. Can pass in the color name, the hex value, or the rgb / rgba value as a String.
     * <br><br>
     * <b>Note:</b> Passing a rgba string with a value for opacity allows buttons to have an opacity / translucency.
     *
     * @param tintColor the tint color
     */
    public void setTintColor(String tintColor) {
        this.tintColor = tintColor;
        if (tintColor != null) {
            getElement().addClassName(_CSS.customTintedButtonClass());
            getElement().getStyle().setBackgroundColor(tintColor);
        } else {
            getElement().removeClassName(_CSS.customTintedButtonClass());
            getElement().getStyle().clearBackgroundColor();
        }
    }

    /**
     * Return the icon color.
     *
     * @return the icon color. Defaults to null
     */
    public final String getIconColor() {
        return iconColor;
    }

    /**
     * Set the icon color. This overrides the default color of the icon an applies a solid color to the icon
     * based on the icon color provided. An example use of this is to set the icon color of a "star" icon to yellow
     * to indicate it has been marked.
     *
     * @param iconColor the icon color. Defaults to null
     */
    public void setIconColor(String iconColor) {
        this.iconColor = iconColor;
        if (this.icon != null) {
            final Element imgElement = iconImage.getElement();
            imgElement.getStyle().setBackgroundColor(iconColor);
        }
    }

    /**
     * Should the button stretch in width?
     *
     * @return true for stretch, false for auto-width based on contents
     */
    public final boolean isStretch() {
        return stretch;
    }

    /**
     * Should the button stretch in width?
     *
     * @param stretch true for stretch, false for auto-width based on contents
     */
    public void setStretch(boolean stretch) {
        this.stretch = stretch;
        if (stretch) {
            getElement().addClassName(_CSS.stretchButtonClass());
        } else {
            getElement().removeClassName(_CSS.stretchButtonClass());
        }
    }

    @Override
    protected void stateChanged() {
        super.stateChanged();
        if (getState() == State.STATE_DOWN || Canvas._booleanValue(isSelected(), false)) {
            getElement().addClassName(_CSS.touchedButtonClass());
        } else {
            getElement().removeClassName(_CSS.touchedButtonClass());
        }
    }
}