1 /* 2 * ToolbarButton.java 3 * 4 * Copyright (C) 2021 by RStudio, PBC 5 * 6 * Unless you have received this program directly from RStudio pursuant 7 * to the terms of a commercial license agreement with RStudio, then 8 * this program is licensed to you under the terms of version 3 of the 9 * GNU Affero General Public License. This program is distributed WITHOUT 10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, 11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the 12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. 13 * 14 */ 15 package org.rstudio.core.client.widget; 16 17 import com.google.gwt.aria.client.Roles; 18 import com.google.gwt.core.client.GWT; 19 import com.google.gwt.dom.client.*; 20 import com.google.gwt.dom.client.Style.Display; 21 import com.google.gwt.event.dom.client.*; 22 import com.google.gwt.event.shared.*; 23 import com.google.gwt.resources.client.ImageResource; 24 import com.google.gwt.uibinder.client.UiBinder; 25 import com.google.gwt.uibinder.client.UiField; 26 import com.google.gwt.user.client.ui.FocusWidget; 27 import com.google.gwt.user.client.ui.Widget; 28 import org.rstudio.core.client.ClassIds; 29 import org.rstudio.core.client.StringUtil; 30 import org.rstudio.core.client.command.ImageResourceProvider; 31 import org.rstudio.core.client.command.SimpleImageResourceProvider; 32 import org.rstudio.core.client.theme.res.ThemeResources; 33 import org.rstudio.core.client.theme.res.ThemeStyles; 34 35 public class ToolbarButton extends FocusWidget 36 { 37 // button with no visible text 38 public static String NoText = null; 39 40 // button with no tooltip/accessibility text 41 public static String NoTitle = null; 42 43 private class SimpleHasHandlers extends HandlerManager implements HasHandlers 44 { SimpleHasHandlers()45 private SimpleHasHandlers() 46 { 47 super(null); 48 } 49 } 50 ToolbarButton( String text, String title, ImageResource leftImg, final HandlerManager eventBus, final GwtEvent<? extends T> targetEvent)51 public <T extends EventHandler> ToolbarButton( 52 String text, 53 String title, 54 ImageResource leftImg, 55 final HandlerManager eventBus, 56 final GwtEvent<? extends T> targetEvent) 57 { 58 this(text, title, leftImg, event -> eventBus.fireEvent(targetEvent)); 59 } 60 ToolbarButton(String text, String title, ImageResourceProvider leftImageProvider, ClickHandler clickHandler)61 public ToolbarButton(String text, 62 String title, 63 ImageResourceProvider leftImageProvider, 64 ClickHandler clickHandler) 65 { 66 this(text, title, leftImageProvider, null, clickHandler); 67 } 68 ToolbarButton(String text, String title, ImageResource leftImage)69 public ToolbarButton(String text, 70 String title, 71 ImageResource leftImage) 72 { 73 this(text, title, new SimpleImageResourceProvider(leftImage), null); 74 } 75 ToolbarButton(String text, String title, ImageResource leftImage, ClickHandler clickHandler)76 public ToolbarButton(String text, 77 String title, 78 ImageResource leftImage, 79 ClickHandler clickHandler) 80 { 81 this(text, title, new SimpleImageResourceProvider(leftImage), clickHandler); 82 } 83 ToolbarButton(String text, String title, ImageResource leftImage, ImageResource rightImage, ClickHandler clickHandler)84 public ToolbarButton(String text, 85 String title, 86 ImageResource leftImage, 87 ImageResource rightImage, 88 ClickHandler clickHandler) 89 { 90 this(text, 91 title, 92 new SimpleImageResourceProvider(leftImage), 93 rightImage, 94 clickHandler); 95 } 96 ToolbarButton(String text, String title, DecorativeImage leftImage, ImageResource rightImage, ClickHandler clickHandler)97 public ToolbarButton(String text, // visible text 98 String title, // a11y / tooltip text 99 DecorativeImage leftImage, 100 ImageResource rightImage, 101 ClickHandler clickHandler) 102 { 103 super(); 104 105 setElement(binder.createAndBindUi(this)); 106 setClassId(null); 107 108 this.setStylePrimaryName(styles_.toolbarButton()); 109 this.addStyleName(styles_.handCursor()); 110 111 setText(text); 112 setTitle(title); 113 setInfoText(null); 114 leftImageWidget_ = leftImage == null ? new DecorativeImage() : leftImage; 115 leftImageWidget_.setStylePrimaryName(styles_.toolbarButtonLeftImage()); 116 leftImageCell_.appendChild(leftImageWidget_.getElement()); 117 if (rightImage != null) 118 { 119 rightImageWidget_ = new DecorativeImage(rightImage); 120 rightImageWidget_.setStylePrimaryName(styles_.toolbarButtonRightImage()); 121 rightImageCell_.appendChild(rightImageWidget_.getElement()); 122 } 123 124 if (clickHandler != null) 125 addClickHandler(clickHandler); 126 127 Roles.getPresentationRole().set(wrapper_); 128 } 129 ToolbarButton(String text, String title, ImageResourceProvider leftImage, ImageResource rightImage, ClickHandler clickHandler)130 public ToolbarButton(String text, 131 String title, 132 ImageResourceProvider leftImage, 133 ImageResource rightImage, 134 ClickHandler clickHandler) 135 { 136 this(text, 137 title, 138 // extract the supplied left image 139 leftImage != null && leftImage.getImageResource() != null ? 140 new DecorativeImage(leftImage.getImageResource()) : 141 new DecorativeImage(), 142 rightImage, 143 clickHandler); 144 145 // let the image provider know it has a rendered copy if we made one 146 if (leftImage != null && leftImageWidget_ != null) 147 leftImage.addRenderedImage(leftImageWidget_); 148 } 149 150 @Override addClickHandler(ClickHandler clickHandler)151 public HandlerRegistration addClickHandler(ClickHandler clickHandler) 152 { 153 /* 154 * When we directly subscribe to this widget's ClickEvent, sometimes the 155 * click gets ignored (inconsistent repro but it happens enough to be 156 * annoying). Doing it this way fixes it. 157 */ 158 159 hasHandlers_.addHandler(ClickEvent.getType(), clickHandler); 160 161 final HandlerRegistration mouseDown = addMouseDownHandler(event -> 162 { 163 event.preventDefault(); 164 event.stopPropagation(); 165 166 addStyleName(styles_.toolbarButtonPushed()); 167 down_ = true; 168 }); 169 170 final HandlerRegistration mouseOut = addMouseOutHandler(event -> 171 { 172 event.preventDefault(); 173 event.stopPropagation(); 174 175 removeStyleName(styles_.toolbarButtonPushed()); 176 down_ = false; 177 }); 178 179 final HandlerRegistration mouseUp = addMouseUpHandler(event -> 180 { 181 event.preventDefault(); 182 event.stopPropagation(); 183 184 if (down_) 185 { 186 down_ = false; 187 removeStyleName(styles_.toolbarButtonPushed()); 188 189 NativeEvent clickEvent = Document.get().createClickEvent( 190 1, 191 event.getScreenX(), 192 event.getScreenY(), 193 event.getClientX(), 194 event.getClientY(), 195 event.getNativeEvent().getCtrlKey(), 196 event.getNativeEvent().getAltKey(), 197 event.getNativeEvent().getShiftKey(), 198 event.getNativeEvent().getMetaKey()); 199 DomEvent.fireNativeEvent(clickEvent, hasHandlers_); 200 } 201 }); 202 203 final HandlerRegistration keyPress = addKeyPressHandler(event -> 204 { 205 char charCode = event.getCharCode(); 206 if (charCode == KeyCodes.KEY_ENTER || charCode == KeyCodes.KEY_SPACE) 207 { 208 event.preventDefault(); 209 event.stopPropagation(); 210 click(); 211 } 212 }); 213 214 return () -> 215 { 216 mouseDown.removeHandler(); 217 mouseOut.removeHandler(); 218 mouseUp.removeHandler(); 219 keyPress.removeHandler(); 220 }; 221 } 222 click()223 public void click() 224 { 225 NativeEvent clickEvent = Document.get().createClickEvent( 226 1, 227 0, 228 0, 229 0, 230 0, 231 false, 232 false, 233 false, 234 false); 235 DomEvent.fireNativeEvent(clickEvent, hasHandlers_); 236 } 237 getParentToolbar()238 protected Toolbar getParentToolbar() 239 { 240 Widget parent = getParent(); 241 while (parent != null) 242 { 243 if (parent instanceof Toolbar) 244 return (Toolbar) parent; 245 parent = parent.getParent(); 246 } 247 248 return null; 249 } 250 setLeftImage(ImageResource imageResource)251 public void setLeftImage(ImageResource imageResource) 252 { 253 leftImageWidget_.setResource(imageResource); 254 } 255 setClassId(String name)256 public void setClassId(String name) 257 { 258 if (!StringUtil.isNullOrEmpty(displayClassId_)) 259 ClassIds.removeClassId(getElement(), displayClassId_); 260 261 displayClassId_ = ClassIds.TOOLBAR_BTN + "_" + ClassIds.idSafeString(getTitle()); 262 if (!StringUtil.isNullOrEmpty(name)) 263 displayClassId_ += "_" + ClassIds.idSafeString(name); 264 265 ClassIds.assignClassId(getElement(), displayClassId_); 266 } 267 setText(boolean visible, String text)268 public void setText(boolean visible, String text) 269 { 270 if (visible) 271 { 272 setText(text); 273 Roles.getButtonRole().setAriaLabelProperty(getElement(), ""); 274 } 275 else 276 { 277 setText(""); 278 Roles.getButtonRole().setAriaLabelProperty(getElement(), text); 279 } 280 } 281 setText(String label)282 public void setText(String label) 283 { 284 if (!StringUtil.isNullOrEmpty(label)) 285 { 286 label_.setInnerText(label); 287 label_.getStyle().setDisplay(Display.BLOCK); 288 removeStyleName(styles_.noLabel()); 289 } 290 else 291 { 292 label_.getStyle().setDisplay(Display.NONE); 293 addStyleName(styles_.noLabel()); 294 } 295 } 296 hasLabel()297 public boolean hasLabel() 298 { 299 return !getStyleName().contains(styles_.noLabel()); 300 } 301 setInfoText(String infoText)302 public void setInfoText(String infoText) 303 { 304 if (!StringUtil.isNullOrEmpty(infoText)) 305 { 306 infoLabel_.setInnerText(infoText); 307 infoLabel_.getStyle().setDisplay(Display.BLOCK); 308 } 309 else 310 { 311 infoLabel_.getStyle().setDisplay(Display.NONE); 312 } 313 } 314 getText()315 public String getText() 316 { 317 return StringUtil.notNull(label_.getInnerText()); 318 } 319 setTitle(String title)320 public void setTitle(String title) 321 { 322 super.setTitle(title); 323 if (!StringUtil.isNullOrEmpty(title)) 324 Roles.getButtonRole().setAriaLabelProperty(getElement(), title); 325 } 326 327 // Class name displayed by Help / Diagnostics / Show DOM Elements command. A default value is 328 // set in the constructor, but this can be updated to be more specific. 329 private String displayClassId_; 330 331 private boolean down_; 332 333 private final SimpleHasHandlers hasHandlers_ = new SimpleHasHandlers(); 334 335 interface Binder extends UiBinder<Element, ToolbarButton> { } 336 337 private static final Binder binder = GWT.create(Binder.class); 338 339 protected static final ThemeStyles styles_ = ThemeResources.INSTANCE.themeStyles(); 340 341 @UiField 342 TableCellElement leftImageCell_; 343 @UiField 344 TableCellElement rightImageCell_; 345 @UiField 346 DivElement label_; 347 @UiField 348 DivElement infoLabel_; 349 @UiField 350 TableElement wrapper_; 351 protected DecorativeImage leftImageWidget_; 352 protected DecorativeImage rightImageWidget_; 353 } 354