/* * ToolbarButton.java * * Copyright (C) 2009-12 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.core.client.widget; import com.google.gwt.core.client.GWT; import com.google.gwt.core.client.Scheduler; import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.dom.client.*; import com.google.gwt.dom.client.Style.Display; import com.google.gwt.event.dom.client.*; import com.google.gwt.event.logical.shared.CloseEvent; import com.google.gwt.event.logical.shared.CloseHandler; import com.google.gwt.event.shared.*; import com.google.gwt.resources.client.ImageResource; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; import com.google.gwt.user.client.ui.FocusWidget; import com.google.gwt.user.client.ui.Image; import com.google.gwt.user.client.ui.PopupPanel; import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; import com.google.gwt.user.client.ui.Widget; import org.rstudio.core.client.StringUtil; import org.rstudio.core.client.command.ImageResourceProvider; import org.rstudio.core.client.command.SimpleImageResourceProvider; import org.rstudio.core.client.resources.ImageResource2x; import org.rstudio.core.client.theme.res.ThemeResources; import org.rstudio.core.client.theme.res.ThemeStyles; public class ToolbarButton extends FocusWidget { private class SimpleHasHandlers extends HandlerManager implements HasHandlers { private SimpleHasHandlers() { super(null); } } public <T extends EventHandler> ToolbarButton( String text, ImageResource leftImg, final HandlerManager eventBus, final GwtEvent<? extends T> targetEvent) { this(text, leftImg, new ClickHandler() { public void onClick(ClickEvent event) { eventBus.fireEvent(targetEvent); } }); } public <T extends EventHandler> ToolbarButton( ImageResource img, final HandlerManager eventBus, final GwtEvent<? extends T> targetEvent) { this(null, img, eventBus, targetEvent); } public ToolbarButton(String text, ImageResourceProvider leftImageProvider, ClickHandler clickHandler) { this(text, leftImageProvider, null, clickHandler); } public ToolbarButton(String text, ImageResource leftImage, ClickHandler clickHandler) { this(text, new SimpleImageResourceProvider(leftImage), clickHandler); } public ToolbarButton(ImageResource image, ClickHandler clickHandler) { this(null, image, clickHandler); } public ToolbarButton(ToolbarPopupMenu menu, boolean rightAlignMenu) { this((String)null, new ImageResource2x(ThemeResources.INSTANCE.menuDownArrow2x()), (ImageResource) null, (ClickHandler) null); addMenuHandlers(menu, rightAlignMenu); addStyleName(styles_.toolbarButtonMenu()); addStyleName(styles_.toolbarButtonMenuOnly()); } public ToolbarButton(String text, ImageResource leftImage, ToolbarPopupMenu menu) { this(text, leftImage, menu, false); } public ToolbarButton(String text, ImageResourceProvider leftImage, ToolbarPopupMenu menu) { this(text, leftImage, menu, false); } public ToolbarButton(String text, ImageResource leftImage, ToolbarPopupMenu menu, boolean rightAlignMenu) { this(text, new SimpleImageResourceProvider(leftImage), menu, rightAlignMenu); } public ToolbarButton(String text, ImageResourceProvider leftImage, ToolbarPopupMenu menu, boolean rightAlignMenu) { this(text, leftImage, new ImageResource2x(ThemeResources.INSTANCE.menuDownArrow2x()), null); addMenuHandlers(menu, rightAlignMenu); addStyleName(styles_.toolbarButtonMenu()); } private void addMenuHandlers(final ToolbarPopupMenu popupMenu, final boolean rightAlign) { menu_ = popupMenu; /* * We want clicks on this button to toggle the visibility of the menu, * as well as having the menu auto-hide itself as it normally does. * It's necessary to manually track the visibility (menuShowing) because * in the case where the menu is showing, clicking on this button first * causes the menu to auto-hide and then our mouseDown handler is called * (so we can't rely on menu.isShowing(), it'll always be false by the * time you get into the mousedown handler). */ final boolean[] menuShowing = new boolean[1]; addMouseDownHandler(new MouseDownHandler() { public void onMouseDown(MouseDownEvent event) { event.preventDefault(); event.stopPropagation(); addStyleName(styles_.toolbarButtonPushed()); // Some menus are rebuilt on every invocation. Ask the menu for // the most up-to-date version before proceeding. popupMenu.getDynamicPopupMenu( new ToolbarPopupMenu.DynamicPopupMenuCallback() { @Override public void onPopupMenu(final ToolbarPopupMenu menu) { if (menuShowing[0]) { removeStyleName(styles_.toolbarButtonPushed()); menu.hide(); } else { if (rightAlign) { menu.setPopupPositionAndShow(new PositionCallback() { @Override public void setPosition(int offsetWidth, int offsetHeight) { menu.setPopupPosition( (rightImageWidget_ != null ? rightImageWidget_.getAbsoluteLeft() : leftImageWidget_.getAbsoluteLeft()) + 20 - offsetWidth, ToolbarButton.this.getAbsoluteTop() + ToolbarButton.this.getOffsetHeight()); } }); } else { menu.showRelativeTo(ToolbarButton.this); } menuShowing[0] = true; } } }); } }); popupMenu.addCloseHandler(new CloseHandler<PopupPanel>() { public void onClose(CloseEvent<PopupPanel> popupPanelCloseEvent) { removeStyleName(styles_.toolbarButtonPushed()); Scheduler.get().scheduleDeferred(new ScheduledCommand() { public void execute() { menuShowing[0] = false; } }); } }); } private ToolbarButton(String text, ImageResource leftImage, ImageResource rightImage, ClickHandler clickHandler) { this(text, new SimpleImageResourceProvider(leftImage), rightImage, clickHandler); } public ToolbarButton(String text, Image leftImage, ImageResource rightImage, ClickHandler clickHandler) { super(); setElement(binder.createAndBindUi(this)); this.setStylePrimaryName(styles_.toolbarButton()); this.addStyleName(styles_.handCursor()); setText(text); setInfoText(null); leftImageWidget_ = leftImage == null ? new Image() : leftImage; leftImageWidget_.setStylePrimaryName(styles_.toolbarButtonLeftImage()); leftImageCell_.appendChild(leftImageWidget_.getElement()); if (rightImage != null) { rightImageWidget_ = new Image(rightImage); rightImageWidget_.setStylePrimaryName(styles_.toolbarButtonRightImage()); rightImageCell_.appendChild(rightImageWidget_.getElement()); } if (clickHandler != null) addClickHandler(clickHandler); } public ToolbarButton(String text, ImageResourceProvider leftImage, ImageResource rightImage, ClickHandler clickHandler) { this(text, // extract the supplied left image leftImage != null && leftImage.getImageResource() != null ? new Image(leftImage.getImageResource()) : new Image(), rightImage, clickHandler); // let the image provider know it has a rendered copy if we made one if (leftImage != null && leftImageWidget_ != null) leftImage.addRenderedImage(leftImageWidget_); } @Override public HandlerRegistration addClickHandler(ClickHandler clickHandler) { /* * When we directly subscribe to this widget's ClickEvent, sometimes the * click gets ignored (inconsistent repro but it happens enough to be * annoying). Doing it this way fixes it. */ hasHandlers_.addHandler(ClickEvent.getType(), clickHandler); final HandlerRegistration mouseDown = addMouseDownHandler(new MouseDownHandler() { public void onMouseDown(MouseDownEvent event) { event.preventDefault(); event.stopPropagation(); addStyleName(styles_.toolbarButtonPushed()); down_ = true; } }); final HandlerRegistration mouseOut = addMouseOutHandler(new MouseOutHandler() { public void onMouseOut(MouseOutEvent event) { event.preventDefault(); event.stopPropagation(); removeStyleName(styles_.toolbarButtonPushed()); down_ = false; } }); final HandlerRegistration mouseUp = addMouseUpHandler(new MouseUpHandler() { public void onMouseUp(MouseUpEvent event) { event.preventDefault(); event.stopPropagation(); if (down_) { down_ = false; removeStyleName(styles_.toolbarButtonPushed()); NativeEvent clickEvent = Document.get().createClickEvent( 1, event.getScreenX(), event.getScreenY(), event.getClientX(), event.getClientY(), event.getNativeEvent().getCtrlKey(), event.getNativeEvent().getAltKey(), event.getNativeEvent().getShiftKey(), event.getNativeEvent().getMetaKey()); DomEvent.fireNativeEvent(clickEvent, hasHandlers_); } } }); return new HandlerRegistration() { public void removeHandler() { mouseDown.removeHandler(); mouseOut.removeHandler(); mouseUp.removeHandler(); } }; } public void click() { NativeEvent clickEvent = Document.get().createClickEvent( 1, 0, 0, 0, 0, false, false, false, false); DomEvent.fireNativeEvent(clickEvent, hasHandlers_); } protected Toolbar getParentToolbar() { Widget parent = getParent(); while (parent != null) { if (parent instanceof Toolbar) return (Toolbar) parent; parent = parent.getParent(); } return null; } public ToolbarPopupMenu getMenu() { return menu_; } public void setLeftImage(ImageResource imageResource) { leftImageWidget_.setResource(imageResource); } public void setText(String label) { if (!StringUtil.isNullOrEmpty(label)) { label_.setInnerText(label); label_.getStyle().setDisplay(Display.BLOCK); removeStyleName(styles_.noLabel()); } else { label_.getStyle().setDisplay(Display.NONE); addStyleName(styles_.noLabel()); } } public void setInfoText(String infoText) { if (!StringUtil.isNullOrEmpty(infoText)) { infoLabel_.setInnerText(infoText); infoLabel_.getStyle().setDisplay(Display.BLOCK); } else { infoLabel_.getStyle().setDisplay(Display.NONE); } } public String getText() { return StringUtil.notNull(label_.getInnerText()); } private boolean down_; private SimpleHasHandlers hasHandlers_ = new SimpleHasHandlers(); interface Binder extends UiBinder<Element, ToolbarButton> { } private ToolbarPopupMenu menu_; private static final Binder binder = GWT.create(Binder.class); private static final ThemeStyles styles_ = ThemeResources.INSTANCE.themeStyles(); @UiField TableCellElement leftImageCell_; @UiField TableCellElement rightImageCell_; @UiField DivElement label_; @UiField DivElement infoLabel_; private Image leftImageWidget_; private Image rightImageWidget_; }