1 /*
2  * ApplicationWindow.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.application.ui;
17 
18 import com.google.gwt.aria.client.Roles;
19 import com.google.gwt.dom.client.Style;
20 import com.google.gwt.dom.client.Style.Unit;
21 import com.google.gwt.user.client.Timer;
22 import com.google.gwt.user.client.Window;
23 import com.google.gwt.user.client.ui.*;
24 import com.google.inject.Inject;
25 import com.google.inject.Provider;
26 import com.google.inject.Singleton;
27 import org.rstudio.core.client.a11y.A11y;
28 import org.rstudio.core.client.widget.AriaLiveStatusWidget;
29 import org.rstudio.core.client.widget.Operation;
30 import org.rstudio.studio.client.application.ApplicationView;
31 import org.rstudio.studio.client.application.AriaLiveService;
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.EventBus;
35 import org.rstudio.studio.client.application.ui.appended.ApplicationEndedPopupPanel;
36 import org.rstudio.studio.client.application.ui.serializationprogress.ApplicationSerializationProgress;
37 import org.rstudio.studio.client.common.GlobalDisplay;
38 import org.rstudio.studio.client.palette.CommandPaletteLauncher;
39 import org.rstudio.studio.client.workbench.prefs.model.UserPrefs;
40 
41 @Singleton
42 public class ApplicationWindow extends Composite
43                                implements ApplicationView,
44                                           RequiresResize,
45                                           ProvidesResize
46 {
47    @Inject
ApplicationWindow(ApplicationHeader applicationHeader, GlobalDisplay globalDisplay, Provider<UserPrefs> pPrefs, EventBus events, Provider<WarningBar> pWarningBar, AriaLiveService ariaLive, CodeSearchLauncher launcher, CommandPaletteLauncher paletteLauncher)48    public ApplicationWindow(ApplicationHeader applicationHeader,
49                             GlobalDisplay globalDisplay,
50                             Provider<UserPrefs> pPrefs,
51                             EventBus events,
52                             Provider<WarningBar> pWarningBar,
53                             AriaLiveService ariaLive,
54                             CodeSearchLauncher launcher,
55                             CommandPaletteLauncher paletteLauncher)
56    {
57       globalDisplay_ = globalDisplay;
58       events_ = events;
59       pPrefs_ = pPrefs;
60       pWarningBar_ = pWarningBar;
61       ariaLive_ = ariaLive;
62 
63       // occupy full client area of the window
64       Window.enableScrolling(false);
65       Window.setMargin("0px");
66 
67       // app ui contained within a vertical panel
68       applicationPanel_ = new LayoutPanel();
69 
70       // header bar
71       applicationHeader_ = applicationHeader;
72       Widget applicationHeaderWidget = applicationHeader_.asWidget();
73       applicationHeaderWidget.setWidth("100%");
74       applicationPanel_.add(applicationHeader_);
75       updateHeaderTopBottom();
76       applicationHeaderWidget.setVisible(false);
77 
78       // aria-live status announcements
79       ariaLiveStatusWidget_ = new AriaLiveStatusWidget();
80       applicationPanel_.add(ariaLiveStatusWidget_);
81       A11y.setVisuallyHidden(applicationPanel_.getWidgetContainerElement(ariaLiveStatusWidget_));
82 
83       // main view container
84       initWidget(applicationPanel_);
85    }
86 
87    @Override
showToolbar(boolean showToolbar, boolean announce)88    public void showToolbar(boolean showToolbar, boolean announce)
89    {
90       boolean currentVisibility = isToolbarShowing();
91       applicationHeader_.showToolbar(showToolbar);
92       updateHeaderTopBottom();
93       updateWorkbenchTopBottom();
94       applicationPanel_.forceLayout();
95       if (announce && showToolbar != currentVisibility)
96          ariaLive_.announce(AriaLiveService.TOOLBAR_VISIBILITY,
97                showToolbar ? "Main toolbar visible" : "Main toolbar hidden",
98                Timing.IMMEDIATE, Severity.STATUS);
99    }
100 
101    @Override
isToolbarShowing()102    public boolean isToolbarShowing()
103    {
104       return applicationHeader_.isToolbarVisible();
105    }
106 
107    @Override
focusToolbar()108    public void focusToolbar()
109    {
110       if (!isToolbarShowing())
111       {
112          ariaLive_.announce(AriaLiveService.TOOLBAR_VISIBILITY,
113                "Toolbar hidden, unable to focus.",
114                Timing.IMMEDIATE, Severity.STATUS);
115          return;
116       }
117       applicationHeader_.focusToolbar();
118    }
119 
getWidget()120    public Widget getWidget()
121    {
122       return this;
123    }
124 
125    @Override
showApplicationQuit()126    public void showApplicationQuit()
127    {
128       ApplicationEndedPopupPanel.showQuit();
129    }
130 
131    @Override
showApplicationMultiSessionQuit()132    public void showApplicationMultiSessionQuit()
133    {
134       ApplicationEndedPopupPanel.showMultiSessionQuit();
135    }
136 
137    @Override
showApplicationSuicide(String reason)138    public void showApplicationSuicide(String reason)
139    {
140       ApplicationEndedPopupPanel.showSuicide(reason);
141    }
142 
143    @Override
showApplicationDisconnected()144    public void showApplicationDisconnected()
145    {
146       ApplicationEndedPopupPanel.showDisconnected();
147    }
148 
149    @Override
showApplicationOffline()150    public void showApplicationOffline()
151    {
152       ApplicationEndedPopupPanel.showOffline();
153    }
154 
155    @Override
showApplicationUpdateRequired()156    public void showApplicationUpdateRequired()
157    {
158       globalDisplay_.showMessage(
159             GlobalDisplay.MSG_INFO,
160             "Application Updated",
161             "An updated version of RStudio is available. Your browser will " +
162             "now be refreshed with the new version. All current work and data " +
163             "will be preserved during the update.",
164             new Operation() {
165                public void execute()
166                {
167                   Window.Location.reload();
168                }
169 
170             });
171    }
172 
173    @Override
showWorkbenchView(Widget workbenchScreen)174    public void showWorkbenchView(Widget workbenchScreen)
175    {
176       workbenchScreen_ = workbenchScreen;
177 
178       applicationHeader_.asWidget().setVisible(true);
179       applicationPanel_.add(workbenchScreen_);
180       updateWorkbenchTopBottom();
181       applicationPanel_.setWidgetLeftRight(workbenchScreen_,
182                                            COMPONENT_SPACING,
183                                            Style.Unit.PX,
184                                            COMPONENT_SPACING,
185                                            Style.Unit.PX);
186    }
187 
showWarning(boolean severe, String message, boolean showLicenseButton)188    private void showWarning(boolean severe, String message, boolean showLicenseButton)
189    {
190       if (warningBar_ == null)
191       {
192          warningBar_ = pWarningBar_.get();
193          Roles.getContentinfoRole().set(warningBar_.getElement());
194          Roles.getContentinfoRole().setAriaLabelProperty(warningBar_.getElement(), "Warning bar");
195          warningBar_.addCloseHandler(warningBarCloseEvent -> hideWarning());
196          applicationPanel_.add(warningBar_);
197          applicationPanel_.setWidgetBottomHeight(warningBar_,
198                                                  COMPONENT_SPACING,
199                                                  Unit.PX,
200                                                  warningBar_.getHeight(),
201                                                  Unit.PX);
202          applicationPanel_.setWidgetLeftRight(warningBar_,
203                                               COMPONENT_SPACING, Unit.PX,
204                                               COMPONENT_SPACING, Unit.PX);
205 
206          workbenchBottom_ = COMPONENT_SPACING*2 + warningBar_.getHeight();
207          if (workbenchScreen_ != null)
208             updateWorkbenchTopBottom();
209 
210          applicationPanel_.animate(250);
211       }
212       warningBar_.setSeverity(severe);
213       warningBar_.setText(message);
214       warningBar_.showLicenseButton(showLicenseButton);
215    }
216 
217    @Override
showLicenseWarning(boolean severe, String message)218    public void showLicenseWarning(boolean severe, String message)
219    {
220       showWarning(severe, message, true);
221 
222    }
223 
224    @Override
showWarning(boolean severe, String message)225    public void showWarning(boolean severe, String message)
226    {
227       showWarning(severe, message, false);
228    }
229 
updateHeaderTopBottom()230    private void updateHeaderTopBottom()
231    {
232       int headerHeight = applicationHeader_.getPreferredHeight();
233       applicationPanel_.setWidgetTopHeight(applicationHeader_,
234                                            0,
235                                            Style.Unit.PX,
236                                            headerHeight,
237                                            Style.Unit.PX);
238       applicationPanel_.setWidgetLeftRight(applicationHeader_,
239                                            0,
240                                            Style.Unit.PX,
241                                            0,
242                                            Style.Unit.PX);
243    }
244 
updateWorkbenchTopBottom()245    private void updateWorkbenchTopBottom()
246    {
247       applicationPanel_.setWidgetTopBottom(
248             workbenchScreen_,
249             applicationHeader_.getPreferredHeight(),
250             Unit.PX,
251             workbenchBottom_,
252             Unit.PX);
253    }
254 
255    @Override
hideWarning()256    public void hideWarning()
257    {
258       if (warningBar_ != null)
259       {
260          applicationPanel_.remove(warningBar_);
261          warningBar_ = null;
262 
263          workbenchBottom_ = COMPONENT_SPACING;
264          if (workbenchScreen_ != null)
265             updateWorkbenchTopBottom();
266 
267          applicationPanel_.animate(250);
268       }
269    }
270 
271    @Override
showSessionAbendWarning()272    public void showSessionAbendWarning()
273    {
274       globalDisplay_.showErrorMessage(
275             "R Session Error",
276             "The previous R session was abnormally terminated due to " +
277             "an unexpected crash.\n\n" +
278             "You may have lost workspace data as a result of this crash.");
279    }
280 
281    @Override
reportStatus(String message, int delayMs, Severity severity)282    public void reportStatus(String message, int delayMs, Severity severity)
283    {
284       ariaLiveStatusWidget_.reportStatus(message, delayMs, severity);
285    }
286 
287    @Override
showSerializationProgress(String msg, boolean modal, int delayMs, int timeoutMs)288    public void showSerializationProgress(String msg,
289                                          boolean modal,
290                                          int delayMs,
291                                          int timeoutMs)
292    {
293       // hide any existing progress
294       hideSerializationProgress();
295 
296       // create and show progress
297       activeSerializationProgress_ =
298                     new ApplicationSerializationProgress(msg, modal, delayMs,
299                           !ariaLive_.isDisabled(AriaLiveService.SESSION_STATE));
300 
301       // implement timeout for *this* serialization progress instance if
302       // requested (check to ensure the same instance because another
303       // serialization progress could occur in the meantime and we don't
304       // want to hide it)
305       if (timeoutMs > 0)
306       {
307          final ApplicationSerializationProgress timeoutSerializationProgress =
308                                                    activeSerializationProgress_;
309          new Timer() {
310             @Override
311             public void run()
312             {
313                if (timeoutSerializationProgress == activeSerializationProgress_)
314                   hideSerializationProgress();
315             }
316          }.schedule(timeoutMs);
317       }
318    }
319 
320    @Override
hideSerializationProgress()321    public void hideSerializationProgress()
322    {
323       if (activeSerializationProgress_ != null)
324       {
325          activeSerializationProgress_.hide();
326          activeSerializationProgress_ = null;
327       }
328    }
329 
330    @Override
onResize()331    public void onResize()
332    {
333       applicationPanel_.onResize();
334    }
335 
336    // main application UI components
337    private LayoutPanel applicationPanel_;
338    private ApplicationHeader applicationHeader_;
339 
340    // active serialization progress message
341    private ApplicationSerializationProgress activeSerializationProgress_;
342 
343    private static final int COMPONENT_SPACING = 6;
344    private Widget workbenchScreen_;
345    private WarningBar warningBar_;
346    private final AriaLiveStatusWidget ariaLiveStatusWidget_;
347    private int workbenchBottom_ = COMPONENT_SPACING;
348    private final GlobalDisplay globalDisplay_;
349    @SuppressWarnings("unused")
350    private final EventBus events_;
351    @SuppressWarnings("unused")
352    private final Provider<UserPrefs> pPrefs_;
353    private final AriaLiveService ariaLive_;
354    private final Provider<WarningBar> pWarningBar_;
355 }