blob: 30554b694b89105e27585c6f9bd824da14ad8fb1 [file] [log] [blame]
// Copyright (C) 2013 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.change;
import static com.google.gerrit.client.FormatUtil.formatAbsBytes;
import static com.google.gerrit.client.FormatUtil.formatAbsPercentage;
import static com.google.gerrit.client.FormatUtil.formatBytes;
import static com.google.gerrit.client.FormatUtil.formatPercentage;
import com.google.gerrit.client.DiffObject;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeEditApi;
import com.google.gerrit.client.changes.CommentInfo;
import com.google.gerrit.client.changes.ReviewInfo;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.info.FileInfo;
import com.google.gerrit.client.patches.PatchUtil;
import com.google.gerrit.client.rpc.CallbackGroup;
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.NavigationTable;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.client.Side;
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.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.EventListener;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwt.user.client.ui.ImageResourceRenderer;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.impl.HyperlinkImpl;
import com.google.gwtexpui.globalkey.client.KeyCommand;
import com.google.gwtexpui.progress.client.ProgressBar;
import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
import java.sql.Timestamp;
public class FileTable extends FlowPanel {
private static final FileTableResources R = GWT.create(FileTableResources.class);
interface FileTableResources extends ClientBundle {
@Source("file_table.css")
FileTableCss css();
}
interface FileTableCss extends CssResource {
String table();
String nohover();
String pointer();
String reviewed();
String status();
String pathColumn();
String commonPrefix();
String renameCopySource();
String draftColumn();
String newColumn();
String commentColumn();
String deltaColumn1();
String deltaColumn2();
String inserted();
String deleted();
String restoreDelete();
String error();
}
public enum Mode {
REVIEW,
EDIT
}
private static final String DELETE;
private static final String RESTORE;
private static final String REVIEWED;
private static final String OPEN;
private static final int C_PATH = 3;
private static final HyperlinkImpl link = GWT.create(HyperlinkImpl.class);
static {
DELETE = DOM.createUniqueId().replace('-', '_');
RESTORE = DOM.createUniqueId().replace('-', '_');
REVIEWED = DOM.createUniqueId().replace('-', '_');
OPEN = DOM.createUniqueId().replace('-', '_');
init(DELETE, RESTORE, REVIEWED, OPEN);
}
private static native void init(String d, String t, String r, String o) /*-{
$wnd[d] = $entry(function(e,i) {
@com.google.gerrit.client.change.FileTable::onDelete(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i)
});
$wnd[t] = $entry(function(e,i) {
@com.google.gerrit.client.change.FileTable::onRestore(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i)
});
$wnd[r] = $entry(function(e,i) {
@com.google.gerrit.client.change.FileTable::onReviewed(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i)
});
$wnd[o] = $entry(function(e,i) {
return @com.google.gerrit.client.change.FileTable::onOpen(Lcom/google/gwt/dom/client/NativeEvent;I)(e,i);
});
}-*/;
private static void onDelete(NativeEvent e, int idx) {
MyTable t = getMyTable(e);
if (t != null) {
t.onDelete(idx);
}
}
private static boolean onRestore(NativeEvent e, int idx) {
MyTable t = getMyTable(e);
if (t != null) {
t.onRestore(idx);
e.preventDefault();
e.stopPropagation();
return false;
}
return true;
}
private static void onReviewed(NativeEvent e, int idx) {
MyTable t = getMyTable(e);
if (t != null) {
t.onReviewed(InputElement.as(Element.as(e.getEventTarget())), idx);
}
}
private static boolean onOpen(NativeEvent e, int idx) {
if (link.handleAsClick(e.<Event>cast())) {
MyTable t = getMyTable(e);
if (t != null) {
t.onOpenRow(1 + idx);
e.preventDefault();
e.stopPropagation();
return false;
}
}
return true;
}
private static MyTable getMyTable(NativeEvent event) {
Element e = event.getEventTarget().cast();
for (e = DOM.getParent(e); e != null; e = DOM.getParent(e)) {
EventListener l = DOM.getEventListener(e);
if (l instanceof MyTable) {
return (MyTable) l;
}
}
return null;
}
private DiffObject base;
private PatchSet.Id curr;
private Project.NameKey project;
private MyTable table;
private boolean register;
private JsArrayString reviewed;
private String scrollToPath;
private ChangeScreen.Style style;
private Widget replyButton;
private boolean editExists;
private Mode mode;
@Override
protected void onLoad() {
super.onLoad();
R.css().ensureInjected();
}
public void set(
DiffObject base,
PatchSet.Id curr,
Project.NameKey project,
ChangeScreen.Style style,
Widget replyButton,
Mode mode,
boolean editExists) {
this.base = base;
this.curr = curr;
this.project = project;
this.style = style;
this.replyButton = replyButton;
this.mode = mode;
this.editExists = editExists;
}
void setValue(
NativeMap<FileInfo> fileMap,
Timestamp myLastReply,
@Nullable NativeMap<JsArray<CommentInfo>> comments,
@Nullable NativeMap<JsArray<CommentInfo>> drafts) {
JsArray<FileInfo> list = fileMap.values();
FileInfo.sortFileInfoByPath(list);
DisplayCommand cmd = new DisplayCommand(fileMap, list, myLastReply, comments, drafts);
if (cmd.execute()) {
cmd.showProgressBar();
Scheduler.get().scheduleIncremental(cmd);
}
}
void showError(Throwable t) {
clear();
Label l = new Label(Resources.M.failedToLoadFileList(t.getMessage()));
add(l);
l.setStyleName(R.css().error());
}
void markReviewed(JsArrayString reviewed) {
if (table != null) {
table.markReviewed(reviewed);
} else {
this.reviewed = reviewed;
}
}
void unregisterKeys() {
register = false;
if (table != null) {
table.setRegisterKeys(false);
}
}
void registerKeys() {
register = true;
if (table != null) {
table.setRegisterKeys(true);
}
}
void scrollToPath(String path) {
if (table != null) {
table.scrollToPath(path);
} else {
scrollToPath = path;
}
}
void openAll() {
if (table != null) {
String self = Gerrit.selfRedirect(null);
for (FileInfo info : Natives.asList(table.list)) {
if (canOpen(info.path())) {
Window.open(self + "#" + url(info), "_blank", null);
}
}
}
}
private boolean canOpen(String path) {
return mode != Mode.EDIT || !Patch.isMagic(path) || Patch.COMMIT_MSG.equals(path);
}
private void setTable(MyTable table) {
clear();
add(table);
this.table = table;
if (register) {
table.setRegisterKeys(true);
}
if (reviewed != null) {
table.markReviewed(reviewed);
reviewed = null;
}
if (scrollToPath != null) {
table.scrollToPath(scrollToPath);
scrollToPath = null;
}
}
private String url(FileInfo info) {
return info.binary()
? Dispatcher.toUnified(project, base, curr, info.path())
: mode == Mode.REVIEW
? Dispatcher.toPatch(project, base, curr, info.path())
: Dispatcher.toEditScreen(project, curr, info.path());
}
private final class MyTable extends NavigationTable<FileInfo> {
private final NativeMap<FileInfo> map;
private final JsArray<FileInfo> list;
MyTable(NativeMap<FileInfo> map, JsArray<FileInfo> list) {
this.map = map;
this.list = list;
table.setWidth("");
keysNavigation.add(
new PrevKeyCommand(0, 'k', Util.C.patchTablePrev()),
new NextKeyCommand(0, 'j', Util.C.patchTableNext()));
keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.patchTableOpenDiff()));
keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER, Util.C.patchTableOpenDiff()));
keysNavigation.add(
new OpenFileCommand(list.length() - 1, 0, '[', Resources.C.openLastFile()),
new OpenFileCommand(0, 0, ']', Resources.C.openCommitMessage()));
keysAction.add(
new KeyCommand(0, 'r', PatchUtil.C.toggleReviewed()) {
@Override
public void onKeyPress(KeyPressEvent event) {
int row = getCurrentRow();
if (1 <= row && row <= MyTable.this.list.length()) {
FileInfo info = MyTable.this.list.get(row - 1);
InputElement b = getReviewed(info);
boolean c = !b.isChecked();
setReviewed(info, c);
b.setChecked(c);
}
}
});
setSavePointerId((!base.isBase() ? base.asString() + ".." : "") + curr.toString());
}
void onDelete(int idx) {
String path = list.get(idx).path();
ChangeEditApi.delete(
project.get(),
curr.getParentKey().get(),
path,
new AsyncCallback<VoidResult>() {
@Override
public void onSuccess(VoidResult result) {
Gerrit.display(PageLinks.toChangeInEditMode(project, curr.getParentKey()));
}
@Override
public void onFailure(Throwable caught) {}
});
}
void onRestore(int idx) {
String path = list.get(idx).path();
ChangeEditApi.restore(
project.get(),
curr.getParentKey().get(),
path,
new AsyncCallback<VoidResult>() {
@Override
public void onSuccess(VoidResult result) {
Gerrit.display(PageLinks.toChangeInEditMode(project, curr.getParentKey()));
}
@Override
public void onFailure(Throwable caught) {}
});
}
void onReviewed(InputElement checkbox, int idx) {
setReviewed(list.get(idx), checkbox.isChecked());
}
private void setReviewed(FileInfo info, boolean r) {
RestApi api =
ChangeApi.revision(project.get(), curr).view("files").id(info.path()).view("reviewed");
if (r) {
api.put(CallbackGroup.<ReviewInfo>emptyCallback());
} else {
api.delete(CallbackGroup.<ReviewInfo>emptyCallback());
}
}
void markReviewed(JsArrayString reviewed) {
for (int i = 0; i < reviewed.length(); i++) {
FileInfo info = map.get(reviewed.get(i));
if (info != null) {
getReviewed(info).setChecked(true);
}
}
}
private InputElement getReviewed(FileInfo info) {
CellFormatter fmt = table.getCellFormatter();
Element e = fmt.getElement(1 + info._row(), 1);
return InputElement.as(e.getFirstChildElement());
}
void scrollToPath(String path) {
FileInfo info = map.get(path);
if (info != null) {
movePointerTo(1 + info._row(), true);
}
}
@Override
protected Object getRowItemKey(FileInfo item) {
return item.path();
}
@Override
protected int findRow(Object id) {
FileInfo info = map.get((String) id);
return info != null ? 1 + info._row() : -1;
}
@Override
protected FileInfo getRowItem(int row) {
if (1 <= row && row <= list.length()) {
return list.get(row - 1);
}
return null;
}
@Override
protected void onOpenRow(int row) {
if (1 <= row && row <= list.length()) {
FileInfo info = list.get(row - 1);
if (canOpen(info.path())) {
Gerrit.display(url(info));
}
}
}
@Override
protected void onCellSingleClick(Event event, int row, int column) {
if (column == C_PATH && link.handleAsClick(event)) {
onOpenRow(row);
} else {
super.onCellSingleClick(event, row, column);
}
}
private class OpenFileCommand extends KeyCommand {
private final int index;
OpenFileCommand(int index, int modifiers, char c, String helpText) {
super(modifiers, c, helpText);
this.index = index;
}
@Override
public void onKeyPress(KeyPressEvent event) {
FileInfo info = list.get(index);
if (canOpen(info.path())) {
Gerrit.display(url(info));
}
}
}
}
private final class DisplayCommand implements RepeatingCommand {
private final SafeHtmlBuilder sb = new SafeHtmlBuilder();
private final MyTable myTable;
private final JsArray<FileInfo> list;
private final Timestamp myLastReply;
private final NativeMap<JsArray<CommentInfo>> comments;
private final NativeMap<JsArray<CommentInfo>> drafts;
private final boolean hasUser;
private final boolean showChangeSizeBars;
private boolean attached;
private int row;
private double start;
private ProgressBar meter;
private String lastPath = "";
private boolean hasBinaryFile;
private boolean hasNonBinaryFile;
private int inserted;
private int deleted;
private long binOldSize;
private long bytesInserted;
private long bytesDeleted;
private DisplayCommand(
NativeMap<FileInfo> map,
JsArray<FileInfo> list,
Timestamp myLastReply,
@Nullable NativeMap<JsArray<CommentInfo>> comments,
@Nullable NativeMap<JsArray<CommentInfo>> drafts) {
this.myTable = new MyTable(map, list);
this.list = list;
this.myLastReply = myLastReply;
this.comments = comments;
this.drafts = drafts;
this.hasUser = Gerrit.isSignedIn();
this.showChangeSizeBars = Gerrit.getUserPreferences().sizeBarInChangeTable();
myTable.addStyleName(R.css().table());
}
@Override
public boolean execute() {
boolean attachedNow = isAttached();
if (!attached && attachedNow) {
// Remember that we have been attached at least once. If
// later we find we aren't attached we should stop running.
attached = true;
} else if (attached && !attachedNow) {
// If the user navigated away, we aren't in the DOM anymore.
// Don't continue to render.
return false;
}
start = System.currentTimeMillis();
if (row == 0) {
header(sb);
computeInsertedDeleted();
}
while (row < list.length()) {
FileInfo info = list.get(row);
info._row(row);
render(sb, info);
if ((++row % 10) == 0 && longRunning()) {
updateMeter();
return true;
}
}
footer(sb);
myTable.resetHtml(sb);
myTable.finishDisplay();
setTable(myTable);
return false;
}
private void computeInsertedDeleted() {
inserted = 0;
deleted = 0;
binOldSize = 0;
bytesInserted = 0;
bytesDeleted = 0;
for (int i = 0; i < list.length(); i++) {
FileInfo info = list.get(i);
if (!Patch.isMagic(info.path())) {
if (!info.binary()) {
hasNonBinaryFile = true;
inserted += info.linesInserted();
deleted += info.linesDeleted();
} else {
hasBinaryFile = true;
binOldSize += info.size() - info.sizeDelta();
if (info.sizeDelta() >= 0) {
bytesInserted += info.sizeDelta();
} else {
bytesDeleted += info.sizeDelta();
}
}
}
}
}
void showProgressBar() {
if (meter == null) {
meter = new ProgressBar(Util.M.loadingPatchSet(curr.get()));
FileTable.this.clear();
FileTable.this.add(meter);
}
updateMeter();
}
void updateMeter() {
if (meter != null) {
int n = list.length();
meter.setValue((100 * row) / n);
}
}
private boolean longRunning() {
return System.currentTimeMillis() - start > 200;
}
private void header(SafeHtmlBuilder sb) {
sb.openTr().setStyleName(R.css().nohover());
sb.openTh().setStyleName(R.css().pointer()).closeTh();
if (mode == Mode.REVIEW) {
sb.openTh().setStyleName(R.css().reviewed()).closeTh();
} else {
sb.openTh().setStyleName(R.css().restoreDelete()).closeTh();
}
sb.openTh().setStyleName(R.css().status()).closeTh();
sb.openTh().append(Util.C.patchTableColumnName()).closeTh();
sb.openTh().setAttribute("colspan", 3).append(Util.C.patchTableColumnComments()).closeTh();
sb.openTh().setAttribute("colspan", 2).append(Util.C.patchTableColumnSize()).closeTh();
sb.closeTr();
}
private void render(SafeHtmlBuilder sb, FileInfo info) {
sb.openTr();
sb.openTd().setStyleName(R.css().pointer()).closeTd();
if (mode == Mode.REVIEW) {
columnReviewed(sb, info);
} else {
columnDeleteRestore(sb, info);
}
columnStatus(sb, info);
columnPath(sb, info);
columnComments(sb, info);
columnDelta1(sb, info);
columnDelta2(sb, info);
sb.closeTr();
}
private void columnReviewed(SafeHtmlBuilder sb, FileInfo info) {
sb.openTd().setStyleName(R.css().reviewed());
if (hasUser) {
sb.openElement("input")
.setAttribute("title", Resources.C.reviewedFileTitle())
.setAttribute("type", "checkbox")
.setAttribute("onclick", REVIEWED + "(event," + info._row() + ")")
.closeSelf();
}
sb.closeTd();
}
private void columnDeleteRestore(SafeHtmlBuilder sb, FileInfo info) {
sb.openTd().setStyleName(R.css().restoreDelete());
if (hasUser) {
if (!Patch.isMagic(info.path())) {
boolean editable = isEditable(info);
sb.openDiv()
.openElement("button")
.setAttribute("title", Resources.C.restoreFileInline())
.setAttribute("onclick", RESTORE + "(event," + info._row() + ")")
.append(new ImageResourceRenderer().render(Gerrit.RESOURCES.editUndo()))
.closeElement("button");
if (editable) {
sb.openElement("button")
.setAttribute("title", Resources.C.removeFileInline())
.setAttribute("onclick", DELETE + "(event," + info._row() + ")")
.append(new ImageResourceRenderer().render(Gerrit.RESOURCES.redNot()))
.closeElement("button");
}
sb.closeDiv();
}
}
sb.closeTd();
}
private boolean isEditable(FileInfo info) {
String status = info.status();
return status == null || !ChangeType.DELETED.matches(status);
}
private void columnStatus(SafeHtmlBuilder sb, FileInfo info) {
sb.openTd().setStyleName(R.css().status());
if (!Patch.isMagic(info.path())
&& info.status() != null
&& !ChangeType.MODIFIED.matches(info.status())) {
sb.append(info.status());
}
sb.closeTd();
}
private void columnPath(SafeHtmlBuilder sb, FileInfo info) {
String path = info.path();
sb.openTd().setStyleName(R.css().pathColumn());
if (!canOpen(path)) {
sb.openDiv();
appendPath(path);
sb.closeDiv();
sb.closeTd();
return;
}
sb.openAnchor();
if (mode == Mode.EDIT && !isEditable(info)) {
sb.setAttribute("onclick", RESTORE + "(event," + info._row() + ")");
} else {
sb.setAttribute("href", "#" + url(info))
.setAttribute("onclick", OPEN + "(event," + info._row() + ")");
}
appendPath(path);
sb.closeAnchor();
if (info.oldPath() != null) {
sb.br();
sb.openSpan().setStyleName(R.css().renameCopySource()).append(info.oldPath()).closeSpan();
}
sb.closeTd();
}
private void appendPath(String path) {
if (Patch.COMMIT_MSG.equals(path)) {
sb.append(Util.C.commitMessage());
} else if (Patch.MERGE_LIST.equals(path)) {
sb.append(Util.C.mergeList());
} else if (Gerrit.getUserPreferences().muteCommonPathPrefixes()) {
int commonPrefixLen = commonPrefix(path);
if (commonPrefixLen > 0) {
sb.openSpan()
.setStyleName(R.css().commonPrefix())
.append(path.substring(0, commonPrefixLen))
.closeSpan();
}
sb.append(path.substring(commonPrefixLen));
lastPath = path;
} else {
sb.append(path);
}
}
private int commonPrefix(String path) {
for (int n = path.length(); n > 0; ) {
int s = path.lastIndexOf('/', n);
if (s < 0) {
return 0;
}
String p = path.substring(0, s + 1);
if (lastPath.startsWith(p)) {
return s + 1;
}
n = s - 1;
}
return 0;
}
private void columnComments(SafeHtmlBuilder sb, FileInfo info) {
JsArray<CommentInfo> cList = filterForParent(get(info.path(), comments));
JsArray<CommentInfo> dList = filterForParent(get(info.path(), drafts));
sb.openTd().setStyleName(R.css().draftColumn());
if (dList.length() > 0) {
sb.append("drafts: ").append(dList.length());
}
sb.closeTd();
int cntAll = cList.length();
int cntNew = 0;
if (myLastReply != null) {
for (int i = cntAll - 1; i >= 0; i--) {
CommentInfo m = cList.get(i);
if (m.updated().compareTo(myLastReply) > 0) {
cntNew++;
} else {
break;
}
}
}
sb.openTd().setStyleName(R.css().newColumn());
if (cntNew > 0) {
sb.append("new: ").append(cntNew);
}
sb.closeTd();
sb.openTd().setStyleName(R.css().commentColumn());
if (cntAll - cntNew > 0) {
sb.append("comments: ").append(cntAll - cntNew);
}
sb.closeTd();
}
private JsArray<CommentInfo> filterForParent(JsArray<CommentInfo> list) {
JsArray<CommentInfo> result = JsArray.createArray().cast();
for (CommentInfo c : Natives.asList(list)) {
if (c.side() == Side.REVISION) {
result.push(c);
} else if (base.isBaseOrAutoMerge() && !c.hasParent()) {
result.push(c);
} else if (base.isParent() && c.parent() == base.getParentNum()) {
result.push(c);
}
}
return result;
}
private JsArray<CommentInfo> get(String p, NativeMap<JsArray<CommentInfo>> m) {
JsArray<CommentInfo> r = null;
if (m != null) {
r = m.get(p);
}
if (r == null) {
r = JsArray.createArray().cast();
}
return r;
}
private void columnDelta1(SafeHtmlBuilder sb, FileInfo info) {
sb.openTd().setStyleName(R.css().deltaColumn1());
if (!Patch.isMagic(info.path()) && !info.binary()) {
if (showChangeSizeBars) {
sb.append(info.linesInserted() + info.linesDeleted());
} else if (!ChangeType.DELETED.matches(info.status())) {
if (ChangeType.ADDED.matches(info.status())) {
sb.append(info.linesInserted()).append(" lines");
} else {
sb.append("+").append(info.linesInserted()).append(", -").append(info.linesDeleted());
}
}
} else if (info.binary()) {
sb.append(formatBytes(info.sizeDelta()));
long oldSize = info.size() - info.sizeDelta();
if (oldSize != 0) {
sb.append(" (").append(formatPercentage(oldSize, info.sizeDelta())).append(")");
}
}
sb.closeTd();
}
private void columnDelta2(SafeHtmlBuilder sb, FileInfo info) {
sb.openTd().setStyleName(R.css().deltaColumn2());
if (showChangeSizeBars
&& !Patch.isMagic(info.path())
&& !info.binary()
&& (info.linesInserted() != 0 || info.linesDeleted() != 0)) {
int w = 80;
int t = inserted + deleted;
int i = Math.max(5, (int) (((double) w) * info.linesInserted() / t));
int d = Math.max(5, (int) (((double) w) * info.linesDeleted() / t));
sb.setAttribute(
"title", Util.M.patchTableSize_LongModify(info.linesInserted(), info.linesDeleted()));
if (0 < info.linesInserted()) {
sb.openDiv()
.setStyleName(R.css().inserted())
.setAttribute("style", "width:" + i + "px")
.closeDiv();
}
if (0 < info.linesDeleted()) {
sb.openDiv()
.setStyleName(R.css().deleted())
.setAttribute("style", "width:" + d + "px")
.closeDiv();
}
}
sb.closeTd();
}
private void footer(SafeHtmlBuilder sb) {
sb.openTr().setStyleName(R.css().nohover());
sb.openTh().setStyleName(R.css().pointer()).closeTh();
if (mode == Mode.REVIEW) {
sb.openTh().setStyleName(R.css().reviewed()).closeTh();
} else {
sb.openTh().setStyleName(R.css().restoreDelete()).closeTh();
}
sb.openTh().setStyleName(R.css().status()).closeTh();
sb.openTd().closeTd(); // path
sb.openTd().setAttribute("colspan", 3).closeTd(); // comments
// delta1
sb.openTh().setStyleName(R.css().deltaColumn1());
if (hasNonBinaryFile) {
sb.append(Util.M.patchTableSize_Modify(inserted, deleted));
}
if (hasBinaryFile) {
if (hasNonBinaryFile) {
sb.br();
}
if (binOldSize != 0) {
sb.append(
Util.M.patchTableSize_ModifyBinaryFilesWithPercentages(
formatAbsBytes(bytesInserted),
formatAbsPercentage(binOldSize, bytesInserted),
formatAbsBytes(bytesDeleted),
formatAbsPercentage(binOldSize, bytesDeleted)));
} else {
sb.append(
Util.M.patchTableSize_ModifyBinaryFiles(
formatAbsBytes(bytesInserted), formatAbsBytes(bytesDeleted)));
}
}
sb.closeTh();
// delta2
sb.openTh().setStyleName(R.css().deltaColumn2());
if (showChangeSizeBars) {
int w = 80;
int t = inserted + deleted;
int i = Math.max(1, (int) (((double) w) * inserted / t));
int d = Math.max(1, (int) (((double) w) * deleted / t));
if (i + d > w && i > d) {
i = w - d;
} else if (i + d > w && d > i) {
d = w - i;
}
if (0 < inserted) {
sb.openDiv()
.setStyleName(R.css().inserted())
.setAttribute("style", "width:" + i + "px")
.closeDiv();
}
if (0 < deleted) {
sb.openDiv()
.setStyleName(R.css().deleted())
.setAttribute("style", "width:" + d + "px")
.closeDiv();
}
}
sb.closeTh();
sb.closeTr();
}
}
}