1 /* 2 * PackagesPreferencesPane.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 17 package org.rstudio.studio.client.workbench.prefs.views; 18 19 import com.google.gwt.dom.client.Style.Unit; 20 import com.google.gwt.core.client.JsArray; 21 import com.google.gwt.event.dom.client.ClickHandler; 22 import com.google.gwt.resources.client.ImageResource; 23 import com.google.gwt.user.client.ui.CheckBox; 24 import com.google.gwt.user.client.ui.HorizontalPanel; 25 import com.google.inject.Inject; 26 27 import java.util.ArrayList; 28 29 import org.rstudio.core.client.BrowseCap; 30 import org.rstudio.core.client.Debug; 31 import org.rstudio.core.client.ElementIds; 32 import org.rstudio.core.client.prefs.RestartRequirement; 33 import org.rstudio.core.client.resources.ImageResource2x; 34 import org.rstudio.core.client.theme.DialogTabLayoutPanel; 35 import org.rstudio.core.client.theme.VerticalTabPanel; 36 import org.rstudio.core.client.widget.FormLabel; 37 import org.rstudio.core.client.widget.InfoBar; 38 import org.rstudio.core.client.widget.MessageDialog; 39 import org.rstudio.core.client.widget.TextBoxWithButton; 40 import org.rstudio.studio.client.common.GlobalDisplay; 41 import org.rstudio.studio.client.common.HelpLink; 42 import org.rstudio.studio.client.common.PackagesHelpLink; 43 import org.rstudio.studio.client.common.SimpleRequestCallback; 44 import org.rstudio.studio.client.common.mirrors.DefaultCRANMirror; 45 import org.rstudio.studio.client.common.mirrors.model.CRANMirror; 46 import org.rstudio.studio.client.common.mirrors.model.MirrorsServerOperations; 47 import org.rstudio.studio.client.common.repos.SecondaryReposWidget; 48 import org.rstudio.studio.client.server.ServerError; 49 import org.rstudio.studio.client.workbench.model.Session; 50 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs; 51 52 public class PackagesPreferencesPane extends PreferencesPane 53 { 54 @Inject PackagesPreferencesPane(PreferencesDialogResources res, GlobalDisplay globalDisplay, UserPrefs uiPrefs, Session session, final DefaultCRANMirror defaultCRANMirror, MirrorsServerOperations server)55 public PackagesPreferencesPane(PreferencesDialogResources res, 56 GlobalDisplay globalDisplay, 57 UserPrefs uiPrefs, 58 Session session, 59 final DefaultCRANMirror defaultCRANMirror, 60 MirrorsServerOperations server) 61 { 62 res_ = res; 63 globalDisplay_ = globalDisplay; 64 server_ = server; 65 66 secondaryReposWidget_ = new SecondaryReposWidget(); 67 68 VerticalTabPanel management = new VerticalTabPanel(ElementIds.PACKAGE_MANAGEMENT_PREFS); 69 VerticalTabPanel development = new VerticalTabPanel(ElementIds.PACKAGE_DEVELOPMENT_PREFS); 70 71 management.add(headerLabel("Package Management")); 72 73 infoBar_ = new InfoBar(InfoBar.WARNING); 74 infoBar_.setText("CRAN repositories modified outside package preferences."); 75 infoBar_.addStyleName(res_.styles().themeInfobar()); 76 spaced(infoBar_); 77 78 ClickHandler selectPrimaryRepo = (clickEvent) -> 79 { 80 defaultCRANMirror.choose(cranMirror -> 81 { 82 cranMirror_ = cranMirror; 83 cranMirrorTextBox_.setText(cranMirror_.getDisplay()); 84 85 secondaryReposWidget_.setCranRepoUrl( 86 cranMirror_.getURL(), 87 cranMirror_.isCustom() 88 ); 89 }); 90 }; 91 92 cranMirrorTextBox_ = new TextBoxWithButton( 93 "Primary CRAN repository:", 94 "", 95 "Change...", 96 null, 97 ElementIds.TextBoxButtonId.PRIMARY_CRAN, 98 true, 99 selectPrimaryRepo); 100 101 cranMirrorTextBox_.getTextBox().addValueChangeHandler(event -> 102 { 103 if (!event.getValue().equals(cranMirror_.getDisplay())) 104 { 105 secondaryReposWidget_.setCranRepoUrl(event.getValue(), true); 106 } 107 }); 108 109 nudgeRight(cranMirrorTextBox_); 110 textBoxWithChooser(cranMirrorTextBox_); 111 cranMirrorTextBox_.setText(""); 112 113 if (session.getSessionInfo().getAllowCRANReposEdit()) 114 { 115 management.add(infoBar_); 116 117 lessSpaced(cranMirrorTextBox_); 118 management.add(cranMirrorTextBox_); 119 120 FormLabel secondaryReposLabel = new FormLabel( 121 "Secondary repositories:", 122 secondaryReposWidget_.getLabeledWidget()); 123 secondaryReposLabel.getElement().getStyle().setMarginLeft(2, Unit.PX); 124 secondaryReposLabel.getElement().getStyle().setMarginBottom(2, Unit.PX); 125 126 management.add(spacedBefore(secondaryReposLabel)); 127 management.add(secondaryReposWidget_); 128 } 129 130 CheckBox chkEnablePackages = checkboxPref("Enable packages pane", 131 uiPrefs.packagesPaneEnabled()); 132 133 chkEnablePackages.addValueChangeHandler(event -> reloadRequired_ = true); 134 135 if (!session.getSessionInfo().getDisablePackages()) 136 { 137 management.add(spacedBefore(chkEnablePackages)); 138 } 139 140 useSecurePackageDownload_ = new CheckBox( 141 "Use secure download method for HTTP"); 142 HorizontalPanel secureDownloadPanel = checkBoxWithHelp( 143 useSecurePackageDownload_, "secure_download", "Help on secure package downloads for R"); 144 lessSpaced(secureDownloadPanel); 145 management.add(secureDownloadPanel); 146 147 useInternet2_ = new CheckBox( 148 "Use Internet Explorer library/proxy for HTTP", 149 true); 150 if (BrowseCap.isWindowsDesktop()) 151 { 152 lessSpaced(chkEnablePackages); 153 spaced(useInternet2_); 154 management.add(useInternet2_); 155 } 156 else 157 { 158 spaced(useSecurePackageDownload_); 159 useSecurePackageDownload_.getElement().getStyle().setMarginBottom(12, Unit.PX); 160 } 161 162 management.add(spacedBefore(new HelpLink("Managing Packages", "managing_packages"))); 163 164 development.add(headerLabel("Package Development")); 165 166 useDevtools_ = new CheckBox("Use devtools package functions if available"); 167 lessSpaced(useDevtools_); 168 development.add(useDevtools_); 169 170 development.add(checkboxPref("Save all files prior to building packages", uiPrefs.saveFilesBeforeBuild())); 171 development.add(checkboxPref("Automatically navigate editor to build errors", uiPrefs.navigateToBuildError())); 172 173 hideObjectFiles_ = new CheckBox("Hide object files in package src directory"); 174 lessSpaced(hideObjectFiles_); 175 development.add(hideObjectFiles_); 176 177 cleanupAfterCheckSuccess_ = new CheckBox("Cleanup output after successful R CMD check"); 178 lessSpaced(cleanupAfterCheckSuccess_); 179 development.add(cleanupAfterCheckSuccess_); 180 181 viewDirAfterCheckFailure_ = new CheckBox("View Rcheck directory after failed R CMD check"); 182 lessSpaced(viewDirAfterCheckFailure_); 183 development.add(viewDirAfterCheckFailure_); 184 185 development.add(checkboxPref("Use Rcpp template when creating C++ files", uiPrefs.useRcppTemplate())); 186 187 useNewlineInMakefiles_ = new CheckBox("Always use LF line-endings in Unix Makefiles"); 188 lessSpaced(useNewlineInMakefiles_); 189 development.add(useNewlineInMakefiles_); 190 191 HelpLink packagesHelpLink = new PackagesHelpLink(); 192 packagesHelpLink.getElement().getStyle().setMarginTop(12, Unit.PX); 193 nudgeRight(packagesHelpLink); 194 development.add(packagesHelpLink); 195 196 cranMirrorTextBox_.setEnabled(false); 197 useInternet2_.setEnabled(false); 198 cleanupAfterCheckSuccess_.setEnabled(false); 199 viewDirAfterCheckFailure_.setEnabled(false); 200 hideObjectFiles_.setEnabled(false); 201 useDevtools_.setEnabled(false); 202 useSecurePackageDownload_.setEnabled(false); 203 204 DialogTabLayoutPanel tabPanel = new DialogTabLayoutPanel("Packages"); 205 tabPanel.setSize("435px", "533px"); 206 tabPanel.add(management, "Management", management.getBasePanelId()); 207 tabPanel.add(development, "Development", development.getBasePanelId()); 208 tabPanel.selectTab(0); 209 add(tabPanel); 210 } 211 212 @Override getIcon()213 public ImageResource getIcon() 214 { 215 return new ImageResource2x(res_.iconPackages2x()); 216 } 217 218 @Override validate()219 public boolean validate() 220 { 221 return true; 222 } 223 224 @Override getName()225 public String getName() 226 { 227 return "Packages"; 228 } 229 230 @Override initialize(UserPrefs prefs)231 protected void initialize(UserPrefs prefs) 232 { 233 cranMirrorTextBox_.setEnabled(true); 234 CRANMirror mirror = prefs.cranMirror().getValue().cast(); 235 if (!mirror.isEmpty()) 236 { 237 cranMirror_ = mirror; 238 239 secondaryReposWidget_.setCranRepoUrl( 240 cranMirror_.getURL(), 241 cranMirror_.isCustom() 242 ); 243 244 cranMirrorTextBox_.setText(cranMirror_.getDisplay()); 245 246 cranMirrorStored_ = cranMirrorTextBox_.getTextBox().getText(); 247 248 secondaryReposWidget_.setRepos(cranMirror_.getSecondaryRepos()); 249 } 250 251 useInternet2_.setEnabled(true); 252 useInternet2_.setValue(prefs.useInternet2().getValue()); 253 useInternet2_.addValueChangeHandler(event -> globalDisplay_.showMessage( 254 MessageDialog.INFO, 255 "Restart R Required", 256 "You must restart your R session for this setting " + 257 "to take effect.") 258 ); 259 260 cleanupAfterCheckSuccess_.setEnabled(true); 261 cleanupAfterCheckSuccess_.setValue(prefs.cleanupAfterRCmdCheck().getValue()); 262 263 viewDirAfterCheckFailure_.setEnabled(true); 264 viewDirAfterCheckFailure_.setValue(prefs.viewDirAfterRCmdCheck().getValue()); 265 266 hideObjectFiles_.setEnabled(true); 267 hideObjectFiles_.setValue(prefs.hideObjectFiles().getValue()); 268 269 useDevtools_.setEnabled(true); 270 useDevtools_.setValue(prefs.useDevtools().getValue()); 271 272 useSecurePackageDownload_.setEnabled(true); 273 useSecurePackageDownload_.setValue(prefs.useSecureDownload().getValue()); 274 275 useNewlineInMakefiles_.setEnabled(true); 276 useNewlineInMakefiles_.setValue(prefs.useNewlinesInMakefiles().getValue()); 277 278 server_.getCRANActives( 279 new SimpleRequestCallback<JsArray<CRANMirror>>() { 280 @Override 281 public void onResponseReceived(JsArray<CRANMirror> mirrors) 282 { 283 boolean cranDiffers = false; 284 285 ArrayList<CRANMirror> secondary = cranMirror_.getSecondaryRepos(); 286 287 if (secondary.size() + 1 != mirrors.length() || mirrors.length() == 0) 288 { 289 cranDiffers = true; 290 } 291 else 292 { 293 // First entry should always be CRAN when set by preferences 294 if (!mirrors.get(0).getName().equals("CRAN") || 295 !mirrors.get(0).getURL().equals(cranMirror_.getURL())) { 296 cranDiffers = true; 297 } 298 for(int i=1; i<mirrors.length(); i++) 299 { 300 if (!mirrors.get(i).getName().equals(secondary.get(i-1).getName()) || 301 !mirrors.get(i).getURL().equals(secondary.get(i-1).getURL())) 302 { 303 cranDiffers = true; 304 break; 305 } 306 } 307 } 308 309 if (cranDiffers) 310 { 311 infoBar_.addStyleName(res_.styles().themeInfobarShowing()); 312 } 313 } 314 315 @Override 316 public void onError(ServerError error) 317 { 318 Debug.logError(error); 319 } 320 } 321 ); 322 } 323 secondaryReposHasChanged()324 private boolean secondaryReposHasChanged() 325 { 326 ArrayList<CRANMirror> secondaryRepos = secondaryReposWidget_.getRepos(); 327 328 if (secondaryRepos.size() != cranMirror_.getSecondaryRepos().size()) 329 return true; 330 331 for (int i = 0; i < secondaryRepos.size(); i++) 332 { 333 if (secondaryRepos.get(i).getSecondary() != cranMirror_.getSecondaryRepos().get(i).getSecondary()) 334 return true; 335 } 336 337 return false; 338 } 339 340 @Override onApply(UserPrefs prefs)341 public RestartRequirement onApply(UserPrefs prefs) 342 { 343 RestartRequirement restartRequirement = super.onApply(prefs); 344 345 if (reloadRequired_) 346 restartRequirement.setUiReloadRequired(true); 347 348 String mirrorTextValue = cranMirrorTextBox_.getTextBox().getText(); 349 350 boolean cranRepoChanged = !mirrorTextValue.equals(cranMirrorStored_); 351 boolean cranRepoChangedToUrl = cranRepoChanged && 352 mirrorTextValue.startsWith("http"); 353 354 if (cranRepoChanged || secondaryReposHasChanged()) 355 { 356 if (cranRepoChangedToUrl) 357 { 358 cranMirror_.setURL(mirrorTextValue); 359 360 cranMirror_.setAsCustom(); 361 } 362 } 363 364 ArrayList<CRANMirror> repos = secondaryReposWidget_.getRepos(); 365 cranMirror_.setSecondaryRepos(repos); 366 367 prefs.cranMirror().setGlobalValue(cranMirror_); 368 prefs.useInternet2().setGlobalValue(useInternet2_.getValue()); 369 prefs.cleanupAfterRCmdCheck().setGlobalValue(cleanupAfterCheckSuccess_.getValue()); 370 prefs.viewDirAfterRCmdCheck().setGlobalValue(viewDirAfterCheckFailure_.getValue()); 371 prefs.hideObjectFiles().setGlobalValue(hideObjectFiles_.getValue()); 372 prefs.useDevtools().setGlobalValue(useDevtools_.getValue()); 373 prefs.useSecureDownload().setGlobalValue(useSecurePackageDownload_.getValue()); 374 prefs.useNewlinesInMakefiles().setGlobalValue(useNewlineInMakefiles_.getValue()); 375 prefs.cranMirror().setGlobalValue(cranMirror_); 376 377 return restartRequirement; 378 } 379 380 private final PreferencesDialogResources res_; 381 private final GlobalDisplay globalDisplay_; 382 private final MirrorsServerOperations server_; 383 private final InfoBar infoBar_; 384 385 private CRANMirror cranMirror_ = CRANMirror.empty(); 386 private final CheckBox useInternet2_; 387 private TextBoxWithButton cranMirrorTextBox_; 388 private final CheckBox cleanupAfterCheckSuccess_; 389 private final CheckBox viewDirAfterCheckFailure_; 390 private final CheckBox hideObjectFiles_; 391 private final CheckBox useDevtools_; 392 private final CheckBox useSecurePackageDownload_; 393 private final CheckBox useNewlineInMakefiles_; 394 private boolean reloadRequired_ = false; 395 private String cranMirrorStored_; 396 private final SecondaryReposWidget secondaryReposWidget_; 397 } 398