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 }