| // Copyright (C) 2008 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.client.patches; |
| |
| import com.google.gerrit.client.Dispatcher; |
| import com.google.gerrit.client.ErrorDialog; |
| import com.google.gerrit.client.Gerrit; |
| import com.google.gerrit.client.RpcStatus; |
| import com.google.gerrit.client.changes.CommitMessageBlock; |
| import com.google.gerrit.client.changes.PatchTable; |
| import com.google.gerrit.client.changes.Util; |
| import com.google.gerrit.client.projects.ConfigInfoCache; |
| import com.google.gerrit.client.rpc.CallbackGroup; |
| import com.google.gerrit.client.rpc.GerritCallback; |
| import com.google.gerrit.client.rpc.ScreenLoadCallback; |
| import com.google.gerrit.client.ui.CommentLinkProcessor; |
| import com.google.gerrit.client.ui.ListenableAccountDiffPreference; |
| import com.google.gerrit.client.ui.Screen; |
| import com.google.gerrit.common.data.PatchScript; |
| import com.google.gerrit.common.data.PatchSetDetail; |
| import com.google.gerrit.prettify.client.ClientSideFormatter; |
| import com.google.gerrit.prettify.client.PrettyFactory; |
| import com.google.gerrit.reviewdb.client.AccountDiffPreference; |
| import com.google.gerrit.reviewdb.client.Patch; |
| import com.google.gerrit.reviewdb.client.PatchSet; |
| import com.google.gwt.core.client.Scheduler; |
| import com.google.gwt.core.client.Scheduler.ScheduledCommand; |
| import com.google.gwt.event.dom.client.KeyPressEvent; |
| import com.google.gwt.event.logical.shared.ValueChangeEvent; |
| import com.google.gwt.event.logical.shared.ValueChangeHandler; |
| import com.google.gwt.event.shared.HandlerRegistration; |
| import com.google.gwt.user.client.rpc.AsyncCallback; |
| import com.google.gwt.user.client.ui.FlowPanel; |
| import com.google.gwtexpui.globalkey.client.GlobalKey; |
| import com.google.gwtexpui.globalkey.client.KeyCommand; |
| import com.google.gwtexpui.globalkey.client.KeyCommandSet; |
| |
| public abstract class PatchScreen extends Screen implements |
| CommentEditorContainer { |
| static final PrettyFactory PRETTY = ClientSideFormatter.FACTORY; |
| static final short LARGE_FILE_CONTEXT = 100; |
| |
| public static class SideBySide extends PatchScreen { |
| public SideBySide(final Patch.Key id, final int patchIndex, |
| final PatchSetDetail patchSetDetail, final PatchTable patchTable, |
| final TopView topView, final PatchSet.Id baseId) { |
| super(id, patchIndex, patchSetDetail, patchTable, topView, baseId); |
| } |
| |
| @Override |
| protected SideBySideTable createContentTable() { |
| return new SideBySideTable(); |
| } |
| |
| @Override |
| public PatchScreen.Type getPatchScreenType() { |
| return PatchScreen.Type.SIDE_BY_SIDE; |
| } |
| } |
| |
| public static class Unified extends PatchScreen { |
| public Unified(final Patch.Key id, final int patchIndex, |
| final PatchSetDetail patchSetDetail, final PatchTable patchTable, |
| final TopView topView, final PatchSet.Id baseId) { |
| super(id, patchIndex, patchSetDetail, patchTable, topView, baseId); |
| } |
| |
| @Override |
| protected UnifiedDiffTable createContentTable() { |
| return new UnifiedDiffTable(); |
| } |
| |
| @Override |
| public PatchScreen.Type getPatchScreenType() { |
| return PatchScreen.Type.UNIFIED; |
| } |
| } |
| |
| /** |
| * What should be displayed in the top of the screen |
| */ |
| public static enum TopView { |
| MAIN, COMMIT, PREFERENCES, PATCH_SETS, FILES |
| } |
| |
| protected final Patch.Key patchKey; |
| protected PatchSetDetail patchSetDetail; |
| protected PatchTable fileList; |
| protected PatchSet.Id idSideA; |
| protected PatchSet.Id idSideB; |
| protected PatchScriptSettingsPanel settingsPanel; |
| protected TopView topView; |
| protected CommentLinkProcessor commentLinkProcessor; |
| |
| private ReviewedPanels reviewedPanels; |
| private HistoryTable historyTable; |
| private FlowPanel topPanel; |
| private FlowPanel contentPanel; |
| private AbstractPatchContentTable contentTable; |
| private CommitMessageBlock commitMessageBlock; |
| private NavLinks topNav; |
| private NavLinks bottomNav; |
| |
| private int rpcSequence; |
| private PatchScript lastScript; |
| |
| /** The index of the file we are currently looking at among the fileList */ |
| private int patchIndex; |
| private ListenableAccountDiffPreference prefs; |
| private HandlerRegistration prefsHandler; |
| |
| /** Keys that cause an action on this screen */ |
| private KeyCommandSet keysNavigation; |
| private KeyCommandSet keysAction; |
| private HandlerRegistration regNavigation; |
| private HandlerRegistration regAction; |
| private boolean intralineFailure; |
| private boolean intralineTimeout; |
| |
| /** |
| * How this patch should be displayed in the patch screen. |
| */ |
| public static enum Type { |
| UNIFIED, SIDE_BY_SIDE |
| } |
| |
| protected PatchScreen(final Patch.Key id, final int patchIndex, |
| final PatchSetDetail detail, final PatchTable patchTable, |
| final TopView top, final PatchSet.Id baseId) { |
| patchKey = id; |
| patchSetDetail = detail; |
| fileList = patchTable; |
| topView = top; |
| |
| idSideA = baseId; // null here means we're diff'ing from the Base |
| idSideB = id.getParentKey(); |
| this.patchIndex = patchIndex; |
| |
| prefs = fileList != null |
| ? fileList.getPreferences() |
| : new ListenableAccountDiffPreference(); |
| if (Gerrit.isSignedIn()) { |
| prefs.reset(); |
| } |
| reviewedPanels = new ReviewedPanels(); |
| settingsPanel = new PatchScriptSettingsPanel(prefs); |
| } |
| |
| @Override |
| public void notifyDraftDelta(int delta) { |
| lastScript = null; |
| } |
| |
| @Override |
| public void remove(CommentEditorPanel panel) { |
| lastScript = null; |
| } |
| |
| private void update(AccountDiffPreference dp) { |
| // Did the user just turn on auto-review? |
| if (!reviewedPanels.getValue() && prefs.getOld().isManualReview() |
| && !dp.isManualReview()) { |
| reviewedPanels.setValue(true); |
| reviewedPanels.setReviewedByCurrentUser(true); |
| } |
| |
| if (lastScript != null && canReuse(dp, lastScript)) { |
| lastScript.setDiffPrefs(dp); |
| RpcStatus.INSTANCE.onRpcStart(null); |
| settingsPanel.setEnabled(false); |
| Scheduler.get().scheduleDeferred(new ScheduledCommand() { |
| @Override |
| public void execute() { |
| try { |
| onResult(lastScript, false /* not the first time */); |
| } finally { |
| RpcStatus.INSTANCE.onRpcComplete(null); |
| } |
| } |
| }); |
| } else { |
| refresh(false); |
| } |
| } |
| |
| private boolean canReuse(AccountDiffPreference dp, PatchScript last) { |
| if (last.getDiffPrefs().getIgnoreWhitespace() != dp.getIgnoreWhitespace()) { |
| // Whitespace ignore setting requires server computation. |
| return false; |
| } |
| |
| final int ctx = dp.getContext(); |
| if (ctx == AccountDiffPreference.WHOLE_FILE_CONTEXT && !last.getA().isWholeFile()) { |
| // We don't have the entire file here, so we can't render it. |
| return false; |
| } |
| |
| if (last.getDiffPrefs().getContext() < ctx && !last.getA().isWholeFile()) { |
| // We don't have sufficient context. |
| return false; |
| } |
| |
| if (dp.isSyntaxHighlighting() |
| && !last.getA().isWholeFile()) { |
| // We need the whole file to syntax highlight accurately. |
| return false; |
| } |
| |
| return true; |
| } |
| |
| @Override |
| protected void onInitUI() { |
| super.onInitUI(); |
| |
| if (Gerrit.isSignedIn()) { |
| setTitleFarEast(reviewedPanels.top); |
| } |
| |
| keysNavigation = new KeyCommandSet(Gerrit.C.sectionNavigation()); |
| keysNavigation.add(new UpToChangeCommand(patchKey.getParentKey(), 0, 'u')); |
| keysNavigation.add(new FileListCmd(0, 'f', PatchUtil.C.fileList())); |
| |
| if (Gerrit.isSignedIn()) { |
| keysAction = new KeyCommandSet(Gerrit.C.sectionActions()); |
| keysAction |
| .add(new ToggleReviewedCmd(0, 'm', PatchUtil.C.toggleReviewed())); |
| keysAction.add(new MarkAsReviewedAndGoToNextCmd(0, 'M', PatchUtil.C |
| .markAsReviewedAndGoToNext())); |
| } |
| |
| historyTable = new HistoryTable(this); |
| |
| commitMessageBlock = new CommitMessageBlock(); |
| |
| topPanel = new FlowPanel(); |
| add(topPanel); |
| |
| contentTable = createContentTable(); |
| contentTable.fileList = fileList; |
| |
| topNav = new NavLinks(keysNavigation, patchKey.getParentKey()); |
| bottomNav = new NavLinks(null, patchKey.getParentKey()); |
| |
| add(topNav); |
| contentPanel = new FlowPanel(); |
| if (getPatchScreenType() == PatchScreen.Type.SIDE_BY_SIDE) { |
| contentPanel.setStyleName(// |
| Gerrit.RESOURCES.css().sideBySideScreenSideBySideTable()); |
| } else { |
| contentPanel.setStyleName(Gerrit.RESOURCES.css().unifiedTable()); |
| } |
| |
| contentPanel.add(contentTable); |
| add(contentPanel); |
| add(bottomNav); |
| if (Gerrit.isSignedIn()) { |
| add(reviewedPanels.bottom); |
| } |
| |
| if (fileList != null) { |
| topNav.display(patchIndex, getPatchScreenType(), fileList); |
| bottomNav.display(patchIndex, getPatchScreenType(), fileList); |
| } |
| } |
| |
| @Override |
| protected void onLoad() { |
| super.onLoad(); |
| |
| if (patchSetDetail == null) { |
| Util.DETAIL_SVC.patchSetDetail(idSideB, |
| new GerritCallback<PatchSetDetail>() { |
| @Override |
| public void onSuccess(PatchSetDetail result) { |
| patchSetDetail = result; |
| if (fileList == null) { |
| fileList = new PatchTable(prefs); |
| fileList.display(idSideA, result); |
| patchIndex = fileList.indexOf(patchKey); |
| } |
| refresh(true); |
| } |
| }); |
| } else { |
| refresh(true); |
| } |
| } |
| |
| @Override |
| protected void onUnload() { |
| if (prefsHandler != null) { |
| prefsHandler.removeHandler(); |
| prefsHandler = null; |
| } |
| if (regNavigation != null) { |
| regNavigation.removeHandler(); |
| regNavigation = null; |
| } |
| if (regAction != null) { |
| regAction.removeHandler(); |
| regAction = null; |
| } |
| super.onUnload(); |
| } |
| |
| @Override |
| public void registerKeys() { |
| super.registerKeys(); |
| contentTable.setRegisterKeys(contentTable.isVisible()); |
| if (regNavigation != null) { |
| regNavigation.removeHandler(); |
| regNavigation = null; |
| } |
| regNavigation = GlobalKey.add(this, keysNavigation); |
| if (regAction != null) { |
| regAction.removeHandler(); |
| regAction = null; |
| } |
| if (keysAction != null) { |
| regAction = GlobalKey.add(this, keysAction); |
| } |
| } |
| |
| protected abstract AbstractPatchContentTable createContentTable(); |
| |
| public abstract PatchScreen.Type getPatchScreenType(); |
| |
| public PatchSet.Id getSideA() { |
| return idSideA; |
| } |
| |
| public Patch.Key getPatchKey() { |
| return patchKey; |
| } |
| |
| public int getPatchIndex() { |
| return patchIndex; |
| } |
| |
| public PatchSetDetail getPatchSetDetail() { |
| return patchSetDetail; |
| } |
| |
| public PatchTable getFileList() { |
| return fileList; |
| } |
| |
| public TopView getTopView() { |
| return topView; |
| } |
| |
| protected void refresh(final boolean isFirst) { |
| final int rpcseq = ++rpcSequence; |
| lastScript = null; |
| settingsPanel.setEnabled(false); |
| reviewedPanels.populate(patchKey, fileList, patchIndex, getPatchScreenType()); |
| if (isFirst && fileList != null && fileList.isLoaded()) { |
| fileList.movePointerTo(patchKey); |
| } |
| |
| CallbackGroup cb = new CallbackGroup(); |
| ConfigInfoCache.get(patchSetDetail.getProject(), |
| cb.add(new AsyncCallback<ConfigInfoCache.Entry>() { |
| @Override |
| public void onSuccess(ConfigInfoCache.Entry result) { |
| commentLinkProcessor = result.getCommentLinkProcessor(); |
| contentTable.setCommentLinkProcessor(commentLinkProcessor); |
| setTheme(result.getTheme()); |
| } |
| |
| @Override |
| public void onFailure(Throwable caught) { |
| // Handled by ScreenLoadCallback.onFailure. |
| } |
| })); |
| PatchUtil.DETAIL_SVC.patchScript(patchKey, idSideA, idSideB, |
| settingsPanel.getValue(), cb.addFinal( |
| new ScreenLoadCallback<PatchScript>(this) { |
| @Override |
| protected void preDisplay(final PatchScript result) { |
| if (rpcSequence == rpcseq) { |
| onResult(result, isFirst); |
| } |
| } |
| |
| @Override |
| public void onFailure(final Throwable caught) { |
| if (rpcSequence == rpcseq) { |
| settingsPanel.setEnabled(true); |
| super.onFailure(caught); |
| } |
| } |
| })); |
| } |
| |
| private void onResult(final PatchScript script, final boolean isFirst) { |
| final String path = PatchTable.getDisplayFileName(patchKey); |
| String fileName = path; |
| final int last = fileName.lastIndexOf('/'); |
| if (last >= 0) { |
| fileName = fileName.substring(last + 1); |
| } |
| |
| setWindowTitle(fileName); |
| setPageTitle(path); |
| |
| if (idSideB.equals(patchSetDetail.getPatchSet().getId())) { |
| commitMessageBlock.setVisible(true); |
| commitMessageBlock.display(patchSetDetail.getInfo().getMessage(), |
| commentLinkProcessor); |
| } else { |
| commitMessageBlock.setVisible(false); |
| Util.DETAIL_SVC.patchSetDetail(idSideB, |
| new GerritCallback<PatchSetDetail>() { |
| @Override |
| public void onSuccess(PatchSetDetail result) { |
| commitMessageBlock.setVisible(true); |
| commitMessageBlock.display(result.getInfo().getMessage(), |
| commentLinkProcessor); |
| } |
| }); |
| } |
| |
| historyTable.display(script.getHistory()); |
| |
| for (Patch p : patchSetDetail.getPatches()) { |
| if (p.getKey().equals(patchKey)) { |
| if (p.getPatchType().equals(Patch.PatchType.BINARY)) { |
| contentTable.isDisplayBinary = true; |
| } |
| break; |
| } |
| } |
| |
| if (contentTable instanceof SideBySideTable |
| && contentTable.isPureMetaChange(script) |
| && !contentTable.isDisplayBinary) { |
| // User asked for SideBySide (or a link guessed, wrong) and we can't |
| // show a pure-rename change there accurately. Switch to |
| // the unified view instead. User can set file comments on binary file |
| // in SideBySide view. |
| // |
| contentTable.removeFromParent(); |
| contentTable = new UnifiedDiffTable(); |
| contentTable.fileList = fileList; |
| contentTable.setCommentLinkProcessor(commentLinkProcessor); |
| contentPanel.add(contentTable); |
| setToken(Dispatcher.toPatchUnified(idSideA, patchKey)); |
| } |
| |
| if (script.isHugeFile()) { |
| AccountDiffPreference dp = script.getDiffPrefs(); |
| int context = dp.getContext(); |
| if (context == AccountDiffPreference.WHOLE_FILE_CONTEXT) { |
| context = Short.MAX_VALUE; |
| } else if (context > Short.MAX_VALUE) { |
| context = Short.MAX_VALUE; |
| } |
| dp.setContext((short) Math.min(context, LARGE_FILE_CONTEXT)); |
| dp.setSyntaxHighlighting(false); |
| script.setDiffPrefs(dp); |
| } |
| |
| contentTable.display(patchKey, idSideA, idSideB, script, patchSetDetail); |
| contentTable.display(script.getCommentDetail(), script.isExpandAllComments()); |
| contentTable.finishDisplay(); |
| contentTable.setRegisterKeys(isCurrentView()); |
| |
| settingsPanel.setEnableSmallFileFeatures(!script.isHugeFile()); |
| settingsPanel.setEnableIntralineDifference(script.hasIntralineDifference()); |
| settingsPanel.setEnabled(true); |
| lastScript = script; |
| |
| if (fileList != null) { |
| topNav.display(patchIndex, getPatchScreenType(), fileList); |
| bottomNav.display(patchIndex, getPatchScreenType(), fileList); |
| } |
| |
| if (Gerrit.isSignedIn()) { |
| boolean isReviewed = false; |
| if (isFirst && !prefs.get().isManualReview()) { |
| isReviewed = true; |
| reviewedPanels.setReviewedByCurrentUser(isReviewed); |
| } else { |
| for (Patch p : patchSetDetail.getPatches()) { |
| if (p.getKey().equals(patchKey)) { |
| isReviewed = p.isReviewedByCurrentUser(); |
| break; |
| } |
| } |
| } |
| reviewedPanels.setValue(isReviewed); |
| } |
| |
| intralineFailure = isFirst && script.hasIntralineFailure(); |
| intralineTimeout = isFirst && script.hasIntralineTimeout(); |
| } |
| |
| @Override |
| public void onShowView() { |
| super.onShowView(); |
| if (prefsHandler == null) { |
| prefsHandler = prefs.addValueChangeHandler( |
| new ValueChangeHandler<AccountDiffPreference>() { |
| @Override |
| public void onValueChange(ValueChangeEvent<AccountDiffPreference> event) { |
| update(event.getValue()); |
| } |
| }); |
| } |
| if (intralineFailure) { |
| intralineFailure = false; |
| new ErrorDialog(PatchUtil.C.intralineFailure()).show(); |
| } else if (intralineTimeout) { |
| intralineTimeout = false; |
| new ErrorDialog(PatchUtil.C.intralineTimeout()).show(); |
| } |
| if (topView != null && prefs.get().isRetainHeader()) { |
| setTopView(topView); |
| } |
| } |
| |
| public void setTopView(TopView tv) { |
| topView = tv; |
| topPanel.clear(); |
| switch(tv) { |
| case COMMIT: topPanel.add(commitMessageBlock); |
| break; |
| case PREFERENCES: topPanel.add(settingsPanel); |
| break; |
| case PATCH_SETS: topPanel.add(historyTable); |
| break; |
| case FILES: topPanel.add(fileList); |
| break; |
| case MAIN: |
| break; |
| } |
| } |
| |
| public class FileListCmd extends KeyCommand { |
| public FileListCmd(int mask, int key, String help) { |
| super(mask, key, help); |
| } |
| |
| @Override |
| public void onKeyPress(final KeyPressEvent event) { |
| if (fileList == null || fileList.isAttached()) { |
| final PatchSet.Id psid = patchKey.getParentKey(); |
| fileList = new PatchTable(prefs); |
| fileList.setSavePointerId("PatchTable " + psid); |
| Util.DETAIL_SVC.patchSetDetail(psid, |
| new GerritCallback<PatchSetDetail>() { |
| public void onSuccess(final PatchSetDetail result) { |
| fileList.display(idSideA, result); |
| } |
| }); |
| } |
| |
| final PatchBrowserPopup p = new PatchBrowserPopup(patchKey, fileList); |
| p.open(); |
| } |
| } |
| |
| public class ToggleReviewedCmd extends KeyCommand { |
| public ToggleReviewedCmd(int mask, int key, String help) { |
| super(mask, key, help); |
| } |
| |
| @Override |
| public void onKeyPress(final KeyPressEvent event) { |
| final boolean isReviewed = !reviewedPanels.getValue(); |
| reviewedPanels.setValue(isReviewed); |
| reviewedPanels.setReviewedByCurrentUser(isReviewed); |
| } |
| } |
| |
| public class MarkAsReviewedAndGoToNextCmd extends KeyCommand { |
| public MarkAsReviewedAndGoToNextCmd(int mask, int key, String help) { |
| super(mask, key, help); |
| } |
| |
| @Override |
| public void onKeyPress(final KeyPressEvent event) { |
| reviewedPanels.go(); |
| } |
| } |
| } |