1 /*
2  * UserPrefs.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.model;
16 
17 import com.google.gwt.core.client.JsArray;
18 import com.google.gwt.core.client.Scheduler;
19 import com.google.inject.Inject;
20 import com.google.inject.Singleton;
21 
22 import org.rstudio.core.client.CommandWithArg;
23 import org.rstudio.core.client.Debug;
24 import org.rstudio.core.client.StringUtil;
25 import org.rstudio.core.client.command.CommandBinder;
26 import org.rstudio.core.client.command.Handler;
27 import org.rstudio.core.client.dom.WindowEx;
28 import org.rstudio.studio.client.RStudioGinjector;
29 import org.rstudio.studio.client.application.ApplicationQuit;
30 import org.rstudio.studio.client.application.AriaLiveService;
31 import org.rstudio.studio.client.application.Desktop;
32 import org.rstudio.studio.client.application.events.AriaLiveStatusEvent.Severity;
33 import org.rstudio.studio.client.application.events.AriaLiveStatusEvent.Timing;
34 import org.rstudio.studio.client.application.events.DeferredInitCompletedEvent;
35 import org.rstudio.studio.client.application.events.EventBus;
36 import org.rstudio.studio.client.application.events.ReloadEvent;
37 import org.rstudio.studio.client.common.Timers;
38 import org.rstudio.studio.client.common.satellite.Satellite;
39 import org.rstudio.studio.client.common.satellite.SatelliteManager;
40 import org.rstudio.studio.client.server.ServerError;
41 import org.rstudio.studio.client.server.ServerRequestCallback;
42 import org.rstudio.studio.client.server.Void;
43 import org.rstudio.studio.client.server.VoidServerRequestCallback;
44 import org.rstudio.studio.client.workbench.commands.Commands;
45 import org.rstudio.studio.client.workbench.model.Session;
46 import org.rstudio.studio.client.workbench.prefs.events.UserPrefsChangedEvent;
47 import org.rstudio.studio.client.common.GlobalDisplay;
48 
49 @Singleton
50 public class UserPrefs extends UserPrefsComputed
51    implements UserPrefsChangedEvent.Handler, DeferredInitCompletedEvent.Handler
52 {
53    public interface Binder
54            extends CommandBinder<Commands, UserPrefs> {}
55 
56    @Inject
UserPrefs(Session session, EventBus eventBus, PrefsServerOperations server, SatelliteManager satelliteManager, Commands commands, Binder binder, GlobalDisplay display, AriaLiveService ariaLive, ApplicationQuit quit)57    public UserPrefs(Session session,
58                     EventBus eventBus,
59                     PrefsServerOperations server,
60                     SatelliteManager satelliteManager,
61                     Commands commands,
62                     Binder binder,
63                     GlobalDisplay display,
64                     AriaLiveService ariaLive,
65                     ApplicationQuit quit)
66    {
67       super(session.getSessionInfo(),
68             (session.getSessionInfo() == null ?
69                JsArray.createArray().cast() :
70                session.getSessionInfo().getPrefs()));
71 
72       session_ = session;
73       eventBus_ = eventBus;
74       server_ = server;
75       satelliteManager_ = satelliteManager;
76       display_ = display;
77       commands_ = commands;
78       reloadAfterInit_ = false;
79       ariaLive_ = ariaLive;
80       quit_ = quit;
81 
82       binder.bind(commands_, this);
83 
84       eventBus.addHandler(UserPrefsChangedEvent.TYPE, this);
85       eventBus.addHandler(DeferredInitCompletedEvent.TYPE, this);
86       Scheduler.get().scheduleDeferred(() ->
87       {
88          origScreenReaderLabel_ = commands_.toggleScreenReaderSupport().getMenuLabel(false);
89          announceScreenReaderState();
90          syncToggleTabKeyMovesFocusState();
91       });
92    }
93 
writeUserPrefs()94    public void writeUserPrefs()
95    {
96       writeUserPrefs(null);
97    }
98 
writeUserPrefs(CommandWithArg<Boolean> onCompleted)99    public void writeUserPrefs(CommandWithArg<Boolean> onCompleted)
100    {
101       updatePrefs(session_.getSessionInfo().getPrefs());
102       server_.setUserPrefs(
103          session_.getSessionInfo().getUserPrefs(),
104          new ServerRequestCallback<Void>()
105          {
106             @Override
107             public void onResponseReceived(Void v)
108             {
109                UserPrefsChangedEvent event = new UserPrefsChangedEvent(
110                      session_.getSessionInfo().getUserPrefLayer());
111 
112                if (Satellite.isCurrentWindowSatellite())
113                {
114                   RStudioGinjector.INSTANCE.getEventBus()
115                      .fireEventToMainWindow(event);
116                }
117                else
118                {
119                   // let satellites know prefs have changed
120                   satelliteManager_.dispatchCrossWindowEvent(event);
121                }
122 
123                if (onCompleted != null)
124                {
125                   onCompleted.execute(true);
126                }
127             }
128             @Override
129             public void onError(ServerError error)
130             {
131                if (onCompleted != null)
132                {
133                   onCompleted.execute(false);
134                }
135                Debug.logError(error);
136             }
137          });
138    }
139 
140    /**
141     * Indicates whether autosave is enabled, via any pref that turns it on.
142     *
143     * @return Whether auto save is enabled.
144     */
autoSaveEnabled()145    public boolean autoSaveEnabled()
146    {
147       return autoSaveOnBlur().getValue() ||
148              StringUtil.equals(autoSaveOnIdle().getValue(), AUTO_SAVE_ON_IDLE_COMMIT);
149    }
150 
151    /**
152     * Indicates the number of milliseconds after which to autosave. Won't return
153     * a value less than 500 since autosaves typically require a network call and
154     * other synchronization.
155     *
156     * @return The number of milliseconds.
157     */
autoSaveMs()158    public int autoSaveMs()
159    {
160       Integer ms = autoSaveIdleMs().getValue();
161       if (ms < 500)
162          return 500;
163       return ms;
164    }
165 
166    @Override
onUserPrefsChanged(UserPrefsChangedEvent e)167    public void onUserPrefsChanged(UserPrefsChangedEvent e)
168    {
169       syncPrefs(e.getName(), e.getValues());
170    }
171 
172    @Handler
onEditUserPrefs()173    public void onEditUserPrefs()
174    {
175       server_.editPreferences(new VoidServerRequestCallback());
176    }
177 
178    @Handler
onClearUserPrefs()179    public void onClearUserPrefs()
180    {
181       display_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,
182          "Confirm Clear Preferences",
183          "Are you sure you want to clear your preferences? All RStudio settings " +
184          "will be restored to their defaults, and your R session will be " +
185          "restarted.",
186          false,
187          (indicator) ->
188          {
189             server_.clearPreferences(new ServerRequestCallback<String>()
190             {
191                public void onResponseReceived(String path)
192                {
193                   indicator.onCompleted();
194                   display_.showMessage(
195                         GlobalDisplay.MSG_INFO,
196                         "Preferences Cleared",
197                         "Your preferences have been cleared, and your R session " +
198                         "will now be restarted. A backup copy of your preferences " +
199                         "can be found at: \n\n" + path,
200                         () ->
201                         {
202                            // Restart R, then reload the UI when done
203                            reloadAfterInit_ = true;
204                            commands_.restartR().execute();
205                         },
206                         "Restart R",
207                         false);
208                }
209 
210                @Override
211                public void onError(ServerError error)
212                {
213                   indicator.onError(error.getMessage());
214                }
215             });
216          },
217          null,
218          "Clear Preferences",
219          "Cancel",
220          false);
221    }
222 
223    @Override
onDeferredInitCompleted(DeferredInitCompletedEvent event)224    public void onDeferredInitCompleted(DeferredInitCompletedEvent event)
225    {
226       // Called when R is finished initializing; if we have just cleared prefs,
227       // we also reload the UI when R's done restarting
228       if (reloadAfterInit_)
229       {
230          reloadAfterInit_ = false;
231          WindowEx.get().reload();
232       }
233    }
234 
235    @Handler
onViewAllPrefs()236    public void onViewAllPrefs()
237    {
238       server_.viewPreferences(new VoidServerRequestCallback());
239    }
240 
setScreenReaderMenuState(boolean checked)241    private void setScreenReaderMenuState(boolean checked)
242    {
243       commands_.toggleScreenReaderSupport().setChecked(checked);
244       commands_.toggleScreenReaderSupport().setMenuLabel(checked ?
245             origScreenReaderLabel_ + " (enabled)" :
246             origScreenReaderLabel_ + " (disabled)");
247    }
248 
announceScreenReaderState()249    private void announceScreenReaderState()
250    {
251       // announce if screen reader is not enabled; most things work without enabling it, but for
252       // best experience user should turn it on
253       if (!enableScreenReader().getValue())
254       {
255          Timers.singleShot(AriaLiveService.STARTUP_ANNOUNCEMENT_DELAY, () ->
256          {
257             String shortcut = commands_.toggleScreenReaderSupport().getShortcutRaw();
258             ariaLive_.announce(AriaLiveService.SCREEN_READER_NOT_ENABLED,
259                   "Warning: screen reader mode not enabled. Turn on using shortcut " + shortcut + ".",
260                   Timing.IMMEDIATE, Severity.ALERT);
261          });
262       }
263       setScreenReaderMenuState(enableScreenReader().getValue());
264    }
265 
setScreenReaderEnabled(boolean enabled)266    public void setScreenReaderEnabled(boolean enabled)
267    {
268       if (Desktop.hasDesktopFrame())
269          Desktop.getFrame().setEnableAccessibility(enabled);
270       enableScreenReader().setGlobalValue(enabled);
271 
272       // When screen-reader is enabled, reduce UI animations as they serve no purpose
273       // other than to potentially confuse the screen reader; turn animations back
274       // on when screen-reader support is disabled as that is the normal default and most
275       // users will never touch it.
276       RStudioGinjector.INSTANCE.getUserPrefs().reducedMotion().setGlobalValue(enabled);
277 
278       // Disable virtual scrolling when screen reader is enabled
279       if (enabled)
280          RStudioGinjector.INSTANCE.getUserPrefs().limitVisibleConsole().setGlobalValue(false);
281    }
282 
283    @Handler
onToggleScreenReaderSupport()284    void onToggleScreenReaderSupport()
285    {
286       display_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,
287             "Confirm Toggle Screen Reader Support",
288             "Are you sure you want to " + (enableScreenReader().getValue() ? "disable" : "enable") + " " +
289             "screen reader support? The application will reload to apply the change.",
290             false,
291             () ->
292             {
293                setScreenReaderEnabled(!enableScreenReader().getValue());
294                writeUserPrefs(succeeded -> {
295                   if (succeeded)
296                   {
297                      if (Desktop.isDesktop())
298                         quit_.doRestart(session_);
299                      else
300                         eventBus_.fireEvent(new ReloadEvent());
301                   }
302                   else
303                   {
304                      display_.showErrorMessage("Error Changing Setting",
305                            "The screen reader support setting could not be changed.");
306                   }
307                });
308             },
309             () -> {
310                setScreenReaderMenuState(enableScreenReader().getValue());
311             },
312             false);
313    }
314 
syncToggleTabKeyMovesFocusState(boolean checked)315    public void syncToggleTabKeyMovesFocusState(boolean checked)
316    {
317       commands_.toggleTabKeyMovesFocus().setChecked(checked);
318    }
319 
syncToggleTabKeyMovesFocusState()320    private void syncToggleTabKeyMovesFocusState()
321    {
322       syncToggleTabKeyMovesFocusState(
323             RStudioGinjector.INSTANCE.getUserPrefs().tabKeyMoveFocus().getValue());
324    }
325 
326    @Handler
onToggleTabKeyMovesFocus()327    void onToggleTabKeyMovesFocus()
328    {
329       boolean newMode = !RStudioGinjector.INSTANCE.getUserPrefs().tabKeyMoveFocus().getValue();
330       RStudioGinjector.INSTANCE.getUserPrefs().tabKeyMoveFocus().setGlobalValue(newMode);
331       writeUserPrefs(succeeded ->
332       {
333          if (succeeded)
334          {
335             syncToggleTabKeyMovesFocusState();
336             ariaLive_.announce(AriaLiveService.TAB_KEY_MODE,
337                   newMode ? "Tab key always moves focus on" : "Tab key always moves focus off",
338                   Timing.IMMEDIATE, Severity.STATUS);
339          }
340          else
341          {
342             display_.showErrorMessage("Error Changing Setting",
343                   "The tab key moves focus setting could not be updated.");
344          }
345       });
346    }
347 
348    public static final int LAYER_DEFAULT  = 0;
349    public static final int LAYER_SYSTEM   = 1;
350    public static final int LAYER_COMPUTED = 2;
351    public static final int LAYER_USER     = 3;
352    public static final int LAYER_PROJECT  = 4;
353 
354    public static final int MAX_TAB_WIDTH = 64;
355    public static final int MAX_WRAP_COLUMN = 256;
356    public static final int MAX_SCREEN_READER_CONSOLE_OUTPUT = 999;
357 
358    private final Session session_;
359    private final PrefsServerOperations server_;
360    private final SatelliteManager satelliteManager_;
361    private final GlobalDisplay display_;
362    private final Commands commands_;
363    private final EventBus eventBus_;
364    private final AriaLiveService ariaLive_;
365    private final ApplicationQuit quit_;
366 
367    private boolean reloadAfterInit_;
368    private String origScreenReaderLabel_;
369 }
370