1 /* 2 * SVNReviewPanel.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.views.vcs.svn.dialog; 16 17 import com.google.gwt.core.client.GWT; 18 import com.google.gwt.core.client.Scheduler; 19 import com.google.gwt.core.client.Scheduler.ScheduledCommand; 20 import com.google.gwt.dom.client.Document; 21 import com.google.gwt.dom.client.NativeEvent; 22 import com.google.gwt.dom.client.Style.Unit; 23 import com.google.gwt.event.dom.client.*; 24 import com.google.gwt.event.logical.shared.ValueChangeEvent; 25 import com.google.gwt.event.logical.shared.ValueChangeHandler; 26 import com.google.gwt.event.shared.GwtEvent; 27 import com.google.gwt.event.shared.HandlerManager; 28 import com.google.gwt.event.shared.HandlerRegistration; 29 import com.google.gwt.resources.client.ClientBundle; 30 import com.google.gwt.resources.client.ImageResource; 31 import com.google.gwt.resources.client.ImageResource.ImageOptions; 32 import com.google.gwt.resources.client.ImageResource.RepeatStyle; 33 import com.google.gwt.uibinder.client.UiBinder; 34 import com.google.gwt.uibinder.client.UiField; 35 import com.google.gwt.user.client.Command; 36 import com.google.gwt.user.client.Event; 37 import com.google.gwt.user.client.Event.NativePreviewEvent; 38 import com.google.gwt.user.client.Event.NativePreviewHandler; 39 import com.google.gwt.user.client.ui.*; 40 import com.google.gwt.user.client.ui.PopupPanel.PositionCallback; 41 import com.google.inject.Inject; 42 import org.rstudio.core.client.WidgetHandlerRegistration; 43 import org.rstudio.core.client.command.KeyboardShortcut; 44 import org.rstudio.core.client.resources.ImageResource2x; 45 import org.rstudio.core.client.widget.FormLabel; 46 import org.rstudio.core.client.widget.LeftRightToggleButton; 47 import org.rstudio.core.client.widget.Toolbar; 48 import org.rstudio.core.client.widget.ToolbarButton; 49 import org.rstudio.core.client.widget.ToolbarPopupMenu; 50 import org.rstudio.studio.client.common.vcs.GitServerOperations.PatchMode; 51 import org.rstudio.studio.client.common.vcs.StatusAndPath; 52 import org.rstudio.studio.client.workbench.commands.Commands; 53 import org.rstudio.studio.client.workbench.views.vcs.common.ChangelistTable; 54 import org.rstudio.studio.client.workbench.views.vcs.common.diff.ChunkOrLine; 55 import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTablePresenter; 56 import org.rstudio.studio.client.workbench.views.vcs.common.diff.LineTableView; 57 import org.rstudio.studio.client.workbench.views.vcs.dialog.SharedStyles; 58 import org.rstudio.studio.client.workbench.views.vcs.dialog.SizeWarningWidget; 59 import org.rstudio.studio.client.workbench.views.vcs.svn.SVNChangelistTablePresenter; 60 import org.rstudio.studio.client.workbench.views.vcs.svn.dialog.SVNReviewPresenter.Display; 61 62 import java.util.ArrayList; 63 64 public class SVNReviewPanel extends ResizeComposite implements Display 65 { 66 interface Resources extends ClientBundle 67 { 68 @Source("SVNReviewPanel.css") styles()69 Styles styles(); 70 71 @ImageOptions(repeatStyle = RepeatStyle.Horizontal) 72 @Source("../../dialog/images/toolbarTile.png") toolbarTile()73 ImageResource toolbarTile(); 74 75 @Source("../../dialog/images/stageAllFiles_2x.png") stageAllFiles2x()76 ImageResource stageAllFiles2x(); 77 78 @Source("../../dialog/images/discard_2x.png") discard2x()79 ImageResource discard2x(); 80 81 @Source("../../dialog/images/stage_2x.png") stage2x()82 ImageResource stage2x(); 83 84 @Source("../../dialog/images/splitterTileV.png") 85 @ImageOptions(repeatStyle = RepeatStyle.Horizontal) splitterTileV()86 ImageResource splitterTileV(); 87 88 @Source("../../dialog/images/splitterTileH.png") 89 @ImageOptions(repeatStyle = RepeatStyle.Vertical) splitterTileH()90 ImageResource splitterTileH(); 91 92 @Source("../../dialog/images/blankFileIcon_2x.png") blankFileIcon2x()93 ImageResource blankFileIcon2x(); 94 } 95 96 interface Styles extends SharedStyles 97 { contextLabel()98 String contextLabel(); diffToolbar()99 String diffToolbar(); diffViewOptions()100 String diffViewOptions(); diffContextLines()101 String diffContextLines(); 102 } 103 104 @SuppressWarnings("unused") 105 private static class ClickCommand implements HasClickHandlers, Command 106 { 107 @Override execute()108 public void execute() 109 { 110 ClickEvent.fireNativeEvent( 111 Document.get() 112 .createClickEvent(0, 113 0, 114 0, 115 0, 116 0, 117 false, 118 false, 119 false, 120 false), 121 this); 122 } 123 124 @Override addClickHandler(ClickHandler handler)125 public HandlerRegistration addClickHandler(ClickHandler handler) 126 { 127 return handlerManager_.addHandler(ClickEvent.getType(), handler); 128 } 129 130 @Override fireEvent(GwtEvent<?> event)131 public void fireEvent(GwtEvent<?> event) 132 { 133 handlerManager_.fireEvent(event); 134 } 135 136 private final HandlerManager handlerManager_ = new HandlerManager(this); 137 } 138 139 private static class ListBoxAdapter implements HasValue<Integer> 140 { ListBoxAdapter(ListBox listBox)141 private ListBoxAdapter(ListBox listBox) 142 { 143 listBox_ = listBox; 144 listBox_.addChangeHandler(new ChangeHandler() 145 { 146 @Override 147 public void onChange(ChangeEvent event) 148 { 149 ValueChangeEvent.fire(ListBoxAdapter.this, getValue()); 150 } 151 }); 152 } 153 154 @Override getValue()155 public Integer getValue() 156 { 157 return Integer.parseInt( 158 listBox_.getValue(listBox_.getSelectedIndex())); 159 } 160 161 @Override setValue(Integer value)162 public void setValue(Integer value) 163 { 164 setValue(value, true); 165 } 166 167 @Override setValue(Integer value, boolean fireEvents)168 public void setValue(Integer value, boolean fireEvents) 169 { 170 String valueStr = value.toString(); 171 for (int i = 0; i < listBox_.getItemCount(); i++) 172 { 173 if (listBox_.getValue(i) == valueStr) 174 { 175 listBox_.setSelectedIndex(i); 176 break; 177 } 178 } 179 } 180 181 @Override addValueChangeHandler(ValueChangeHandler<Integer> handler)182 public HandlerRegistration addValueChangeHandler(ValueChangeHandler<Integer> handler) 183 { 184 return handlers_.addHandler(ValueChangeEvent.getType(), handler); 185 } 186 187 @Override fireEvent(GwtEvent<?> event)188 public void fireEvent(GwtEvent<?> event) 189 { 190 handlers_.fireEvent(event); 191 } 192 193 private final ListBox listBox_; 194 private final HandlerManager handlers_ = new HandlerManager(this); 195 } 196 197 interface Binder extends UiBinder<Widget, SVNReviewPanel> 198 { 199 } 200 201 @Inject SVNReviewPanel(SVNChangelistTablePresenter changelist, LineTableView diffPane, Commands commands)202 public SVNReviewPanel(SVNChangelistTablePresenter changelist, 203 LineTableView diffPane, 204 Commands commands) 205 { 206 commands_ = commands; 207 splitPanel_ = new SplitLayoutPanel(4); 208 209 changelist_ = changelist.getView(); 210 lines_ = diffPane; 211 lines_.getElement().setTabIndex(-1); 212 lines_.hideStageCommands(); 213 214 overrideSizeWarning_ = new SizeWarningWidget("diff"); 215 216 topToolbar_ = new Toolbar("SVN Review"); 217 diffToolbar_ = new Toolbar("SVN Diff"); 218 219 changelist.setSelectFirstItemByDefault(true); 220 221 Widget widget = GWT.<Binder>create(Binder.class).createAndBindUi(this); 222 initWidget(widget); 223 224 topToolbar_.addStyleName(RES.styles().toolbar()); 225 topToolbar_.getWrapper().addStyleName(RES.styles().toolbarInnerWrapper()); 226 227 switchViewButton_ = new LeftRightToggleButton("Changes", "History", true); 228 switchViewButton_.getElement().getStyle().setMarginRight(8, Unit.PX); 229 topToolbar_.addLeftWidget(switchViewButton_); 230 231 topToolbar_.addLeftSeparator(); 232 233 topToolbar_.addLeftWidget(new ToolbarButton( 234 "Refresh", ToolbarButton.NoTitle, commands.vcsRefresh().getImageResource(), 235 new ClickHandler() { 236 @Override 237 public void onClick(ClickEvent event) 238 { 239 changelist_.showProgress(); 240 commands_.vcsRefresh().execute(); 241 } 242 })); 243 244 topToolbar_.addLeftSeparator(); 245 246 topToolbar_.addLeftWidget(commands.vcsAddFiles().createToolbarButton()); 247 topToolbar_.addLeftWidget(commands.vcsRemoveFiles().createToolbarButton()); 248 topToolbar_.addLeftSeparator(); 249 topToolbar_.addLeftWidget(commands.vcsRevert().createToolbarButton()); 250 topToolbar_.addLeftWidget(commands.vcsIgnore().createToolbarButton()); 251 topToolbar_.addLeftSeparator(); 252 topToolbar_.addLeftWidget(commands.vcsResolve().createToolbarButton()); 253 topToolbar_.addLeftSeparator(); 254 topToolbar_.addLeftWidget(commands.vcsCommit().createToolbarButton()); 255 256 257 commands.vcsPull().setButtonLabel("Update"); 258 commands.vcsPull().setMenuLabel("Update"); 259 topToolbar_.addRightWidget(commands.vcsPull().createToolbarButton()); 260 261 diffToolbar_.addStyleName(RES.styles().toolbar()); 262 diffToolbar_.addStyleName(RES.styles().diffToolbar()); 263 diffToolbar_.getWrapper().addStyleName(RES.styles().diffToolbarInnerWrapper()); 264 265 diffToolbar_.addLeftSeparator(); 266 discardAllButton_ = diffToolbar_.addLeftWidget(new ToolbarButton( 267 "Discard All", ToolbarButton.NoTitle, new ImageResource2x(RES.discard2x()))); 268 269 contextLines_.addStyleName(RES.styles().diffContextLines()); 270 lblContext_.setFor(contextLines_); 271 listBoxAdapter_ = new ListBoxAdapter(contextLines_); 272 273 new WidgetHandlerRegistration(this) 274 { 275 @Override 276 protected HandlerRegistration doRegister() 277 { 278 return Event.addNativePreviewHandler(new NativePreviewHandler() 279 { 280 @Override 281 public void onPreviewNativeEvent(NativePreviewEvent event) 282 { 283 NativeEvent nativeEvent = event.getNativeEvent(); 284 if (event.getTypeInt() == Event.ONKEYDOWN 285 && KeyboardShortcut.getModifierValue(nativeEvent) == KeyboardShortcut.CTRL) 286 { 287 switch (nativeEvent.getKeyCode()) 288 { 289 case KeyCodes.KEY_DOWN: 290 nativeEvent.preventDefault(); 291 scrollBy(diffScroll_, getLineScroll(diffScroll_), 0); 292 break; 293 case KeyCodes.KEY_UP: 294 nativeEvent.preventDefault(); 295 scrollBy(diffScroll_, 296 -getLineScroll(diffScroll_), 297 0); 298 break; 299 case KeyCodes.KEY_PAGEDOWN: 300 nativeEvent.preventDefault(); 301 scrollBy(diffScroll_, getPageScroll(diffScroll_), 0); 302 break; 303 case KeyCodes.KEY_PAGEUP: 304 nativeEvent.preventDefault(); 305 scrollBy(diffScroll_, 306 -getPageScroll(diffScroll_), 307 0); 308 break; 309 } 310 } 311 } 312 }); 313 } 314 }; 315 } 316 317 private void scrollBy(ScrollPanel scrollPanel, int vscroll, int hscroll) 318 { 319 if (vscroll != 0) 320 { 321 scrollPanel.setVerticalScrollPosition( 322 Math.max(0, scrollPanel.getVerticalScrollPosition() + vscroll)); 323 } 324 325 if (hscroll != 0) 326 { 327 scrollPanel.setHorizontalScrollPosition( 328 Math.max(0, scrollPanel.getHorizontalScrollPosition() + hscroll)); 329 } 330 } 331 332 private int getLineScroll(ScrollPanel panel) 333 { 334 return 30; 335 } 336 337 private int getPageScroll(ScrollPanel panel) 338 { 339 // Return slightly less than the client height (so there's overlap between 340 // one screen and the next) but never less than the line scroll height. 341 return Math.max( 342 getLineScroll(panel), 343 panel.getElement().getClientHeight() - getLineScroll(panel)); 344 } 345 346 @Override 347 public HasClickHandlers getSwitchViewButton() 348 { 349 return switchViewButton_; 350 } 351 352 @Override 353 public HasClickHandlers getDiscardAllButton() 354 { 355 return discardAllButton_; 356 } 357 358 359 @Override 360 public void setData(ArrayList<ChunkOrLine> lines) 361 { 362 int vscroll = diffScroll_.getVerticalScrollPosition(); 363 int hscroll = diffScroll_.getHorizontalScrollPosition(); 364 365 getLineTableDisplay().setData(lines, PatchMode.Working); 366 367 diffScroll_.setVerticalScrollPosition(vscroll); 368 diffScroll_.setHorizontalScrollPosition(hscroll); 369 } 370 371 @Override 372 public ArrayList<String> getSelectedPaths() 373 { 374 return changelist_.getSelectedPaths(); 375 } 376 377 @Override 378 public ArrayList<StatusAndPath> getSelectedItems() 379 { 380 return changelist_.getSelectedItems(); 381 } 382 383 @Override 384 public void setSelectedStatusAndPaths(ArrayList<StatusAndPath> selectedPaths) 385 { 386 changelist_.setSelectedStatusAndPaths(selectedPaths); 387 } 388 389 @Override 390 public LineTablePresenter.Display getLineTableDisplay() 391 { 392 return lines_; 393 } 394 395 @Override 396 public ChangelistTable getChangelistTable() 397 { 398 return changelist_; 399 } 400 401 @Override 402 public HasValue<Integer> getContextLines() 403 { 404 return listBoxAdapter_; 405 } 406 407 @Override 408 public HasClickHandlers getOverrideSizeWarningButton() 409 { 410 return overrideSizeWarning_; 411 } 412 413 @Override 414 public void showSizeWarning(long sizeInBytes) 415 { 416 overrideSizeWarning_.setSize(sizeInBytes); 417 diffScroll_.setWidget(overrideSizeWarning_); 418 } 419 420 @Override 421 public void hideSizeWarning() 422 { 423 diffScroll_.setWidget(lines_); 424 } 425 426 @Override 427 public void showContextMenu(final int clientX, 428 final int clientY) 429 { 430 final ToolbarPopupMenu menu = new ToolbarPopupMenu(); 431 432 menu.addItem(commands_.vcsAddFiles().createMenuItem(false)); 433 menu.addItem(commands_.vcsRemoveFiles().createMenuItem(false)); 434 menu.addSeparator(); 435 menu.addItem(commands_.vcsRevert().createMenuItem(false)); 436 menu.addItem(commands_.vcsIgnore().createMenuItem(false)); 437 menu.addSeparator(); 438 menu.addItem(commands_.vcsResolve().createMenuItem(false)); 439 menu.addSeparator(); 440 menu.addItem(commands_.vcsOpen().createMenuItem(false)); 441 442 menu.setPopupPositionAndShow(new PositionCallback() { 443 @Override 444 public void setPosition(int offsetWidth, int offsetHeight) 445 { 446 menu.setPopupPosition(clientX, clientY); 447 } 448 }); 449 } 450 451 @Override 452 public void onShow() 453 { 454 Scheduler.get().scheduleDeferred(new ScheduledCommand() 455 { 456 @Override 457 public void execute() 458 { 459 changelist_.focus(); 460 } 461 }); 462 } 463 464 @UiField(provided = true) 465 SplitLayoutPanel splitPanel_; 466 @UiField(provided = true) 467 ChangelistTable changelist_; 468 @UiField(provided = true) 469 LineTableView lines_; 470 @UiField 471 FormLabel lblContext_; 472 @UiField 473 ListBox contextLines_; 474 @UiField(provided = true) 475 Toolbar topToolbar_; 476 @UiField(provided = true) 477 Toolbar diffToolbar_; 478 @UiField 479 ScrollPanel diffScroll_; 480 481 private final Commands commands_; 482 483 private ListBoxAdapter listBoxAdapter_; 484 485 private ToolbarButton discardAllButton_; 486 487 private LeftRightToggleButton switchViewButton_; 488 489 private SizeWarningWidget overrideSizeWarning_; 490 491 private static final Resources RES = GWT.create(Resources.class); 492 static { 493 RES.styles().ensureInjected(); 494 } 495 }