1 /*
2  * GeneralPreferencesPane.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.studio.client.workbench.prefs.views;
16 
17 import com.google.gwt.event.dom.client.ChangeEvent;
18 import com.google.gwt.event.dom.client.ClickEvent;
19 import com.google.gwt.event.dom.client.ClickHandler;
20 
21 import java.util.HashMap;
22 import java.util.Map;
23 
24 import org.rstudio.core.client.BrowseCap;
25 import org.rstudio.core.client.ElementIds;
26 import org.rstudio.core.client.StringUtil;
27 import org.rstudio.core.client.files.FileSystemContext;
28 import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
29 import org.rstudio.core.client.prefs.RestartRequirement;
30 import org.rstudio.core.client.resources.ImageResource2x;
31 import org.rstudio.core.client.theme.DialogTabLayoutPanel;
32 import org.rstudio.core.client.theme.VerticalTabPanel;
33 import org.rstudio.core.client.widget.DirectoryChooserTextBox;
34 import org.rstudio.core.client.widget.MessageDialog;
35 import org.rstudio.core.client.widget.SelectWidget;
36 import org.rstudio.core.client.widget.TextBoxWithButton;
37 import org.rstudio.studio.client.RStudioGinjector;
38 import org.rstudio.studio.client.application.Desktop;
39 import org.rstudio.studio.client.application.model.RVersionSpec;
40 import org.rstudio.studio.client.application.model.RVersionsInfo;
41 import org.rstudio.studio.client.application.ui.RVersionSelectWidget;
42 import org.rstudio.studio.client.common.FileDialogs;
43 import org.rstudio.studio.client.common.GlobalDisplay;
44 import org.rstudio.studio.client.workbench.WorkbenchContext;
45 import org.rstudio.studio.client.workbench.model.RemoteFileSystemContext;
46 import org.rstudio.studio.client.workbench.model.Session;
47 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;
48 
49 import com.google.gwt.core.client.JsArrayString;
50 import com.google.gwt.dom.client.Style;
51 import com.google.gwt.dom.client.Style.Unit;
52 import com.google.gwt.resources.client.ImageResource;
53 import com.google.gwt.user.client.ui.CheckBox;
54 import com.google.gwt.user.client.ui.Label;
55 import com.google.inject.Inject;
56 
57 public class GeneralPreferencesPane extends PreferencesPane
58 {
59    @Inject
GeneralPreferencesPane(RemoteFileSystemContext fsContext, FileDialogs fileDialogs, UserPrefs prefs, Session session, GlobalDisplay globalDisplay, WorkbenchContext context)60    public GeneralPreferencesPane(RemoteFileSystemContext fsContext,
61                                  FileDialogs fileDialogs,
62                                  UserPrefs prefs,
63                                  Session session,
64                                  GlobalDisplay globalDisplay,
65                                  WorkbenchContext context)
66    {
67       fsContext_ = fsContext;
68       fileDialogs_ = fileDialogs;
69       prefs_ = prefs;
70       session_ = session;
71 
72       RVersionsInfo versionsInfo = context.getRVersionsInfo();
73       VerticalTabPanel basic = new VerticalTabPanel(ElementIds.GENERAL_BASIC_PREFS);
74 
75       basic.add(headerLabel("R Sessions"));
76       if (BrowseCap.isWindowsDesktop())
77       {
78          rVersion_ = new TextBoxWithButton(
79                "R version:",
80                "",
81                "Change...",
82                null,
83                ElementIds.TextBoxButtonId.R_VERSION,
84                true,
85                new ClickHandler()
86                {
87                   @Override
88                   public void onClick(ClickEvent event)
89                   {
90                      Desktop.getFrame().chooseRVersion(ver ->
91                      {
92                         if (!StringUtil.isNullOrEmpty(ver))
93                         {
94                            rVersion_.setText(ver);
95 
96                            globalDisplay.showMessage(MessageDialog.INFO,
97                                  "Change R Version",
98                                  "You need to quit and re-open RStudio " +
99                                        "in order for this change to take effect.");
100                         }
101                      });
102                   }
103                });
104          rVersion_.setWidth("100%");
105          rVersion_.setText("Loading...");
106          rVersion_.getElement().getStyle().setMarginLeft(2, Unit.PX);
107          Desktop.getFrame().getRVersion(version -> {
108             rVersion_.setText(version);
109          });
110          spaced(rVersion_);
111          basic.add(rVersion_);
112       }
113       if (versionsInfo.isMultiVersion())
114       {
115          rServerRVersion_ = new RVersionSelectWidget(
116                                        versionsInfo.getAvailableRVersions());
117          basic.add(tight(rServerRVersion_));
118 
119          rememberRVersionForProjects_ =
120                         new CheckBox("Restore last used R version for projects");
121 
122          rememberRVersionForProjects_.setValue(true);
123          Style style = rememberRVersionForProjects_.getElement().getStyle();
124          style.setMarginTop(5, Unit.PX);
125          style.setMarginBottom(12, Unit.PX);
126          basic.add(rememberRVersionForProjects_);
127       }
128 
129       dirChooser_ = new DirectoryChooserTextBox(
130             "Default working directory (when not in a project):",
131             ElementIds.TextBoxButtonId.DEFAULT_WORKING_DIR,
132             null,
133             fileDialogs_,
134             fsContext_);
135 
136       spaced(dirChooser_);
137       nudgeRight(dirChooser_);
138       textBoxWithChooser(dirChooser_);
139       basic.add(dirChooser_);
140 
141       restoreLastProject_ = new CheckBox("Restore most recently opened project at startup");
142       lessSpaced(restoreLastProject_);
143       basic.add(restoreLastProject_);
144 
145       basic.add(checkboxPref("Restore previously open source documents at startup", prefs_.restoreSourceDocuments()));
146 
147       rProfileOnResume_ = new CheckBox("Run Rprofile when resuming suspended session");
148       if (!Desktop.isDesktop())
149          basic.add(rProfileOnResume_);
150 
151       basic.add(spacedBefore(headerLabel("Workspace")));
152       basic.add(loadRData_ = new CheckBox("Restore .RData into workspace at startup"));
153       lessSpaced(loadRData_);
154 
155       saveWorkspace_ = new SelectWidget(
156             "Save workspace to .RData on exit:",
157             new String[]
158             {
159                "Always",
160                "Never",
161                "Ask"
162             },
163             new String[]
164             {
165                UserPrefs.SAVE_WORKSPACE_ALWAYS,
166                UserPrefs.SAVE_WORKSPACE_NEVER,
167                UserPrefs.SAVE_WORKSPACE_ASK
168             }, false, true, false);
169       spaced(saveWorkspace_);
170       basic.add(saveWorkspace_);
171 
172       basic.add(headerLabel("History"));
173       alwaysSaveHistory_ = new CheckBox(
174             "Always save history (even when not saving .RData)");
175       lessSpaced(alwaysSaveHistory_);
176       basic.add(alwaysSaveHistory_);
177 
178       removeHistoryDuplicates_ = new CheckBox(
179                                  "Remove duplicate entries in history");
180       basic.add(removeHistoryDuplicates_);
181 
182       basic.add(spacedBefore(headerLabel("Other")));
183 
184       basic.add(checkboxPref(
185             "Wrap around when navigating to previous/next tab",
186             prefs_.wrapTabNavigation(),
187             true /*defaultSpaced*/));
188 
189       // provide check for updates option in desktop mode when not
190       // already globally disabled
191       if (Desktop.isDesktop() &&
192           !session.getSessionInfo().getDisableCheckForUpdates())
193       {
194          basic.add(checkboxPref("Automatically notify me of updates to RStudio",
195                    prefs_.checkForUpdates(), true /*defaultSpaced*/));
196       }
197 
198       // crash reporting - only show in desktop mode
199       enableCrashReporting_ = checkboxPref("Send automated crash reports to RStudio",
200             prefs_.submitCrashReports());
201       if (Desktop.isDesktop())
202       {
203          lessSpaced(enableCrashReporting_);
204          enableCrashReporting_.setEnabled(session.getSessionInfo().getCrashHandlerSettingsModifiable());
205          basic.add(enableCrashReporting_);
206       }
207 
208       VerticalTabPanel graphics = new VerticalTabPanel(ElementIds.GENERAL_GRAPHICS_PREFS);
209 
210       initializeGraphicsBackendWidget();
211       graphics.add(headerLabel("Graphics Device"));
212       graphics.add(graphicsBackend_);
213 
214       graphicsAntialias_ = new SelectWidget(
215             "Antialiasing:",
216             new String[] {
217                   "(Default)",
218                   "None",
219                   "Gray",
220                   "Subpixel"
221             },
222             new String[] {
223                   UserPrefs.GRAPHICS_ANTIALIASING_DEFAULT,
224                   UserPrefs.GRAPHICS_ANTIALIASING_NONE,
225                   UserPrefs.GRAPHICS_ANTIALIASING_GRAY,
226                   UserPrefs.GRAPHICS_ANTIALIASING_SUBPIXEL
227             },
228             false,
229             true,
230             false);
231 
232       graphics.add(graphicsAntialias_);
233 
234       VerticalTabPanel advanced = new VerticalTabPanel(ElementIds.GENERAL_ADVANCED_PREFS);
235 
236       showServerHomePage_ = new SelectWidget(
237             "Show server home page:",
238             new String[] {
239                   "Multiple active sessions",
240                   "Always",
241                   "Never"
242             },
243             new String[] {
244                  "sessions",
245                  "always",
246                  "never"
247             },
248             false,
249             true,
250             false);
251 
252       reuseSessionsForProjectLinks_ = new CheckBox("Re-use idle sessions for project links");
253       lessSpaced(reuseSessionsForProjectLinks_);
254       boolean firstHeader = true;
255 
256       if (!Desktop.hasDesktopFrame())
257       {
258          if (session_.getSessionInfo().getShowUserHomePage() ||
259              session_.getSessionInfo().getMultiSession())
260          {
261             Label homePageLabel = headerLabel("Home Page");
262             spacedBefore(homePageLabel);
263             advanced.add(homePageLabel);
264             firstHeader = false;
265          }
266          if (session_.getSessionInfo().getShowUserHomePage())
267          {
268             tight(showServerHomePage_);
269             advanced.add(showServerHomePage_);
270          }
271          if (session_.getSessionInfo().getMultiSession())
272             advanced.add(reuseSessionsForProjectLinks_);
273       }
274 
275      Label debuggingLabel = headerLabel("Debugging");
276      if (!firstHeader)
277      {
278         spacedBefore(debuggingLabel);
279         firstHeader = false;
280      }
281      advanced.add(debuggingLabel);
282      advanced.add(checkboxPref(
283            "Use debug error handler only when my code contains errors",
284            prefs_.handleErrorsInUserCodeOnly()));
285 
286       if (Desktop.hasDesktopFrame())
287       {
288          Label osLabel = headerLabel("OS Integration");
289          spacedBefore(osLabel);
290          advanced.add(osLabel);
291 
292          renderingEngineWidget_ = new SelectWidget("Rendering engine:", new String[] {});
293          renderingEngineWidget_.addChoice("Auto-detect (recommended)", ENGINE_AUTO);
294          renderingEngineWidget_.addChoice("Desktop OpenGL", ENGINE_DESKTOP);
295          if (BrowseCap.isLinuxDesktop())
296          {
297             renderingEngineWidget_.addChoice("OpenGL for Embedded Systems", ENGINE_GLES);
298          }
299          renderingEngineWidget_.addChoice("Software", ENGINE_SOFTWARE);
300          advanced.add(spaced(renderingEngineWidget_));
301 
302          Desktop.getFrame().desktopRenderingEngine((String engine) -> {
303             if (StringUtil.isNullOrEmpty(engine))
304                return;
305             renderingEngineWidget_.setValue(engine);
306             renderingEngine_ = engine;
307          });
308 
309          useGpuExclusions_ = new CheckBox("Use GPU exclusion list (recommended)");
310          advanced.add(lessSpaced(useGpuExclusions_));
311          Desktop.getFrame().getIgnoreGpuExclusionList((Boolean ignore) -> {
312             desktopIgnoreGpuExclusions_ = ignore;
313             useGpuExclusions_.setValue(!ignore);
314          });
315 
316          useGpuDriverBugWorkarounds_ = new CheckBox("Use GPU driver bug workarounds (recommended)");
317          advanced.add(lessSpaced(useGpuDriverBugWorkarounds_));
318          Desktop.getFrame().getDisableGpuDriverBugWorkarounds((Boolean disable) -> {
319             desktopDisableGpuDriverBugWorkarounds_ = disable;
320             useGpuDriverBugWorkarounds_.setValue(!disable);
321          });
322 
323          if (BrowseCap.isLinuxDesktop())
324          {
325             clipboardMonitoring_ = new CheckBox("Enable X11 clipboard monitoring");
326             advanced.add(lessSpaced(clipboardMonitoring_));
327             Desktop.getFrame().getClipboardMonitoring(monitoring ->
328             {
329                desktopMonitoring_ = monitoring;
330                clipboardMonitoring_.setValue(monitoring);
331             });
332          }
333 
334          fullPathInTitle_ = new CheckBox("Show full path to project in window title");
335          advanced.add(lessSpaced(fullPathInTitle_));
336       }
337 
338       Label otherLabel = headerLabel("Other");
339       spacedBefore(otherLabel);
340       advanced.add(otherLabel);
341 
342       showLastDotValue_ = new CheckBox("Show .Last.value in environment listing");
343       lessSpaced(showLastDotValue_);
344       advanced.add(showLastDotValue_);
345 
346       String[] labels = {"7", "8", "9", "10", "11", "12", "13", "14", "16", "18", "24", "36"};
347       String[] values = new String[labels.length];
348       for (int i = 0; i < labels.length; i++)
349          values[i] = Double.parseDouble(labels[i]) + "";
350 
351       helpFontSize_ = new SelectWidget("Help panel font size:",
352                                        labels,
353                                        values,
354                                        false, /* Multi select */
355                                        true, /* Horizontal label */
356                                        false /* List on left */);
357       if (!helpFontSize_.setValue(prefs_.helpFontSizePoints().getValue() + ""))
358          helpFontSize_.getListBox().setSelectedIndex(3);
359       advanced.add(helpFontSize_);
360 
361       showServerHomePage_.setEnabled(false);
362       reuseSessionsForProjectLinks_.setEnabled(false);
363       saveWorkspace_.setEnabled(false);
364       loadRData_.setEnabled(false);
365       dirChooser_.setEnabled(false);
366       alwaysSaveHistory_.setEnabled(false);
367       removeHistoryDuplicates_.setEnabled(false);
368       rProfileOnResume_.setEnabled(false);
369       showLastDotValue_.setEnabled(false);
370       restoreLastProject_.setEnabled(false);
371 
372       DialogTabLayoutPanel tabPanel = new DialogTabLayoutPanel("General");
373       tabPanel.setSize("435px", "533px");
374       tabPanel.add(basic, "Basic", basic.getBasePanelId());
375       tabPanel.add(graphics, "Graphics", graphics.getBasePanelId());
376       tabPanel.add(advanced, "Advanced", advanced.getBasePanelId());
377       tabPanel.selectTab(0);
378       add(tabPanel);
379    }
380 
381    @Override
initialize(UserPrefs prefs)382    protected void initialize(UserPrefs prefs)
383    {
384       boolean isLauncherSession = session_.getSessionInfo().getLauncherSession();
385       showServerHomePage_.setEnabled(!isLauncherSession);
386 
387       reuseSessionsForProjectLinks_.setEnabled(true);
388       saveWorkspace_.setEnabled(true);
389       loadRData_.setEnabled(true);
390       dirChooser_.setEnabled(true);
391 
392       if (!isLauncherSession)
393          showServerHomePage_.setValue(prefs.showUserHomePage().getValue());
394       else
395          showServerHomePage_.setValue(UserPrefs.SHOW_USER_HOME_PAGE_ALWAYS);
396 
397       reuseSessionsForProjectLinks_.setValue(prefs.reuseSessionsForProjectLinks().getValue());
398 
399       int saveWorkspaceIndex;
400       switch (prefs.saveWorkspace().getValue())
401       {
402          case UserPrefs.SAVE_WORKSPACE_NEVER:
403             saveWorkspaceIndex = 1;
404             break;
405          case UserPrefs.SAVE_WORKSPACE_ALWAYS:
406             saveWorkspaceIndex = 0;
407             break;
408          case UserPrefs.SAVE_WORKSPACE_ASK:
409          default:
410             saveWorkspaceIndex = 2;
411             break;
412       }
413       saveWorkspace_.getListBox().setSelectedIndex(saveWorkspaceIndex);
414 
415       loadRData_.setValue(prefs.loadWorkspace().getValue());
416 
417       // NOTE: we intentionally ignore sessionInfo's version of the
418       // initial working directory here as that might reference a
419       // project-specific location, and we don't want that to end up
420       // encoded as part of the user's global preferences
421       String workingDir = prefs.initialWorkingDirectory().getValue();
422       if (StringUtil.isNullOrEmpty(workingDir))
423          workingDir = "~";
424 
425       dirChooser_.setText(workingDir);
426 
427       alwaysSaveHistory_.setEnabled(true);
428       removeHistoryDuplicates_.setEnabled(true);
429 
430       alwaysSaveHistory_.setValue(prefs.alwaysSaveHistory().getValue());
431       removeHistoryDuplicates_.setValue(prefs.removeHistoryDuplicates().getValue());
432 
433       rProfileOnResume_.setValue(prefs.runRprofileOnResume().getValue());
434       rProfileOnResume_.setEnabled(true);
435 
436       showLastDotValue_.setValue(prefs.showLastDotValue().getValue());
437       showLastDotValue_.setEnabled(true);
438 
439       if (rServerRVersion_ != null)
440          rServerRVersion_.setRVersion(prefs.defaultRVersion().getValue().cast());
441 
442       if (rememberRVersionForProjects_ != null)
443       {
444          rememberRVersionForProjects_.setValue(
445                                    prefs.restoreProjectRVersion().getValue());
446       }
447 
448       if (fullPathInTitle_ != null)
449          fullPathInTitle_.setValue(prefs.fullProjectPathInWindowTitle().getValue());
450 
451       enableCrashReporting_.setValue(prefs.submitCrashReports().getValue());
452 
453       // projects prefs
454       restoreLastProject_.setEnabled(true);
455       restoreLastProject_.setValue(prefs.restoreLastProject().getValue());
456 
457       // graphics prefs
458       graphicsBackend_.setValue(prefs.graphicsBackend().getValue());
459       graphicsAntialias_.setValue(prefs.graphicsAntialiasing().getValue());
460    }
461 
462 
463    @Override
getIcon()464    public ImageResource getIcon()
465    {
466       return new ImageResource2x(PreferencesDialogBaseResources.INSTANCE.iconR2x());
467    }
468 
469    @Override
onApply(UserPrefs prefs)470    public RestartRequirement onApply(UserPrefs prefs)
471    {
472       RestartRequirement restartRequirement = super.onApply(prefs);
473 
474       {
475          double helpFontSize = Double.parseDouble(helpFontSize_.getValue());
476          prefs.helpFontSizePoints().setGlobalValue(helpFontSize);
477       }
478 
479       if (clipboardMonitoring_ != null &&
480           desktopMonitoring_ != clipboardMonitoring_.getValue())
481       {
482          // set monitoring property if changed
483          restartRequirement.setDesktopRestartRequired(true);
484          boolean desktopMonitoring = clipboardMonitoring_.getValue();
485          desktopMonitoring_ = desktopMonitoring;
486          Desktop.getFrame().setClipboardMonitoring(desktopMonitoring);
487       }
488 
489       if (fullPathInTitle_ != null &&
490          fullPathInTitle_.getValue() != prefs.fullProjectPathInWindowTitle().getValue())
491       {
492          restartRequirement.setDesktopRestartRequired(true);
493          prefs.fullProjectPathInWindowTitle().setGlobalValue(fullPathInTitle_.getValue());
494       }
495 
496       if (renderingEngineWidget_ != null &&
497           !StringUtil.equals(renderingEngineWidget_.getValue(), renderingEngine_))
498       {
499          // set desktop renderer when changed
500          restartRequirement.setDesktopRestartRequired(true);
501          String renderingEngine = renderingEngineWidget_.getValue();
502          renderingEngine_ = renderingEngine;
503          Desktop.getFrame().setDesktopRenderingEngine(renderingEngine);
504       }
505 
506       if (useGpuExclusions_ != null &&
507           desktopIgnoreGpuExclusions_ != !useGpuExclusions_.getValue())
508       {
509          restartRequirement.setDesktopRestartRequired(true);
510          boolean ignore = !useGpuExclusions_.getValue();
511          desktopIgnoreGpuExclusions_ = ignore;
512          Desktop.getFrame().setIgnoreGpuExclusionList(ignore);
513       }
514 
515       if (useGpuDriverBugWorkarounds_ != null &&
516           desktopDisableGpuDriverBugWorkarounds_ != !useGpuDriverBugWorkarounds_.getValue())
517       {
518          restartRequirement.setDesktopRestartRequired(true);
519          boolean disable = !useGpuDriverBugWorkarounds_.getValue();
520          desktopDisableGpuDriverBugWorkarounds_ = disable;
521          Desktop.getFrame().setDisableGpuDriverBugWorkarounds(disable);
522       }
523 
524       if (saveWorkspace_.isEnabled())
525       {
526          prefs.saveWorkspace().setGlobalValue(saveWorkspace_.getValue());
527       }
528 
529       prefs.loadWorkspace().setGlobalValue(loadRData_.getValue());
530       prefs.runRprofileOnResume().setGlobalValue(rProfileOnResume_.getValue());
531       prefs.initialWorkingDirectory().setGlobalValue(dirChooser_.getText());
532       prefs.showLastDotValue().setGlobalValue(showLastDotValue_.getValue());
533       prefs.alwaysSaveHistory().setGlobalValue(alwaysSaveHistory_.getValue());
534       prefs.removeHistoryDuplicates().setGlobalValue(removeHistoryDuplicates_.getValue());
535       prefs.restoreLastProject().setGlobalValue(restoreLastProject_.getValue());
536       prefs.graphicsBackend().setGlobalValue(graphicsBackend_.getValue());
537       prefs.graphicsAntialiasing().setGlobalValue(graphicsAntialias_.getValue());
538 
539       // Pro specific
540       if (showServerHomePage_ != null && showServerHomePage_.isEnabled())
541          prefs.showUserHomePage().setGlobalValue(showServerHomePage_.getValue());
542       if (reuseSessionsForProjectLinks_ != null && reuseSessionsForProjectLinks_.isEnabled())
543          prefs.reuseSessionsForProjectLinks().setGlobalValue(reuseSessionsForProjectLinks_.getValue());
544       if (rServerRVersion_ != null && rServerRVersion_.isEnabled())
545          prefs.defaultRVersion().setGlobalValue(rServerRVersion_.getRVersion());
546       if (rememberRVersionForProjects_ != null && rememberRVersionForProjects_.isEnabled())
547          prefs.restoreProjectRVersion().setGlobalValue(rememberRVersionForProjects_.getValue());
548 
549       return restartRequirement;
550    }
551 
552    @Override
getName()553    public String getName()
554    {
555       return "General";
556    }
557 
558    @SuppressWarnings("unused")
getDefaultRVersion()559    private RVersionSpec getDefaultRVersion()
560    {
561       if (rServerRVersion_ != null)
562          return rServerRVersion_.getRVersion();
563       else
564          return RVersionSpec.createEmpty();
565    }
566 
567    @SuppressWarnings("unused")
getRestoreProjectRVersion()568    private boolean getRestoreProjectRVersion()
569    {
570       if (rememberRVersionForProjects_ != null)
571          return rememberRVersionForProjects_.getValue();
572       else
573          return false;
574    }
575 
initializeGraphicsBackendWidget()576    private void initializeGraphicsBackendWidget()
577    {
578       Map<String, String> valuesToLabelsMap = new HashMap<>();
579       valuesToLabelsMap.put(UserPrefs.GRAPHICS_BACKEND_DEFAULT, " (Default)");
580       valuesToLabelsMap.put(UserPrefs.GRAPHICS_BACKEND_QUARTZ,    "Quartz");
581       valuesToLabelsMap.put(UserPrefs.GRAPHICS_BACKEND_WINDOWS,   "Windows");
582       valuesToLabelsMap.put(UserPrefs.GRAPHICS_BACKEND_CAIRO,     "Cairo");
583       valuesToLabelsMap.put(UserPrefs.GRAPHICS_BACKEND_CAIRO_PNG, "Cairo PNG");
584       valuesToLabelsMap.put(UserPrefs.GRAPHICS_BACKEND_RAGG,      "AGG");
585 
586       JsArrayString supportedBackends =
587             session_.getSessionInfo().getGraphicsBackends();
588 
589       String[] values = new String[supportedBackends.length() + 1];
590       values[0] = "default";
591       for (int i = 0; i < supportedBackends.length(); i++)
592          values[i + 1] = supportedBackends.get(i);
593 
594       String[] labels = new String[supportedBackends.length() + 1];
595       for (int i = 0; i < labels.length; i++)
596          labels[i] = valuesToLabelsMap.get(values[i]);
597 
598       graphicsBackend_ =
599             new SelectWidget("Backend:", labels, values, false, true, false);
600 
601       graphicsBackend_.addChangeHandler((ChangeEvent event) ->
602       {
603          String backend = graphicsBackend_.getValue();
604          if (StringUtil.equals(backend, UserPrefs.GRAPHICS_BACKEND_RAGG))
605          {
606             RStudioGinjector.INSTANCE.getDependencyManager().withRagg(
607                   "Using the AGG renderer",
608                   (Boolean succeeded) ->
609                   {
610                      if (!succeeded)
611                      {
612                         graphicsBackend_.setValue(UserPrefs.GRAPHICS_BACKEND_DEFAULT);
613                      }
614                   });
615          }
616       });
617    }
618 
619    private static final String ENGINE_AUTO        = "auto";
620    private static final String ENGINE_DESKTOP     = "desktop";
621    private static final String ENGINE_GLES        = "gles";
622    private static final String ENGINE_SOFTWARE    = "software";
623 
624    private boolean desktopMonitoring_ = false;
625    private boolean desktopIgnoreGpuExclusions_ = false;
626    private boolean desktopDisableGpuDriverBugWorkarounds_ = false;
627 
628    private final FileSystemContext fsContext_;
629    private final FileDialogs fileDialogs_;
630    private RVersionSelectWidget rServerRVersion_ = null;
631    private CheckBox rememberRVersionForProjects_ = null;
632    private CheckBox reuseSessionsForProjectLinks_ = null;
633    private SelectWidget helpFontSize_;
634    private CheckBox clipboardMonitoring_ = null;
635    private CheckBox fullPathInTitle_ = null;
636    private CheckBox useGpuExclusions_ = null;
637    private CheckBox useGpuDriverBugWorkarounds_ = null;
638    private SelectWidget renderingEngineWidget_ = null;
639    private String renderingEngine_ = null;
640 
641    private SelectWidget graphicsBackend_;
642    private SelectWidget graphicsAntialias_;
643 
644    private SelectWidget showServerHomePage_;
645    private SelectWidget saveWorkspace_;
646    private TextBoxWithButton rVersion_;
647    private TextBoxWithButton dirChooser_;
648    private CheckBox loadRData_;
649    private final CheckBox alwaysSaveHistory_;
650    private final CheckBox removeHistoryDuplicates_;
651    private CheckBox restoreLastProject_;
652    private CheckBox rProfileOnResume_;
653    private CheckBox showLastDotValue_;
654    private CheckBox enableCrashReporting_;
655    private final UserPrefs prefs_;
656    private final Session session_;
657 }
658