1 /*
2  * Workbench.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;
16 
17 import com.google.gwt.core.client.GWT;
18 import com.google.gwt.core.client.Scheduler;
19 import com.google.gwt.user.client.Command;
20 import com.google.inject.Inject;
21 import com.google.inject.Provider;
22 
23 import org.rstudio.core.client.BrowseCap;
24 import org.rstudio.core.client.Size;
25 import org.rstudio.core.client.StringUtil;
26 import org.rstudio.core.client.TimeBufferedCommand;
27 import org.rstudio.core.client.command.AppCommand;
28 import org.rstudio.core.client.command.CommandBinder;
29 import org.rstudio.core.client.command.Handler;
30 import org.rstudio.core.client.files.FileSystemItem;
31 import org.rstudio.core.client.files.filedialog.events.OpenFileDialogEvent;
32 import org.rstudio.core.client.widget.ModifyKeyboardShortcutsWidget;
33 import org.rstudio.core.client.widget.Operation;
34 import org.rstudio.core.client.widget.OperationWithInput;
35 import org.rstudio.core.client.widget.ProgressIndicator;
36 import org.rstudio.core.client.widget.ProgressOperationWithInput;
37 import org.rstudio.studio.client.RStudioGinjector;
38 import org.rstudio.studio.client.application.ApplicationVisibility;
39 import org.rstudio.studio.client.application.Desktop;
40 import org.rstudio.studio.client.application.events.DeferredInitCompletedEvent;
41 import org.rstudio.studio.client.application.events.EventBus;
42 import org.rstudio.studio.client.common.ConsoleDispatcher;
43 import org.rstudio.studio.client.common.FileDialogs;
44 import org.rstudio.studio.client.common.GlobalDisplay;
45 import org.rstudio.studio.client.common.GlobalDisplay.NewWindowOptions;
46 import org.rstudio.studio.client.common.GlobalProgressDelayer;
47 import org.rstudio.studio.client.common.SimpleRequestCallback;
48 import org.rstudio.studio.client.common.dependencies.DependencyManager;
49 import org.rstudio.studio.client.common.filetypes.FileTypeRegistry;
50 import org.rstudio.studio.client.common.rstudioapi.AskSecretManager;
51 import org.rstudio.studio.client.common.vcs.AskPassManager;
52 import org.rstudio.studio.client.common.vcs.ShowPublicKeyDialog;
53 import org.rstudio.studio.client.common.vcs.VCSConstants;
54 import org.rstudio.studio.client.htmlpreview.HTMLPreview;
55 import org.rstudio.studio.client.htmlpreview.events.ShowHTMLPreviewEvent;
56 import org.rstudio.studio.client.htmlpreview.events.ShowPageViewerEvent;
57 import org.rstudio.studio.client.htmlpreview.model.HTMLPreviewParams;
58 import org.rstudio.studio.client.pdfviewer.PDFViewer;
59 import org.rstudio.studio.client.plumber.PlumberAPI;
60 import org.rstudio.studio.client.projects.ProjectOpener;
61 import org.rstudio.studio.client.projects.model.ProjectTemplateRegistryProvider;
62 import org.rstudio.studio.client.rmarkdown.RmdOutput;
63 import org.rstudio.studio.client.rmarkdown.events.ShinyGadgetDialogEvent;
64 import org.rstudio.studio.client.server.Server;
65 import org.rstudio.studio.client.server.ServerError;
66 import org.rstudio.studio.client.server.ServerRequestCallback;
67 import org.rstudio.studio.client.server.VoidServerRequestCallback;
68 import org.rstudio.studio.client.server.remote.ExecuteUserCommandEvent;
69 import org.rstudio.studio.client.shiny.ShinyApplication;
70 import org.rstudio.studio.client.shiny.ui.ShinyGadgetDialog;
71 import org.rstudio.studio.client.workbench.commands.Commands;
72 import org.rstudio.studio.client.workbench.commands.ReportShortcutBindingEvent;
73 import org.rstudio.studio.client.workbench.events.*;
74 import org.rstudio.studio.client.workbench.events.ShowMainMenuEvent.Menu;
75 import org.rstudio.studio.client.workbench.model.*;
76 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;
77 import org.rstudio.studio.client.workbench.snippets.ui.EditSnippetsDialog;
78 import org.rstudio.studio.client.workbench.views.choosefile.ChooseFile;
79 import org.rstudio.studio.client.workbench.views.files.events.DirectoryNavigateEvent;
80 import org.rstudio.studio.client.workbench.views.source.SourceWindowManager;
81 import org.rstudio.studio.client.workbench.views.source.editors.profiler.ProfilerPresenter;
82 import org.rstudio.studio.client.workbench.views.terminal.events.ActivateNamedTerminalEvent;
83 import org.rstudio.studio.client.workbench.views.tutorial.TutorialPresenter.Tutorial;
84 import org.rstudio.studio.client.workbench.views.tutorial.events.TutorialCommandEvent;
85 import org.rstudio.studio.client.workbench.views.tutorial.events.TutorialLaunchEvent;
86 import org.rstudio.studio.client.workbench.views.vcs.git.model.GitState;
87 
88 public class Workbench implements BusyEvent.Handler,
89                                   ShowErrorMessageEvent.Handler,
90                                   UserPromptEvent.Handler,
91                                   ShowWarningBarEvent.Handler,
92                                   BrowseUrlEvent.Handler,
93                                   QuotaStatusEvent.Handler,
94                                   WorkbenchLoadedEvent.Handler,
95                                   WorkbenchMetricsChangedEvent.Handler,
96                                   InstallRtoolsEvent.Handler,
97                                   ShinyGadgetDialogEvent.Handler,
98                                   ExecuteUserCommandEvent.Handler,
99                                   AdminNotificationEvent.Handler,
100                                   OpenFileDialogEvent.Handler,
101                                   ShowPageViewerEvent.Handler,
102                                   TutorialLaunchEvent.Handler,
103                                   DeferredInitCompletedEvent.Handler,
104                                   ReportShortcutBindingEvent.Handler
105 {
106    interface Binder extends CommandBinder<Commands, Workbench> {}
107 
108    @Inject
Workbench(WorkbenchMainView view, WorkbenchContext workbenchContext, GlobalDisplay globalDisplay, Commands commands, EventBus eventBus, Session session, Provider<UserPrefs> pPrefs, Server server, RemoteFileSystemContext fsContext, FileDialogs fileDialogs, FileTypeRegistry fileTypeRegistry, ConsoleDispatcher consoleDispatcher, WorkbenchNewSession newSession, ProjectOpener projectOpener, Provider<GitState> pGitState, SourceWindowManager sourceWindowManager, UserInterfaceHighlighter highlighter, ChooseFile chooseFile, AskPassManager askPass, PDFViewer pdfViewer, HTMLPreview htmlPreview, ProfilerPresenter prof, ShinyApplication sApp, PlumberAPI sAPI, DependencyManager dm, ApplicationVisibility av, RmdOutput rmdOutput, ProjectTemplateRegistryProvider provider, WorkbenchServerOperations serverOperations, AskSecretManager askSecret)109    public Workbench(WorkbenchMainView view,
110                     WorkbenchContext workbenchContext,
111                     GlobalDisplay globalDisplay,
112                     Commands commands,
113                     EventBus eventBus,
114                     Session session,
115                     Provider<UserPrefs> pPrefs,
116                     Server server,
117                     RemoteFileSystemContext fsContext,
118                     FileDialogs fileDialogs,
119                     FileTypeRegistry fileTypeRegistry,
120                     ConsoleDispatcher consoleDispatcher,
121                     WorkbenchNewSession newSession,
122                     ProjectOpener projectOpener,
123                     Provider<GitState> pGitState,
124                     SourceWindowManager sourceWindowManager,
125                     UserInterfaceHighlighter highlighter,       // force gin to create
126                     ChooseFile chooseFile,                      // force gin to create
127                     AskPassManager askPass,                     // force gin to create
128                     PDFViewer pdfViewer,                        // force gin to create
129                     HTMLPreview htmlPreview,                    // force gin to create
130                     ProfilerPresenter prof,                     // force gin to create
131                     ShinyApplication sApp,                      // force gin to create
132                     PlumberAPI sAPI,                            // force gin to create
133                     DependencyManager dm,                       // force gin to create
134                     ApplicationVisibility av,                   // force gin to create
135                     RmdOutput rmdOutput,                        // force gin to create
136                     ProjectTemplateRegistryProvider provider,   // force gin to create
137                     WorkbenchServerOperations serverOperations, // force gin to create
138                     AskSecretManager askSecret)                 // force gin to create
139   {
140       view_ = view;
141       workbenchContext_ = workbenchContext;
142       projectOpener_ = projectOpener;
143       globalDisplay_ = globalDisplay;
144       commands_ = commands;
145       eventBus_ = eventBus;
146       session_ = session;
147       pPrefs_ = pPrefs;
148       server_ = server;
149       fsContext_ = fsContext;
150       fileDialogs_ = fileDialogs;
151       fileTypeRegistry_ = fileTypeRegistry;
152       consoleDispatcher_ = consoleDispatcher;
153       pGitState_ = pGitState;
154       newSession_ = newSession;
155       serverOperations_ = serverOperations;
156       sourceWindowManager_ = sourceWindowManager;
157 
158       ((Binder)GWT.create(Binder.class)).bind(commands, this);
159 
160       // edit
161       eventBus.addHandler(BusyEvent.TYPE, this);
162       eventBus.addHandler(ShowErrorMessageEvent.TYPE, this);
163       eventBus.addHandler(UserPromptEvent.TYPE, this);
164       eventBus.addHandler(ShowWarningBarEvent.TYPE, this);
165       eventBus.addHandler(BrowseUrlEvent.TYPE, this);
166       eventBus.addHandler(QuotaStatusEvent.TYPE, this);
167       eventBus.addHandler(WorkbenchLoadedEvent.TYPE, this);
168       eventBus.addHandler(WorkbenchMetricsChangedEvent.TYPE, this);
169       eventBus.addHandler(InstallRtoolsEvent.TYPE, this);
170       eventBus.addHandler(ShinyGadgetDialogEvent.TYPE, this);
171       eventBus.addHandler(ExecuteUserCommandEvent.TYPE, this);
172       eventBus.addHandler(AdminNotificationEvent.TYPE, this);
173       eventBus.addHandler(OpenFileDialogEvent.TYPE, this);
174       eventBus.addHandler(ShowPageViewerEvent.TYPE, this);
175       eventBus.addHandler(TutorialLaunchEvent.TYPE, this);
176       eventBus.addHandler(DeferredInitCompletedEvent.TYPE, this);
177       eventBus.addHandler(ReportShortcutBindingEvent.TYPE, this);
178 
179       // We don't want to send setWorkbenchMetrics more than once per 1/2-second
180       metricsChangedCommand_ = new TimeBufferedCommand(500)
181       {
182          @Override
183          protected void performAction(boolean shouldSchedulePassive)
184          {
185             assert !shouldSchedulePassive;
186 
187             server_.setWorkbenchMetrics(lastWorkbenchMetrics_,
188                                         new VoidServerRequestCallback());
189          }
190       };
191    }
192 
getMainView()193    public WorkbenchMainView getMainView()
194    {
195       return view_;
196    }
197 
onWorkbenchLoaded(WorkbenchLoadedEvent event)198    public void onWorkbenchLoaded(WorkbenchLoadedEvent event)
199    {
200       server_.initializeForMainWorkbench();
201 
202       FileSystemItem defaultDialogDir =
203             session_.getSessionInfo().getActiveProjectDir();
204       if (defaultDialogDir != null)
205          workbenchContext_.setDefaultFileDialogDir(defaultDialogDir);
206 
207       checkForInitMessages();
208       checkForLicenseMessage();
209 
210       RStudioGinjector.INSTANCE.getFocusVisiblePolyfill().load(null);
211 
212       if (Desktop.isDesktop() &&
213           StringUtil.equals(session_.getSessionInfo().getVcsName(), VCSConstants.GIT_ID))
214       {
215          pGitState_.get().addVcsRefreshHandler(vcsRefreshEvent ->
216          {
217             String title = workbenchContext_.createWindowTitle();
218             if (title != null)
219                Desktop.getFrame().setWindowTitle(title);
220          });
221       }
222    }
223 
onTutorialLaunch(final TutorialLaunchEvent event)224    public void onTutorialLaunch(final TutorialLaunchEvent event)
225    {
226       commands_.activateTutorial().execute();
227 
228       Tutorial tutorial = event.getTutorial();
229       Scheduler.get().scheduleDeferred(() -> {
230 
231          TutorialCommandEvent commandEvent = new TutorialCommandEvent(
232                TutorialCommandEvent.TYPE_LAUNCH_DEFAULT_TUTORIAL,
233                tutorial.toJsObject());
234 
235          eventBus_.fireEvent(commandEvent);
236 
237       });
238    }
239 
onDeferredInitCompleted(DeferredInitCompletedEvent ev)240    public void onDeferredInitCompleted(DeferredInitCompletedEvent ev)
241    {
242       checkForCrashHandlerPermission();
243    }
244 
onBusy(BusyEvent event)245    public void onBusy(BusyEvent event)
246    {
247    }
248 
onShowErrorMessage(ShowErrorMessageEvent event)249    public void onShowErrorMessage(ShowErrorMessageEvent event)
250    {
251       ErrorMessage errorMessage = event.getErrorMessage();
252       globalDisplay_.showErrorMessage(errorMessage.getTitle(),
253                                       errorMessage.getMessage());
254 
255    }
256 
257    @Override
onShowWarningBar(ShowWarningBarEvent event)258    public void onShowWarningBar(ShowWarningBarEvent event)
259    {
260       globalDisplay_.showWarningBar(event.isSevere(), event.getMessage());
261    }
262 
onBrowseUrl(BrowseUrlEvent event)263    public void onBrowseUrl(BrowseUrlEvent event)
264    {
265       BrowseUrlInfo urlInfo = event.getUrlInfo();
266       NewWindowOptions newWindowOptions = new NewWindowOptions();
267       newWindowOptions.setName(urlInfo.getWindow());
268       globalDisplay_.openWindow(urlInfo.getUrl(), newWindowOptions);
269    }
270 
onWorkbenchMetricsChanged(WorkbenchMetricsChangedEvent event)271    public void onWorkbenchMetricsChanged(WorkbenchMetricsChangedEvent event)
272    {
273       lastWorkbenchMetrics_ = event.getWorkbenchMetrics();
274       metricsChangedCommand_.nudge();
275    }
276 
onQuotaStatus(QuotaStatusEvent event)277    public void onQuotaStatus(QuotaStatusEvent event)
278    {
279       QuotaStatus quotaStatus = event.getQuotaStatus();
280 
281       // always show warning if the user is over quota
282       if (quotaStatus.isOverQuota())
283       {
284          long over = quotaStatus.getUsed() - quotaStatus.getQuota();
285          StringBuilder msg = new StringBuilder();
286          msg.append("You are ");
287          msg.append(StringUtil.formatFileSize(over));
288          msg.append(" over your ");
289          msg.append(StringUtil.formatFileSize(quotaStatus.getQuota()));
290          msg.append(" file storage limit. Please remove files to ");
291          msg.append("continue working.");
292          globalDisplay_.showWarningBar(false, msg.toString());
293       }
294 
295       // show a warning if the user is near their quota (but no more
296       // than one time per instantiation of the application)
297       else if (quotaStatus.isNearQuota() && !nearQuotaWarningShown_)
298       {
299          StringBuilder msg = new StringBuilder();
300          msg.append("You are nearly over your ");
301          msg.append(StringUtil.formatFileSize(quotaStatus.getQuota()));
302          msg.append(" file storage limit.");
303          globalDisplay_.showWarningBar(false, msg.toString());
304 
305          nearQuotaWarningShown_ = true;
306       }
307    }
308 
309    @Override
onShinyGadgetDialog(ShinyGadgetDialogEvent event)310    public void onShinyGadgetDialog(ShinyGadgetDialogEvent event)
311    {
312       new ShinyGadgetDialog(event.getCaption(),
313                             event.getUrl(),
314                             new Size(event.getPreferredWidth(),
315                                      event.getPreferredHeight())).showModal();
316    }
317 
318    @Handler
onSetWorkingDir()319    public void onSetWorkingDir()
320    {
321       fileDialogs_.chooseFolder(
322             "Choose Working Directory",
323             fsContext_,
324             workbenchContext_.getCurrentWorkingDir(),
325             new ProgressOperationWithInput<FileSystemItem>()
326             {
327                public void execute(FileSystemItem input,
328                                    ProgressIndicator indicator)
329                {
330                   if (input == null)
331                      return;
332 
333                   // set console
334                   consoleDispatcher_.executeSetWd(input, true);
335 
336                   // set files pane
337                   eventBus_.fireEvent(new DirectoryNavigateEvent(input));
338 
339                   indicator.onCompleted();
340                }
341             });
342    }
343 
344    @Handler
onSetWorkingDirToProjectDir()345    void onSetWorkingDirToProjectDir()
346    {
347       FileSystemItem projectDir = session_.getSessionInfo()
348             .getActiveProjectDir();
349       if (projectDir != null)
350       {
351          consoleDispatcher_.executeSetWd(projectDir, false);
352          eventBus_.fireEvent(new DirectoryNavigateEvent(projectDir, true));
353       }
354    }
355 
356    @Handler
onNewSession()357    void onNewSession()
358    {
359       newSession_.openNewSession(globalDisplay_,
360                                  workbenchContext_,
361                                  serverOperations_,
362                                  projectOpener_,
363                                  server_);
364    }
365 
366    @Handler
onSourceFile()367    public void onSourceFile()
368    {
369       fileDialogs_.openFile(
370             "Source File",
371             fsContext_,
372             workbenchContext_.getCurrentWorkingDir(),
373             new ProgressOperationWithInput<FileSystemItem>()
374             {
375                public void execute(FileSystemItem input, ProgressIndicator indicator)
376                {
377                   if (input == null)
378                      return;
379 
380                   indicator.onCompleted();
381 
382                   consoleDispatcher_.executeSourceCommand(
383                         input.getPath(),
384                         fileTypeRegistry_.getTextTypeForFile(input),
385                         pPrefs_.get().defaultEncoding().getValue(),
386                         false,
387                         false,
388                         true,
389                         false);
390 
391                   commands_.activateConsole().execute();
392                }
393             });
394    }
395 
396    @Handler
onVersionControlShowRsaKey()397    public void onVersionControlShowRsaKey()
398    {
399       final ProgressIndicator indicator = new GlobalProgressDelayer(
400             globalDisplay_, 500, "Reading RSA public key...").getIndicator();
401 
402       // compute path to public key
403       String sshDir = session_.getSessionInfo().getDefaultSSHKeyDir();
404       final String keyPath = FileSystemItem.createDir(sshDir).completePath(
405                                                                "id_rsa.pub");
406 
407       // read it
408       server_.gitSshPublicKey(keyPath, new ServerRequestCallback<String> () {
409 
410          @Override
411          public void onResponseReceived(String publicKeyContents)
412          {
413             indicator.onCompleted();
414 
415             new ShowPublicKeyDialog("RSA Public Key",
416                                     publicKeyContents).showModal();
417          }
418 
419          @Override
420          public void onError(ServerError error)
421          {
422             String msg = "Error attempting to read key '" + keyPath + "' (" +
423                          error.getUserMessage() + ")";
424             indicator.onError(msg);
425          }
426       });
427    }
428 
429    @Handler
onShowShellDialog()430    public void onShowShellDialog()
431    {
432       if (Desktop.isDesktop())
433       {
434          server_.getTerminalOptions(new SimpleRequestCallback<TerminalOptions>()
435          {
436             @Override
437             public void onResponseReceived(TerminalOptions options)
438             {
439                Desktop.getFrame().openTerminal(
440                      StringUtil.notNull(options.getTerminalPath()),
441                      StringUtil.notNull(options.getWorkingDirectory()),
442                      StringUtil.notNull(options.getExtraPathEntries()),
443                      options.getShellType());
444             }
445          });
446       }
447       else
448       {
449          eventBus_.fireEvent(new ActivateNamedTerminalEvent());
450       }
451    }
452 
453    @Handler
onBrowseAddins()454    public void onBrowseAddins()
455    {
456       BrowseAddinsDialog dialog = new BrowseAddinsDialog(new OperationWithInput<Command>()
457       {
458          @Override
459          public void execute(Command input)
460          {
461             if (input != null)
462                input.execute();
463          }
464       });
465       dialog.showModal();
466    }
467 
468    @Handler
onModifyKeyboardShortcuts()469    public void onModifyKeyboardShortcuts()
470    {
471       new ModifyKeyboardShortcutsWidget().showModal();
472    }
473 
474    @Handler
onEditCodeSnippets()475    public void onEditCodeSnippets()
476    {
477       new EditSnippetsDialog().showModal();
478    }
479 
480    @Handler
onToggleFullScreen()481    public void onToggleFullScreen()
482    {
483       if (Desktop.hasDesktopFrame())
484          Desktop.getFrame().toggleFullscreenMode();
485    }
486 
487    @Handler
onShowFileMenu()488    public void onShowFileMenu()
489    {
490       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.File));
491    }
492 
493    @Handler
onShowEditMenu()494    public void onShowEditMenu()
495    {
496       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.Edit));
497    }
498 
499    @Handler
onShowCodeMenu()500    public void onShowCodeMenu()
501    {
502       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.Code));
503    }
504 
505    @Handler
onShowViewMenu()506    public void onShowViewMenu()
507    {
508       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.View));
509    }
510 
511    @Handler
onShowPlotsMenu()512    public void onShowPlotsMenu()
513    {
514       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.Plots));
515    }
516 
517    @Handler
onShowSessionMenu()518    public void onShowSessionMenu()
519    {
520       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.Session));
521    }
522 
523    @Handler
onShowBuildMenu()524    public void onShowBuildMenu()
525    {
526       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.Build));
527    }
528 
529    @Handler
onShowDebugMenu()530    public void onShowDebugMenu()
531    {
532       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.Debug));
533    }
534 
535    @Handler
onShowProfileMenu()536    public void onShowProfileMenu()
537    {
538       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.Profile));
539    }
540 
541    @Handler
onShowToolsMenu()542    public void onShowToolsMenu()
543    {
544       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.Tools));
545    }
546 
547    @Handler
onShowHelpMenu()548    public void onShowHelpMenu()
549    {
550       eventBus_.fireEvent(new ShowMainMenuEvent(Menu.Help));
551    }
552 
checkForInitMessages()553    private void checkForInitMessages()
554    {
555       if (!Desktop.isDesktop())
556       {
557          server_.getInitMessages(new ServerRequestCallback<String>() {
558             @Override
559             public void onResponseReceived(String message)
560             {
561                if (message != null)
562                   globalDisplay_.showWarningBar(false, message);
563             }
564 
565             @Override
566             public void onError(ServerError error)
567             {
568                // ignore
569             }
570          });
571       }
572       else
573       {
574          Desktop.getFrame().getInitMessages(message ->
575          {
576             if (!StringUtil.isNullOrEmpty(message))
577             {
578                globalDisplay_.showLicenseWarningBar(false, message);
579             }
580          });
581       }
582    }
583 
checkForLicenseMessage()584    private void checkForLicenseMessage()
585    {
586       String licenseMessage = session_.getSessionInfo().getLicenseMessage();
587       if (!StringUtil.isNullOrEmpty(licenseMessage))
588       {
589          globalDisplay_.showLicenseWarningBar(false, licenseMessage);
590       }
591    }
592 
checkForCrashHandlerPermission()593    private void checkForCrashHandlerPermission()
594    {
595       boolean shouldPrompt = session_.getSessionInfo().getPromptForCrashHandlerPermission();
596       if (shouldPrompt)
597       {
598          String message =
599                "May we upload crash reports to RStudio automatically?\n\nCrash reports don't include " +
600                "any personal information, except for IP addresses which are used to determine how many users " +
601                "are affected by each crash.\n\nCrash reporting can be disabled at any time under the Global Options.";
602 
603          globalDisplay_.showYesNoMessage(GlobalDisplay.MSG_QUESTION,
604                "Enable Automated Crash Reporting",
605                message,
606                false,
607                new Operation() {
608                   @Override
609                   public void execute() {
610                      server_.setUserCrashHandlerPrompted(true, new SimpleRequestCallback<>());
611                   }
612                },
613                new Operation() {
614                   @Override
615                   public void execute() {
616                      server_.setUserCrashHandlerPrompted(false, new SimpleRequestCallback<>());
617                   }
618                },
619                true);
620       }
621    }
622 
onUserPrompt(UserPromptEvent event)623    public void onUserPrompt(UserPromptEvent event)
624    {
625       // is cancel supported?
626       UserPrompt userPrompt = event.getUserPrompt();
627 
628       // resolve labels
629       String yesLabel = userPrompt.getYesLabel();
630       if (StringUtil.isNullOrEmpty(yesLabel))
631          yesLabel = "Yes";
632       String noLabel = userPrompt.getNoLabel();
633       if (StringUtil.isNullOrEmpty(noLabel))
634          noLabel = "No";
635 
636       // show dialog
637       globalDisplay_.showYesNoMessage(
638                  userPrompt.getType(),
639                  userPrompt.getCaption(),
640                  userPrompt.getMessage(),
641                  userPrompt.getIncludeCancel(),
642                  userPromptResponse(UserPrompt.RESPONSE_YES),
643                  userPromptResponse(UserPrompt.RESPONSE_NO),
644                  userPrompt.getIncludeCancel() ?
645                        userPromptResponse(UserPrompt.RESPONSE_CANCEL) : null,
646                  yesLabel,
647                  noLabel,
648                  userPrompt.getYesIsDefault());
649    }
650 
userPromptResponse(final int response)651    private Operation userPromptResponse(final int response)
652    {
653       return new Operation() {
654          public void execute()
655          {
656             server_.userPromptCompleted(response,
657                                         new SimpleRequestCallback<>());
658 
659          }
660       };
661    }
662 
663    public void onAdminNotification(AdminNotificationEvent event)
664    {
665       AdminNotification notification = event.getAdminNotification();
666 
667       // show dialog
668       globalDisplay_.showMessage(notification.getType(),
669                                  "Admin Notification",
670                                  notification.getMessage(),
671                                  adminNotificationAcknowledged(notification.getId()));
672    }
673 
674    @Override
675    public void onOpenFileDialog(OpenFileDialogEvent event)
676    {
677       final ProgressOperationWithInput<FileSystemItem> onSelected =
678             new ProgressOperationWithInput<FileSystemItem>()
679       {
680          @Override
681          public void execute(FileSystemItem input,
682                              ProgressIndicator indicator)
683          {
684             indicator.onCompleted();
685 
686             server_.openFileDialogCompleted(
687                   input == null ? "" : input.getPath(),
688                   new VoidServerRequestCallback());
689          }
690       };
691 
692       String caption = event.getCaption();
693       String label = event.getLabel();
694       int type = event.getType();
695       FileSystemItem initialFilePath = event.getFile();
696       String filter = event.getFilter();
697       boolean selectExisting = event.selectExisting();
698 
699       if (type == OpenFileDialogEvent.TYPE_SELECT_FILE)
700       {
701          if (selectExisting)
702          {
703             fileDialogs_.openFile(
704                   caption,
705                   label,
706                   fsContext_,
707                   initialFilePath,
708                   filter,
709                   false,
710                   false,
711                   onSelected);
712          }
713          else
714          {
715             fileDialogs_.saveFile(
716                   caption,
717                   label,
718                   fsContext_,
719                   initialFilePath,
720                   "",
721                   false,
722                   false,
723                   onSelected);
724          }
725       }
726       else if (type == OpenFileDialogEvent.TYPE_SELECT_DIRECTORY)
727       {
728          fileDialogs_.chooseFolder(
729                caption,
730                label,
731                fsContext_,
732                initialFilePath,
733                false,
734                onSelected);
735       }
736       else
737       {
738          assert false: "unexpected file dialog type '" + type + "'";
739          server_.openFileDialogCompleted(null, new VoidServerRequestCallback());
740       }
741    }
742 
743    private Operation adminNotificationAcknowledged(final String id)
744    {
745       return new Operation() {
746          public void execute()
747          {
748             server_.adminNotificationAcknowledged(id, new SimpleRequestCallback<>());
749          }
750       };
751    }
752 
753    @Override
754    public void onInstallRtools(final InstallRtoolsEvent event)
755    {
756       if (BrowseCap.isWindowsDesktop())
757       {
758          Desktop.getFrame().installRtools(StringUtil.notNull(event.getVersion()),
759                                           StringUtil.notNull(event.getInstallerPath()));
760       }
761    }
762 
763    @Override
764    public void onShowPageViewer(ShowPageViewerEvent event)
765    {
766       // show the page viewer window
767       HTMLPreviewParams params = event.getParams();
768       eventBus_.fireEvent(new ShowHTMLPreviewEvent(params));
769 
770       // server will now take care of sending the html_preview_completed event
771    }
772 
773    @Override
774    public void onExecuteUserCommand(ExecuteUserCommandEvent event)
775    {
776       server_.executeUserCommand(event.getCommandName(), new VoidServerRequestCallback());
777    }
778 
779    @Override
780    public void onReportShortcutBinding(ReportShortcutBindingEvent event)
781    {
782       AppCommand command = commands_.getCommandById(event.getCommand());
783       if (command == null)
784          globalDisplay_.showWarningBar(false, event.getCommand());
785       else
786          globalDisplay_.showWarningBar(false, event.getCommand() + " : " + command.summarize());
787    }
788 
789    private final Server server_;
790    private final WorkbenchServerOperations serverOperations_;
791    private final EventBus eventBus_;
792    private final Session session_;
793    private final Provider<UserPrefs> pPrefs_;
794    private final WorkbenchMainView view_;
795    private final GlobalDisplay globalDisplay_;
796    private final Commands commands_;
797    private final RemoteFileSystemContext fsContext_;
798    private final FileDialogs fileDialogs_;
799    private final FileTypeRegistry fileTypeRegistry_;
800    private final WorkbenchContext workbenchContext_;
801    private final ProjectOpener projectOpener_;
802    private final ConsoleDispatcher consoleDispatcher_;
803    private final Provider<GitState> pGitState_;
804    private final TimeBufferedCommand metricsChangedCommand_;
805    private WorkbenchMetrics lastWorkbenchMetrics_;
806    private final WorkbenchNewSession newSession_;
807    private boolean nearQuotaWarningShown_ = false;
808 
809    @SuppressWarnings("unused") private final SourceWindowManager sourceWindowManager_;
810 }
811