blob: 7a97df123e8f02e4b461525a9eed88df0a376b2e [file] [log] [blame]
// Copyright (C) 2010 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.diff;
import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.account.DiffPreferences;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ReviewInfo;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.diff.DiffInfo.Region;
import com.google.gerrit.client.info.ChangeInfo;
import com.google.gerrit.client.info.FileInfo;
import com.google.gerrit.client.info.WebLinkInfo;
import com.google.gerrit.client.patches.PatchUtil;
import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.NativeMap;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.RestApi;
import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.client.GeneralPreferencesInfo.DiffView;
import com.google.gerrit.reviewdb.client.Patch;
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Style.Visibility;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.UIObject;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.globalkey.client.KeyCommandSet;
import com.google.gwtexpui.safehtml.client.SafeHtml;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.util.List;
public class Header extends Composite {
interface Binder extends UiBinder<HTMLPanel, Header> {}
private static final Binder uiBinder = GWT.create(Binder.class);
static {
Resources.I.style().ensureInjected();
}
private enum ReviewedState {
AUTO_REVIEW,
LOADED
}
@UiField CheckBox reviewed;
@UiField Element project;
@UiField Element filePath;
@UiField Element fileNumber;
@UiField Element fileCount;
@UiField Element noDiff;
@UiField FlowPanel linkPanel;
@UiField InlineHyperlink prev;
@UiField InlineHyperlink up;
@UiField InlineHyperlink next;
@UiField Image preferences;
private final KeyCommandSet keys;
@Nullable private final Project.NameKey projectKey;
private final DiffObject base;
private final PatchSet.Id patchSetId;
private final String path;
private final DiffView diffScreenType;
private final DiffPreferences prefs;
private boolean hasPrev;
private boolean hasNext;
private String nextPath;
private JsArray<FileInfo> files;
private PreferencesAction prefsAction;
private ReviewedState reviewedState;
Header(
KeyCommandSet keys,
@Nullable Project.NameKey project,
DiffObject base,
DiffObject patchSetId,
String path,
DiffView diffSreenType,
DiffPreferences prefs) {
initWidget(uiBinder.createAndBindUi(this));
this.keys = keys;
this.projectKey = project;
this.base = base;
this.patchSetId = patchSetId.asPatchSetId();
this.path = path;
this.diffScreenType = diffSreenType;
this.prefs = prefs;
if (!Gerrit.isSignedIn()) {
reviewed.getElement().getStyle().setVisibility(Visibility.HIDDEN);
}
SafeHtml.setInnerHTML(filePath, formatPath(path));
up.setTargetHistoryToken(
PageLinks.toChange(
project,
patchSetId.asPatchSetId().getParentKey(),
base.asString(),
patchSetId.asPatchSetId().getId()));
}
public static SafeHtml formatPath(String path) {
SafeHtmlBuilder b = new SafeHtmlBuilder();
if (Patch.COMMIT_MSG.equals(path)) {
return b.append(Util.C.commitMessage());
} else if (Patch.MERGE_LIST.equals(path)) {
return b.append(Util.C.mergeList());
}
int s = path.lastIndexOf('/') + 1;
b.append(path.substring(0, s));
b.openElement("b");
b.append(path.substring(s));
b.closeElement("b");
return b;
}
private int findCurrentFileIndex(JsArray<FileInfo> files) {
int currIndex = 0;
for (int i = 0; i < files.length(); i++) {
if (path.equals(files.get(i).path())) {
currIndex = i;
break;
}
}
return currIndex;
}
@Override
protected void onLoad() {
DiffApi.list(
Project.NameKey.asStringOrNull(projectKey),
patchSetId,
base.asPatchSetId(),
new GerritCallback<NativeMap<FileInfo>>() {
@Override
public void onSuccess(NativeMap<FileInfo> result) {
files = result.values();
FileInfo.sortFileInfoByPath(files);
fileNumber.setInnerText(
Integer.toString(Natives.asList(files).indexOf(result.get(path)) + 1));
fileCount.setInnerText(Integer.toString(files.length()));
}
});
if (Gerrit.isSignedIn()) {
ChangeApi.revision(Project.NameKey.asStringOrNull(projectKey), patchSetId)
.view("files")
.addParameterTrue("reviewed")
.get(
new AsyncCallback<JsArrayString>() {
@Override
public void onSuccess(JsArrayString result) {
boolean b = Natives.asList(result).contains(path);
reviewed.setValue(b, false);
if (!b && reviewedState == ReviewedState.AUTO_REVIEW) {
postAutoReviewed();
}
reviewedState = ReviewedState.LOADED;
}
@Override
public void onFailure(Throwable caught) {}
});
}
}
void autoReview() {
if (reviewedState == ReviewedState.LOADED && !reviewed.getValue()) {
postAutoReviewed();
} else {
reviewedState = ReviewedState.AUTO_REVIEW;
}
}
void setChangeInfo(ChangeInfo info) {
project.setInnerText(info.project());
}
void init(PreferencesAction pa, List<InlineHyperlink> links, List<WebLinkInfo> webLinks) {
prefsAction = pa;
prefsAction.setPartner(preferences);
for (InlineHyperlink link : links) {
linkPanel.add(link);
}
for (WebLinkInfo webLink : webLinks) {
linkPanel.add(webLink.toAnchor());
}
}
@UiHandler("reviewed")
void onValueChange(ValueChangeEvent<Boolean> event) {
if (event.getValue()) {
reviewed().put(CallbackGroup.<ReviewInfo>emptyCallback());
} else {
reviewed().delete(CallbackGroup.<ReviewInfo>emptyCallback());
}
}
private void postAutoReviewed() {
reviewed()
.background()
.put(
new AsyncCallback<ReviewInfo>() {
@Override
public void onSuccess(ReviewInfo result) {
reviewed.setValue(true, false);
}
@Override
public void onFailure(Throwable caught) {}
});
}
private RestApi reviewed() {
return ChangeApi.revision(Project.NameKey.asStringOrNull(projectKey), patchSetId)
.view("files")
.id(path)
.view("reviewed");
}
@UiHandler("preferences")
void onPreferences(@SuppressWarnings("unused") ClickEvent e) {
prefsAction.show();
}
private String url(FileInfo info) {
return diffScreenType == DiffView.UNIFIED_DIFF
? Dispatcher.toUnified(projectKey, base, patchSetId, info.path())
: Dispatcher.toSideBySide(projectKey, base, patchSetId, info.path());
}
private KeyCommand setupNav(InlineHyperlink link, char key, String help, FileInfo info) {
if (info != null) {
final String url = url(info);
link.setTargetHistoryToken(url);
link.setTitle(
PatchUtil.M.fileNameWithShortcutKey(
FileInfo.getFileName(info.path()), Character.toString(key)));
KeyCommand k =
new KeyCommand(0, key, help) {
@Override
public void onKeyPress(KeyPressEvent event) {
Gerrit.display(url);
}
};
keys.add(k);
if (link == prev) {
hasPrev = true;
} else {
hasNext = true;
}
return k;
}
link.getElement().getStyle().setVisibility(Visibility.HIDDEN);
keys.add(new UpToChangeCommand(projectKey, patchSetId, 0, key));
return null;
}
private boolean shouldSkipFile(FileInfo curr, CommentsCollections comments) {
return prefs.skipDeleted() && ChangeType.DELETED.matches(curr.status())
|| prefs.skipUnchanged() && ChangeType.RENAMED.matches(curr.status())
|| prefs.skipUncommented() && !comments.hasCommentForPath(curr.path());
}
void setupPrevNextFiles(CommentsCollections comments) {
FileInfo prevInfo = null;
FileInfo nextInfo = null;
int currIndex = findCurrentFileIndex(files);
for (int i = currIndex - 1; i >= 0; i--) {
FileInfo curr = files.get(i);
if (shouldSkipFile(curr, comments)) {
continue;
}
prevInfo = curr;
break;
}
for (int i = currIndex + 1; i < files.length(); i++) {
FileInfo curr = files.get(i);
if (shouldSkipFile(curr, comments)) {
continue;
}
nextInfo = curr;
break;
}
KeyCommand p = setupNav(prev, '[', PatchUtil.C.previousFileHelp(), prevInfo);
KeyCommand n = setupNav(next, ']', PatchUtil.C.nextFileHelp(), nextInfo);
if (p != null && n != null) {
keys.pair(p, n);
}
nextPath = nextInfo != null ? nextInfo.path() : null;
}
Runnable toggleReviewed() {
return () -> reviewed.setValue(!reviewed.getValue(), true);
}
Runnable navigate(Direction dir) {
switch (dir) {
case PREV:
return () -> (hasPrev ? prev : up).go();
case NEXT:
return () -> (hasNext ? next : up).go();
default:
return () -> {};
}
}
Runnable reviewedAndNext() {
return () -> {
if (Gerrit.isSignedIn()) {
reviewed.setValue(true, true);
}
navigate(Direction.NEXT).run();
};
}
String getNextPath() {
return nextPath;
}
void setNoDiff(DiffInfo diff) {
if (diff.binary()) {
UIObject.setVisible(noDiff, false); // Don't bother showing "No Differences"
} else {
JsArray<Region> regions = diff.content();
boolean b = regions.length() == 0 || (regions.length() == 1 && regions.get(0).ab() != null);
UIObject.setVisible(noDiff, b);
}
}
}