1 /* 2 * DataImportFileChooser.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 16 package org.rstudio.studio.client.workbench.views.environment.dataimport; 17 18 import com.google.gwt.aria.client.Roles; 19 import org.rstudio.core.client.StringUtil; 20 import org.rstudio.core.client.files.FileSystemItem; 21 import org.rstudio.core.client.widget.CanSetControlId; 22 import org.rstudio.core.client.widget.Operation; 23 import org.rstudio.core.client.widget.ProgressIndicator; 24 import org.rstudio.core.client.widget.ProgressOperationWithInput; 25 import org.rstudio.core.client.widget.ThemedButton; 26 import org.rstudio.studio.client.RStudioGinjector; 27 import org.rstudio.studio.client.workbench.WorkbenchContext; 28 29 import com.google.gwt.core.client.GWT; 30 import com.google.gwt.dom.client.Style.Unit; 31 import com.google.gwt.uibinder.client.UiBinder; 32 import com.google.gwt.uibinder.client.UiField; 33 import com.google.gwt.user.client.Timer; 34 import com.google.gwt.user.client.ui.Composite; 35 import com.google.gwt.user.client.ui.TextBox; 36 import com.google.gwt.user.client.ui.Widget; 37 import com.google.inject.Inject; 38 39 public class DataImportFileChooser extends Composite 40 implements CanSetControlId 41 { DataImportFileChooser(Operation updateOperation, boolean growTextbox)42 public DataImportFileChooser(Operation updateOperation, boolean growTextbox) 43 { 44 RStudioGinjector.INSTANCE.injectMembers(this); 45 46 initWidget(uiBinder.createAndBindUi(this)); 47 48 updateOperation_ = updateOperation; 49 50 if (growTextbox) 51 { 52 locationTextBox_.getElement().getStyle().setHeight(22, Unit.PX); 53 locationTextBox_.getElement().getStyle().setMarginTop(0, Unit.PX); 54 } 55 56 locationTextBox_.addValueChangeHandler(stringValueChangeEvent -> 57 { 58 }); 59 60 actionButton_.addClickHandler(event -> 61 { 62 if (updateMode_) 63 { 64 updateOperation_.execute(); 65 } 66 else 67 { 68 FileSystemItem fileSystemItemPath = FileSystemItem.createFile(getText()); 69 if (getText() == "") { 70 fileSystemItemPath = workbenchContext_.getDefaultFileDialogDir(); 71 } 72 73 RStudioGinjector.INSTANCE.getFileDialogs().openFile( 74 "Choose File", 75 RStudioGinjector.INSTANCE.getRemoteFileSystemContext(), 76 fileSystemItemPath, 77 new ProgressOperationWithInput<FileSystemItem>() 78 { 79 public void execute(FileSystemItem input, 80 ProgressIndicator indicator) 81 { 82 if (input == null) 83 return; 84 85 locationTextBox_.setText(input.getPath()); 86 preventModeChange(); 87 88 indicator.onCompleted(); 89 90 updateOperation_.execute(); 91 } 92 }); 93 } 94 }); 95 96 checkForTextBoxChange(); 97 } 98 99 @Inject initialize(WorkbenchContext workbenchContext)100 private void initialize(WorkbenchContext workbenchContext) 101 { 102 workbenchContext_ = workbenchContext; 103 } 104 setEnabled(boolean enabled)105 public void setEnabled(boolean enabled) 106 { 107 locationTextBox_.setEnabled(enabled); 108 actionButton_.setEnabled(enabled); 109 } 110 getText()111 public String getText() 112 { 113 return locationTextBox_.getText(); 114 } 115 116 @Override onDetach()117 public void onDetach() 118 { 119 checkTextBoxInterval_ = 0; 120 } 121 setFocus()122 public void setFocus() 123 { 124 locationTextBox_.setFocus(true); 125 } 126 127 @UiField 128 TextBox locationTextBox_; 129 130 @UiField 131 ThemedButton actionButton_; 132 checkForTextBoxChange()133 private void checkForTextBoxChange() 134 { 135 if (checkTextBoxInterval_ == 0) 136 return; 137 138 // Check continuously for changes in the textbox to reliably detect changes even when OS pastes text 139 new Timer() 140 { 141 @Override 142 public void run() 143 { 144 if (lastTextBoxValue_ != null && locationTextBox_.getText() != lastTextBoxValue_) 145 { 146 switchToUpdateMode(!locationTextBox_.getText().isEmpty()); 147 } 148 149 lastTextBoxValue_ = locationTextBox_.getText(); 150 checkForTextBoxChange(); 151 } 152 }.schedule(checkTextBoxInterval_); 153 } 154 preventModeChange()155 private void preventModeChange() 156 { 157 lastTextBoxValue_ = locationTextBox_.getText(); 158 } 159 switchToUpdateMode(Boolean updateMode)160 public void switchToUpdateMode(Boolean updateMode) 161 { 162 if (updateMode_ != updateMode) 163 { 164 updateMode_ = updateMode; 165 if (updateMode) 166 { 167 actionButton_.setText(updateModeCaption_); 168 } 169 else 170 { 171 actionButton_.setText(browseModeCaption_ + "..."); 172 } 173 updateButtonAriaLabel(); 174 } 175 } 176 177 /** 178 * @param suffix aria-label for the button to provide additional context to 179 * screen reader users; applied as a suffix to the visible 180 * button text, e.g. "Browse..." becomes "Browse for File/URL..." 181 */ setAriaLabelSuffix(String suffix)182 public void setAriaLabelSuffix(String suffix) 183 { 184 ariaLabelSuffix_ = suffix; 185 updateButtonAriaLabel(); 186 } 187 updateButtonAriaLabel()188 public void updateButtonAriaLabel() 189 { 190 if (StringUtil.isNullOrEmpty(ariaLabelSuffix_)) 191 { 192 Roles.getButtonRole().setAriaLabelProperty(actionButton_.getElement(), ""); 193 return; 194 } 195 196 final String prefix = updateMode_ ? updateModeCaption_ : browseModeCaption_ + " for"; 197 final String finalSuffix = updateMode_ ? "" : "..."; 198 Roles.getButtonRole().setAriaLabelProperty(actionButton_.getElement(), 199 prefix + " " + ariaLabelSuffix_ + finalSuffix); 200 } 201 202 @Override setElementId(String id)203 public void setElementId(String id) 204 { 205 locationTextBox_.getElement().setId(id); 206 } 207 208 private static final String browseModeCaption_ = "Browse"; 209 private static final String updateModeCaption_ = "Update"; 210 private boolean updateMode_ = false; 211 private String lastTextBoxValue_; 212 private int checkTextBoxInterval_ = 250; 213 private final Operation updateOperation_; 214 private String ariaLabelSuffix_; 215 216 private static DataImportFileChooserUiBinder uiBinder = GWT.create(DataImportFileChooserUiBinder.class); 217 interface DataImportFileChooserUiBinder extends UiBinder<Widget, DataImportFileChooser> {} 218 219 private WorkbenchContext workbenchContext_; 220 } 221