blob: 618412fa3af30cf91e6f45a0806ecc2718913583 [file] [log] [blame]
// 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.FormatUtil;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.SignedInListener;
import com.google.gerrit.client.changes.Util;
import com.google.gerrit.client.data.AccountInfoCache;
import com.google.gerrit.client.reviewdb.Patch;
import com.google.gerrit.client.reviewdb.PatchLineComment;
import com.google.gerrit.client.reviewdb.PatchSet;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.ComplexDisclosurePanel;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.KeyboardListener;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtjsonrpc.client.VoidResult;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractPatchContentTable extends FancyFlexTable<Object> {
private static final long AGE = 7 * 24 * 60 * 60 * 1000L;
protected AccountInfoCache accountCache = AccountInfoCache.empty();
protected Patch.Key patchKey;
protected List<PatchSet.Id> versions;
private final Timestamp aged =
new Timestamp(System.currentTimeMillis() - AGE);
private final SignedInListener signedInListener = new SignedInListener() {
public void onSignIn() {
if (patchKey != null) {
PatchUtil.DETAIL_SVC.myDrafts(patchKey,
new GerritCallback<List<PatchLineComment>>() {
public void onSuccess(final List<PatchLineComment> result) {
if (!result.isEmpty()) {
bindDrafts(result);
}
}
});
}
}
public void onSignOut() {
// TODO we should probably confirm with the user before sign out starts
// that its OK to sign out if any of our editors are unsaved.
// (bug GERRIT-16)
//
for (int row = 0; row < table.getRowCount();) {
final int nCells = table.getCellCount(row);
int inc = 1;
for (int cell = 0; cell < nCells; cell++) {
if (table.getWidget(row, cell) instanceof CommentEditorPanel) {
destroyEditor(table, row, cell);
inc = 0;
}
}
row += inc;
}
}
};
protected AbstractPatchContentTable() {
table.setStyleName("gerrit-PatchContentTable");
}
@Override
public void onLoad() {
super.onLoad();
Gerrit.addSignedInListener(signedInListener);
}
@Override
public void onUnload() {
Gerrit.removeSignedInListener(signedInListener);
super.onUnload();
}
@Override
protected MyFlexTable createFlexTable() {
return new DoubleClickFlexTable();
}
@Override
protected boolean onKeyPress(final char keyCode, final int modifiers) {
if (modifiers == 0) {
switch (keyCode) {
case KeyboardListener.KEY_UP:
case KeyboardListener.KEY_DOWN:
return false;
case 'c':
case 'r':
for (int row = getCurrentRow(); 0 <= row; row--) {
final Object item = getRowItem(row);
if (!(item instanceof CommentList) && item != null) {
onOpenItem(item);
break;
}
}
return true;
}
}
return super.onKeyPress(keyCode, modifiers);
}
@Override
protected Object getRowItemKey(final Object item) {
return null;
}
/** Invoked when the user clicks on a table cell. */
protected abstract void onCellDoubleClick(int row, int column);
protected abstract void bindDrafts(List<PatchLineComment> drafts);
protected void initVersions(int fileCnt) {
if (versions == null) {
versions = new ArrayList<PatchSet.Id>();
for (int file = 0; file < fileCnt - 1; file++) {
versions.add(PatchSet.BASE);
}
versions.add(patchKey.getParentKey());
}
}
protected List<PatchSet.Id> getVersions() {
return versions;
}
protected PatchSet.Id getVersion(final int fileId) {
return versions.get(fileId);
}
protected void setVersion(final int fileId, final PatchSet.Id v) {
versions.set(fileId, v);
}
protected int fileFor(final PatchLineComment c) {
int fileId;
for (fileId = 0; fileId < versions.size(); fileId++) {
final PatchSet.Id i = versions.get(fileId);
if (PatchSet.BASE.equals(i) && c.getSide() == fileId
&& patchKey.equals(c.getKey().getParentKey())) {
break;
}
if (c.getSide() == versions.size() - 1
&& i.equals(c.getKey().getParentKey().getParentKey())) {
break;
}
}
return fileId;
}
protected void createCommentEditor(final int suggestRow, final int column,
final int line, final short file) {
int row = suggestRow;
int spans[] = new int[column + 1];
OUTER: while (row < table.getRowCount()) {
int col = 0;
for (int cell = 0; cell < table.getCellCount(row); cell++) {
while (col < column && 0 < spans[col]) {
spans[col++]--;
}
spans[col] = table.getFlexCellFormatter().getRowSpan(row, cell);
if (col == column) {
if (table.getWidget(row, cell) instanceof ComplexDisclosurePanel) {
row++;
} else {
break OUTER;
}
}
}
}
if (column < table.getCellCount(row)
&& table.getWidget(row, column) instanceof CommentEditorPanel) {
// Don't insert two editors on the same position, it doesn't make
// any sense to the user.
//
((CommentEditorPanel) table.getWidget(row, column)).setFocus(true);
return;
}
if (!Gerrit.isSignedIn()) {
Gerrit.doSignIn(new AsyncCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
createCommentEditor(suggestRow, column, line, file);
}
public void onFailure(Throwable caught) {
}
});
return;
}
final Patch.Key parentKey;
final short side;
if (PatchSet.BASE.equals(getVersion(file))) {
parentKey = patchKey;
side = file;
} else {
parentKey = new Patch.Key(getVersion(file), patchKey.get());
side = (short) 1;
}
final PatchLineComment newComment =
new PatchLineComment(new PatchLineComment.Key(parentKey, null), line,
Gerrit.getUserAccount().getId());
newComment.setSide(side);
newComment.setMessage("");
final CommentEditorPanel ed = new CommentEditorPanel(newComment);
boolean needInsert = true;
for (int cell = 0; cell < table.getCellCount(row); cell++) {
final Widget w = table.getWidget(row, cell);
if (w instanceof CommentEditorPanel
|| w instanceof ComplexDisclosurePanel) {
needInsert = false;
break;
}
}
if (needInsert) {
table.insertRow(row);
table.getCellFormatter().setStyleName(row, 0, S_ICON_CELL);
}
table.setWidget(row, column, ed);
table.getFlexCellFormatter().setStyleName(row, column, "Comment");
int span = 1;
for (int r = row + 1; r < table.getRowCount(); r++) {
boolean hasComment = false;
for (int c = 0; c < table.getCellCount(r); c++) {
final Widget w = table.getWidget(r, c);
if (w instanceof ComplexDisclosurePanel
|| w instanceof CommentEditorPanel) {
hasComment = true;
break;
}
}
if (hasComment) {
table.removeCell(r, column);
span++;
} else {
break;
}
}
if (span > 1) {
table.getFlexCellFormatter().setRowSpan(row, column, span);
}
for (int r = row - 1; r > 0; r--) {
if (getRowItem(r) instanceof CommentList) {
continue;
} else if (getRowItem(r) != null) {
movePointerTo(r);
break;
}
}
ed.setFocus(true);
}
@Override
protected void onOpenItem(final Object item) {
if (item instanceof CommentList) {
for (final ComplexDisclosurePanel p : ((CommentList) item).panels) {
p.setOpen(!p.isOpen());
}
}
}
public void setAccountInfoCache(final AccountInfoCache aic) {
assert aic != null;
accountCache = aic;
}
public void setPatchKey(final Patch.Key id) {
patchKey = id;
}
static void destroyEditor(final FlexTable table, final int row, final int col) {
table.clearCell(row, col);
final int span = table.getFlexCellFormatter().getRowSpan(row, col);
boolean removeRow = true;
final int nCells = table.getCellCount(row);
for (int cell = 0; cell < nCells; cell++) {
if (table.getWidget(row, cell) != null) {
removeRow = false;
break;
}
}
if (removeRow) {
for (int r = row - 1; 0 <= r; r--) {
boolean data = false;
for (int c = 0; c < table.getCellCount(r); c++) {
data |= table.getWidget(r, c) != null;
final int s = table.getFlexCellFormatter().getRowSpan(r, c) - 1;
if (r + s == row) {
table.getFlexCellFormatter().setRowSpan(r, c, s);
}
}
if (!data) {
break;
}
}
table.removeRow(row);
} else if (span != 1) {
table.getFlexCellFormatter().setRowSpan(row, col, 1);
for (int r = row + 1; r < row + span; r++) {
table.insertCell(r, col + 1);
}
}
}
protected void bindComment(final int row, final int col,
final PatchLineComment line, final boolean isLast) {
if (line.getStatus() == PatchLineComment.Status.DRAFT) {
boolean takeFocus = false;
if (row + 1 < table.getRowCount() && col < table.getCellCount(row + 1)
&& table.getWidget(row + 1, col) instanceof CommentEditorPanel
&& ((CommentEditorPanel) table.getWidget(row + 1, col)).isNew()) {
// Assume the second panel is a new one created while logging in;
// this line is from the myDrafts callback and we discovered we
// already have a draft at this location. We want to destroy the
// dummy editor and keep the real one.
//
destroyEditor(table, row + 1, col);
takeFocus = true;
}
final CommentEditorPanel plc = new CommentEditorPanel(line);
table.setWidget(row, col, plc);
table.getFlexCellFormatter().setStyleName(row, col, "Comment");
if (takeFocus) {
plc.setFocus(true);
}
return;
}
final LineCommentPanel mp = new LineCommentPanel(line);
String panelHeader;
final ComplexDisclosurePanel panel;
if (line.getAuthor() != null) {
panelHeader = FormatUtil.nameEmail(accountCache.get(line.getAuthor()));
} else {
panelHeader = Util.C.messageNoAuthor();
}
if (isLast) {
mp.isRecent = true;
} else {
// TODO Instead of opening messages by strict age, do it by "unread"?
mp.isRecent = line.getWrittenOn().after(aged);
}
panel = new ComplexDisclosurePanel(panelHeader, mp.isRecent);
panel.getHeader().add(
new InlineLabel(Util.M.messageWrittenOn(FormatUtil.mediumFormat(line
.getWrittenOn()))));
panel.setContent(mp);
table.setWidget(row, col, panel);
table.getFlexCellFormatter().setStyleName(row, col, "Comment");
CommentList l = (CommentList) getRowItem(row);
if (l == null) {
l = new CommentList();
setRowItem(row, l);
}
l.comments.add(line);
l.panels.add(panel);
}
protected static class CommentList {
final List<PatchLineComment> comments = new ArrayList<PatchLineComment>();
final List<ComplexDisclosurePanel> panels =
new ArrayList<ComplexDisclosurePanel>();
}
protected class DoubleClickFlexTable extends MyFlexTable {
public DoubleClickFlexTable() {
sinkEvents(Event.ONDBLCLICK | Event.ONCLICK);
}
@Override
public void onBrowserEvent(final Event event) {
switch (DOM.eventGetType(event)) {
case Event.ONCLICK: {
// Find out which cell was actually clicked.
final Element td = getEventTargetCell(event);
if (td == null) {
break;
}
final Element tr = DOM.getParent(td);
final Element body = DOM.getParent(tr);
final int row = DOM.getChildIndex(body, tr);
if (getRowItem(row) != null) {
movePointerTo(row);
return;
}
break;
}
case Event.ONDBLCLICK: {
// Find out which cell was actually clicked.
Element td = getEventTargetCell(event);
if (td == null) {
return;
}
Element tr = DOM.getParent(td);
Element body = DOM.getParent(tr);
int row = DOM.getChildIndex(body, tr);
int column = DOM.getChildIndex(tr, td);
onCellDoubleClick(row, column);
return;
}
}
super.onBrowserEvent(event);
}
}
}