/*
* Ext GWT 2.2.4 - Ext for GWT
* Copyright(c) 2007-2010, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.widget.button;
import com.extjs.gxt.ui.client.GXT;
import com.extjs.gxt.ui.client.Style;
import com.extjs.gxt.ui.client.Style.ButtonArrowAlign;
import com.extjs.gxt.ui.client.Style.ButtonScale;
import com.extjs.gxt.ui.client.Style.IconAlign;
import com.extjs.gxt.ui.client.aria.FocusFrame;
import com.extjs.gxt.ui.client.core.El;
import com.extjs.gxt.ui.client.core.Template;
import com.extjs.gxt.ui.client.event.ButtonEvent;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.event.MenuEvent;
import com.extjs.gxt.ui.client.event.PreviewEvent;
import com.extjs.gxt.ui.client.event.SelectionListener;
import com.extjs.gxt.ui.client.util.BaseEventPreview;
import com.extjs.gxt.ui.client.util.IconHelper;
import com.extjs.gxt.ui.client.util.TextMetrics;
import com.extjs.gxt.ui.client.util.Util;
import com.extjs.gxt.ui.client.widget.BoxComponent;
import com.extjs.gxt.ui.client.widget.IconSupport;
import com.extjs.gxt.ui.client.widget.menu.Menu;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.DeferredCommand;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.AbstractImagePrototype;
import com.google.gwt.user.client.ui.Accessibility;
/**
* A button component.
*
* <dl>
* <dt><b>Events:</b></dt>
*
* <dd><b>BeforeSelect</b> : ButtonEvent(button, event)<br>
* <div>Fires before this button is selected.</div>
* <ul>
* <li>button : this</li>
* <li>event : the dom event</li>
* </ul>
* </dd>
*
* <dd><b>Select</b> : ButtonEvent(button, event)<br>
* <div>Fires when this button is selected.</div>
* <ul>
* <li>button : this</li>
* <li>event : the dom event</li>
* </ul>
* </dd>
*
* <dd><b>MenuShow</b> : ButtonEvent(button, item)<br>
* <div>If this button has a menu, this event fires when it is shown.</div>
* <ul>
* <li>button : this</li>
* <li>menu : the menu</li>
* </ul>
* </dd>
*
* <dd><b>MenuHide</b> : ButtonEvent(button, item)<br>
* <div>If this button has a menu, this event fires when it is hidden.</div>
* <ul>
* <li>button : this</li>
* <li>menu : the menu</li>
* </ul>
* </dd>
*
* </dl>
*/
@SuppressWarnings("deprecation")
public class Button extends BoxComponent implements IconSupport {
private static Template buttonTemplate;
protected El buttonEl;
protected String buttonSelector = "button";
protected AbstractImagePrototype icon;
protected Menu menu;
protected ButtonScale scale = ButtonScale.SMALL;
protected Template template;
protected String text;
private ButtonArrowAlign arrowAlign = ButtonArrowAlign.RIGHT;
private boolean handleMouseEvents = true;
private IconAlign iconAlign = IconAlign.LEFT;
private String menuAlign = "tl-bl?";
private Listener<MenuEvent> menuListener;
private int minWidth = Style.DEFAULT;
private BaseEventPreview preview;
private int tabIndex = 0;
private String type = "button";
/**
* Creates a new button.
*/
public Button() {
baseStyle = "x-btn";
preview = new BaseEventPreview() {
protected boolean onAutoHide(PreviewEvent ce) {
Button.this.onMouseOut(null);
return true;
}
};
// if the user holds down the mouse button on menu and split
// buttons, content on the page is selected
disableTextSelection(true);
}
/**
* Creates a new button with the given text.
*
* @param text the button text
*/
public Button(String text) {
this();
setText(text);
}
/**
* Creates a new button with the given text and iconStyle.
*
* @param text the button text
* @param icon the icon
*/
public Button(String text, AbstractImagePrototype icon) {
this(text);
setIcon(icon);
}
/**
* Creates a new button with the given text, iconStyle and specified selection
* listener.
*
* @param text the button text
* @param icon the icon
* @param listener the selection listener
*/
public Button(String text, AbstractImagePrototype icon, SelectionListener<ButtonEvent> listener) {
this(text, icon);
addSelectionListener(listener);
}
/**
* Creates a new button with the given text and specified selection listener.
*
* @param text the button's text
* @param listener the selection listener
*/
public Button(String text, SelectionListener<ButtonEvent> listener) {
this(text);
addSelectionListener(listener);
}
/**
* Adds a selection listener.
*
* @param listener the listener to add
*/
public void addSelectionListener(SelectionListener<ButtonEvent> listener) {
addListener(Events.Select, listener);
}
/**
* Returns the button's arrow alignment.
*
* @return the arrow alignment
*/
public ButtonArrowAlign getArrowAlign() {
return arrowAlign;
}
/**
* Returns the button's icon style.
*
* @return the icon style
*/
public AbstractImagePrototype getIcon() {
return icon;
}
/**
* Returns the button's icon alignment.
*
* @return the icon alignment
*/
public IconAlign getIconAlign() {
return iconAlign;
}
/**
* Returns the button's menu (if it has one).
*
* @return the menu
*/
public Menu getMenu() {
return menu;
}
/**
* Returns the button's menu alignment.
*
* @return the menu alignment
*/
public String getMenuAlign() {
return menuAlign;
}
/**
* Returns the button's minimum width.
*
* @return the minWidth the minimum width
*/
public int getMinWidth() {
return minWidth;
}
/**
* Returns true if mouse over effect is disabled.
*
* @return the handleMouseEvents the handle mouse event state
*/
public boolean getMouseEvents() {
return handleMouseEvents;
}
/**
* Returns the buttons scale.
*
* @return the scale
*/
public ButtonScale getScale() {
return scale;
}
/**
* Returns the button's text.
*
* @return the button text
*/
public String getText() {
return text;
}
/**
* @return the type
*/
public String getType() {
return type;
}
/**
* Hide this button's menu (if it has one).
*/
public void hideMenu() {
if (menu != null) {
menu.hide();
}
}
@Override
public void onComponentEvent(ComponentEvent ce) {
super.onComponentEvent(ce);
ButtonEvent be = (ButtonEvent) ce;
switch (ce.getEventTypeInt()) {
case Event.ONMOUSEOVER:
onMouseOver(ce);
break;
case Event.ONMOUSEOUT:
onMouseOut(ce);
break;
case Event.ONMOUSEDOWN:
onMouseDown(ce);
break;
case Event.ONMOUSEUP:
onMouseUp(ce);
break;
case Event.ONCLICK:
onClick(ce);
break;
case Event.ONFOCUS:
onFocus(ce);
break;
case Event.ONBLUR:
onBlur(be);
break;
case Event.ONKEYUP:
onKeyPress(be);
break;
}
}
/**
* Removes a previously added listener.
*
* @param listener the listener to be removed
*/
public void removeSelectionListener(SelectionListener<ButtonEvent> listener) {
removeListener(Events.Select, listener);
}
/**
* Sets the arrow alignment (defaults to RIGHT).
*
* @param arrowAlign the arrow alignment
*/
public void setArrowAlign(ButtonArrowAlign arrowAlign) {
this.arrowAlign = arrowAlign;
}
/**
* Sets the button's icon style. The style name should match a CSS style that
* specifies a background image using the following format:
*
* <pre>
*
* <code> .my-icon { background: url(images/icons/my-icon.png) no-repeat
* center left !important; } </code>
*
* </pre>
*
* @param icon the icon
*/
public void setIcon(AbstractImagePrototype icon) {
if (rendered) {
El oldIcon = buttonEl.selectNode("." + baseStyle + "-image");
if (oldIcon != null) {
oldIcon.remove();
el().removeStyleName(baseStyle + "-text-icon", baseStyle + "-icon", baseStyle + "-noicon");
}
el().addStyleName(
(icon != null ? (!Util.isEmptyString(text) ? " " + baseStyle + "-text-icon" : " " + baseStyle + "-icon")
: " " + baseStyle + "-noicon"));
Element e = null;
if (icon != null) {
e = (Element) icon.createElement().cast();
Accessibility.setRole(e, "presentation");
fly(e).addStyleName(baseStyle + "-image");
buttonEl.insertFirst(e);
El.fly(e).makePositionable(true);
}
autoWidth();
alignIcon(e);
}
this.icon = icon;
}
/**
* Sets the icon alignment (defaults to LEFT).
*
* @param iconAlign the icon alignment
*/
public void setIconAlign(IconAlign iconAlign) {
this.iconAlign = iconAlign;
}
public void setIconStyle(String icon) {
setIcon(IconHelper.create(icon));
}
/**
* Sets the button's menu.
*
* @param menu the menu
*/
public void setMenu(Menu menu) {
if (menuListener == null) {
menuListener = new Listener<MenuEvent>() {
public void handleEvent(MenuEvent be) {
if (Events.Show.equals(be.getType())) {
onMenuShow(be);
} else if (Events.Hide.equals(be.getType())) {
onMenuHide(be);
}
}
};
}
if (this.menu != null) {
this.menu.setData("parent", null);
this.menu.removeListener(Events.Hide, menuListener);
this.menu.removeListener(Events.Show, menuListener);
}
this.menu = menu;
if (this.menu != null) {
this.menu.setData("parent", this);
this.menu.addListener(Events.Hide, menuListener);
this.menu.addListener(Events.Show, menuListener);
this.menu.getAriaSupport().setLabelledBy(getId());
getElement().setAttribute("aria-owns", menu.getId());
}
}
/**
* Sets the position to align the menu to, see {@link El#alignTo} for more
* details (defaults to 'tl-bl?', pre-render).
*
* @param menuAlign the menu alignment
*/
public void setMenuAlign(String menuAlign) {
this.menuAlign = menuAlign;
}
/**
* Sets he minimum width for this button (used to give a set of buttons a
* common width)
*
* @param minWidth the minimum width
*/
public void setMinWidth(int minWidth) {
this.minWidth = minWidth;
}
/**
* False to disable visual cues on mouseover, mouseout and mousedown (defaults
* to true).
*
* @param handleMouseEvents false to disable mouse over changes
*/
public void setMouseEvents(boolean handleMouseEvents) {
this.handleMouseEvents = handleMouseEvents;
}
/**
* Sets the button scale.
*
* @param scale the scale to set
*/
public void setScale(ButtonScale scale) {
this.scale = scale;
}
/**
* Sets the button's tab index.
*
* @param index the tab index
*/
@Override
public void setTabIndex(int index) {
this.tabIndex = index;
if (rendered && buttonEl != null) {
buttonEl.dom.setPropertyInt("tabIndex", index);
}
}
/**
* Sets the button's text.
*
* @param text the new text
*/
public void setText(String text) {
this.text = text;
if (rendered) {
buttonEl.update(Util.isEmptyString(text) ? " " : text);
setIcon(icon);
}
}
/**
* Submit, reset or button (defaults to 'button').
*
* @param type the new type
*/
public void setType(String type) {
this.type = type;
}
/**
* Show this button's menu (if it has one).
*/
public void showMenu() {
if (menu != null) {
if (GXT.isAriaEnabled()) {
// delay need for readers
DeferredCommand.addCommand(new Command() {
public void execute() {
menu.show(getElement(), menuAlign);
}
});
} else {
menu.show(getElement(), menuAlign);
}
}
}
@Override
protected void afterRender() {
super.afterRender();
setTabIndex(tabIndex);
setIcon(icon);
}
protected void alignIcon(Element icon) {
if (icon != null) {
String align = null;
if (iconAlign == IconAlign.BOTTOM) {
align = "b-b";
} else if (iconAlign == IconAlign.TOP) {
align = "t-t";
} else if (iconAlign == IconAlign.LEFT) {
align = "l-l";
} else if (iconAlign == IconAlign.RIGHT) {
align = "r-r";
}
int[] offset = null;
if (GXT.isIE8 && GXT.isStrict && (iconAlign == IconAlign.LEFT || iconAlign == IconAlign.RIGHT)
&& (scale == ButtonScale.LARGE || scale == ButtonScale.MEDIUM)) {
if (scale == ButtonScale.LARGE) {
offset = new int[] {0, -8};
} else if (scale == ButtonScale.MEDIUM) {
offset = new int[] {0, -4};
}
icon.getStyle().setProperty("left", "");
icon.getStyle().setProperty("top", "");
}
El.fly(icon).alignTo(buttonEl.dom, align, offset);
}
}
protected void autoWidth() {
if (rendered && width == null && buttonEl != null) {
int w = 0;
if (!Util.isEmptyString(text)) {
TextMetrics.get().bind(buttonEl);
w = TextMetrics.get().getWidth(text);
w += buttonEl.getFrameWidth("lr");
if (GXT.isGecko || GXT.isWebKit) {
w += 6;
}
} else {
buttonEl.dom.getStyle().setProperty("width", "");
w = buttonEl.getStyleSize(false).width;
}
int adj = 0;
if (GXT.isAriaEnabled()) {
adj += text == null ? 10 : 25;
}
w += adj;
if (w < minWidth - 6) {
buttonEl.setWidth(minWidth - 6, true);
} else {
buttonEl.setWidth(w, true);
}
}
}
@Override
protected ComponentEvent createComponentEvent(Event event) {
return new ButtonEvent(this);
}
@Override
protected El getFocusEl() {
return buttonEl;
}
protected String getMenuClass() {
if (menu != null) {
if (arrowAlign == ButtonArrowAlign.BOTTOM) {
return baseStyle + "-arrow-bottom";
} else {
return baseStyle + "-arrow";
}
} else {
return "";
}
}
@Override
protected void notifyShow() {
super.notifyShow();
if (icon != null) {
El e = buttonEl.selectNode("." + baseStyle + "-image");
if (e != null) {
alignIcon(e.dom);
}
}
}
protected void onBlur(ButtonEvent e) {
removeStyleName(baseStyle + "-focus");
if (GXT.isFocusManagerEnabled()) {
FocusFrame.get().unframe();
}
}
protected void onClick(ComponentEvent ce) {
ce.preventDefault();
focus();
hideToolTip();
if (!disabled) {
ButtonEvent be = new ButtonEvent(this);
if (!fireEvent(Events.BeforeSelect, be)) {
return;
}
if (menu != null && !menu.isVisible()) {
showMenu();
}
fireEvent(Events.Select, be);
}
}
@Override
protected void onDetach() {
super.onDetach();
preview.remove();
removeStyleName(baseStyle + "-click");
removeStyleName(baseStyle + "-over");
removeStyleName(baseStyle + "-menu-active");
removeStyleName(baseStyle + "-focus");
}
@Override
protected void onDisable() {
if (!GXT.isIE6 || text == null) {
addStyleName(disabledStyle);
}
removeStyleName(baseStyle + "-over");
el().disable();
buttonEl.dom.setAttribute("aria-disabled", "true");
}
@Override
protected void onEnable() {
super.onEnable();
el().enable();
buttonEl.dom.setAttribute("aria-disabled", "false");
}
protected void onFocus(ComponentEvent ce) {
if (!disabled) {
addStyleName(baseStyle + "-focus");
if (GXT.isFocusManagerEnabled() && !GXT.isIE) {
FocusFrame.get().frame(this);
}
}
}
protected void onKeyPress(ButtonEvent be) {
if (be.getEvent().getKeyCode() == KeyCodes.KEY_DOWN) {
if (menu != null && !menu.isVisible()) {
showMenu();
}
}
}
protected void onMenuHide(ComponentEvent ce) {
removeStyleName(baseStyle + "-menu-active");
ButtonEvent be = new ButtonEvent(this);
be.setMenu(menu);
fireEvent(Events.MenuHide, be);
focus();
}
protected void onMenuShow(ComponentEvent ce) {
addStyleName(baseStyle + "-menu-active");
ButtonEvent be = new ButtonEvent(this);
be.setMenu(menu);
fireEvent(Events.MenuShow, be);
if (GXT.isFocusManagerEnabled()) {
if (menu.getItemCount() > 0) {
menu.setActiveItem(menu.getItem(0), false);
}
}
}
protected void onMouseDown(ComponentEvent ce) {
if (handleMouseEvents) {
addStyleName(baseStyle + "-click");
}
}
protected void onMouseOut(ComponentEvent ce) {
removeStyleName(baseStyle + "-click");
removeStyleName(baseStyle + "-over");
}
protected void onMouseOver(ComponentEvent ce) {
if (handleMouseEvents) {
addStyleName(baseStyle + "-over");
preview.add();
}
}
protected void onMouseUp(ComponentEvent ce) {
removeStyleName(baseStyle + "-click");
}
@Override
protected void onRender(Element target, int index) {
if (template == null) {
if (buttonTemplate == null) {
StringBuffer sb = new StringBuffer();
sb.append("<table cellspacing=\"0\" role=\"presentation\"><tbody class=\"{2}\" >");
sb.append("<tr><td class=\"{4}-tl\"><i> </i></td><td class=\"{4}-tc\"></td><td class=\"{4}-tr\"><i> </i></td></tr>");
sb.append("<tr><td class=\"{4}-ml\"><i> </i></td><td class=\"{4}-mc\"><em class=\"{3}\" unselectable=\"on\"><button class=\"{4}-text\" type=\"{1}\" style='position: static'>{0}</button></em></td><td class=\"{4}-mr\"><i> </i></td></tr>");
sb.append("<tr><td class=\"{4}-bl\"><i> </i></td><td class=\"{4}-bc\"></td><td class=\"{4}-br\"><i> </i></td></tr>");
sb.append("</tbody></table>");
buttonTemplate = new Template(sb.toString());
}
template = buttonTemplate;
}
setElement(
template.create((text != null && text.length() > 0) ? text : " ", getType(), baseStyle + "-"
+ scale.name().toLowerCase() + " " + baseStyle + "-icon-" + scale.name().toLowerCase() + "-"
+ iconAlign.name().toLowerCase(), getMenuClass(), baseStyle), target, index);
super.onRender(target, index);
buttonEl = el().selectNode(buttonSelector);
buttonEl.makePositionable();
if (getFocusEl() != null) {
getFocusEl().addEventsSunk(Event.FOCUSEVENTS);
}
preview.getIgnoreList().add(getElement());
buttonEl.setTabIndex(0);
if (GXT.isAriaEnabled()) {
Accessibility.setRole(buttonEl.dom, Accessibility.ROLE_BUTTON);
if (menu != null) {
Accessibility.setState(buttonEl.dom, "aria-haspopup", "true");
addStyleName(baseStyle + "-menu");
}
}
sinkEvents(Event.ONCLICK | Event.MOUSEEVENTS | Event.FOCUSEVENTS | Event.KEYEVENTS);
}
@Override
protected void onResize(int width, int height) {
super.onResize(width, height);
int adj = 0;
if (GXT.isAriaEnabled()) {
adj += text == null ? 10 : 25;
}
buttonEl.setSize(adj + width - 6, height - 6, true);
}
@Override
protected void setAriaState(String stateName, String stateValue) {
Accessibility.setState(buttonEl.dom, stateName, stateValue);
}
}