| // 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.changes; |
| |
| import static com.google.gerrit.client.FormatUtil.shortFormat; |
| |
| import com.google.gerrit.client.FormatUtil; |
| import com.google.gerrit.client.Gerrit; |
| import com.google.gerrit.client.patches.PatchUtil; |
| import com.google.gerrit.client.rpc.GerritCallback; |
| import com.google.gerrit.client.ui.AccountDashboardLink; |
| import com.google.gerrit.client.ui.BranchLink; |
| import com.google.gerrit.client.ui.ChangeLink; |
| import com.google.gerrit.client.ui.NavigationTable; |
| import com.google.gerrit.client.ui.NeedsSignInKeyCommand; |
| import com.google.gerrit.client.ui.ProjectLink; |
| import com.google.gerrit.common.PageLinks; |
| import com.google.gerrit.common.data.AccountInfo; |
| import com.google.gerrit.common.data.AccountInfoCache; |
| import com.google.gerrit.common.data.ApprovalSummary; |
| import com.google.gerrit.common.data.ApprovalSummarySet; |
| import com.google.gerrit.common.data.ApprovalType; |
| import com.google.gerrit.common.data.ChangeInfo; |
| import com.google.gerrit.common.data.ToggleStarRequest; |
| import com.google.gerrit.reviewdb.Account; |
| import com.google.gerrit.reviewdb.ApprovalCategory; |
| import com.google.gerrit.reviewdb.ApprovalCategoryValue; |
| import com.google.gerrit.reviewdb.Change; |
| import com.google.gerrit.reviewdb.PatchSetApproval; |
| import com.google.gwt.dom.client.Element; |
| import com.google.gwt.event.dom.client.ClickEvent; |
| import com.google.gwt.event.dom.client.ClickHandler; |
| import com.google.gwt.event.dom.client.KeyCodes; |
| import com.google.gwt.event.dom.client.KeyPressEvent; |
| import com.google.gwt.resources.client.ImageResource; |
| import com.google.gwt.user.client.DOM; |
| import com.google.gwt.user.client.ui.Image; |
| import com.google.gwt.user.client.ui.UIObject; |
| import com.google.gwt.user.client.ui.Widget; |
| import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter; |
| import com.google.gwt.user.client.ui.HTMLTable.Cell; |
| import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; |
| import com.google.gwtjsonrpc.client.VoidResult; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| public class ChangeTable extends NavigationTable<ChangeInfo> { |
| private static final int C_STAR = 1; |
| private static final int C_ID = 2; |
| private static final int C_SUBJECT = 3; |
| private static final int C_OWNER = 4; |
| private static final int C_PROJECT = 5; |
| private static final int C_BRANCH = 6; |
| private static final int C_LAST_UPDATE = 7; |
| private static final int BASE_COLUMNS = 8; |
| |
| private final List<Section> sections; |
| private AccountInfoCache accountCache = AccountInfoCache.empty(); |
| private final List<ApprovalType> approvalTypes; |
| private final int columns; |
| |
| public ChangeTable() { |
| this(false); |
| } |
| |
| public ChangeTable(boolean showApprovals) { |
| approvalTypes = Gerrit.getConfig().getApprovalTypes().getApprovalTypes(); |
| if (showApprovals) { |
| columns = BASE_COLUMNS + approvalTypes.size(); |
| } else { |
| columns = BASE_COLUMNS; |
| } |
| |
| keysNavigation.add(new PrevKeyCommand(0, 'k', Util.C.changeTablePrev())); |
| keysNavigation.add(new NextKeyCommand(0, 'j', Util.C.changeTableNext())); |
| keysNavigation.add(new OpenKeyCommand(0, 'o', Util.C.changeTableOpen())); |
| keysNavigation.add(new OpenKeyCommand(0, KeyCodes.KEY_ENTER, Util.C |
| .changeTableOpen())); |
| |
| if (Gerrit.isSignedIn()) { |
| keysAction.add(new StarKeyCommand(0, 's', Util.C.changeTableStar())); |
| } |
| |
| sections = new ArrayList<Section>(); |
| table.setText(0, C_STAR, ""); |
| table.setText(0, C_ID, Util.C.changeTableColumnID()); |
| table.setText(0, C_SUBJECT, Util.C.changeTableColumnSubject()); |
| table.setText(0, C_OWNER, Util.C.changeTableColumnOwner()); |
| table.setText(0, C_PROJECT, Util.C.changeTableColumnProject()); |
| table.setText(0, C_BRANCH, Util.C.changeTableColumnBranch()); |
| table.setText(0, C_LAST_UPDATE, Util.C.changeTableColumnLastUpdate()); |
| for (int i = BASE_COLUMNS; i < columns; i++) { |
| final ApprovalType type = approvalTypes.get(i - BASE_COLUMNS); |
| final ApprovalCategory cat = type.getCategory(); |
| String text = cat.getAbbreviatedName(); |
| if (text == null) { |
| text = cat.getName(); |
| } |
| table.setText(0, i, text); |
| if (text != null) { |
| table.getCellFormatter().getElement(0, i).setTitle(cat.getName()); |
| } |
| } |
| |
| final FlexCellFormatter fmt = table.getFlexCellFormatter(); |
| fmt.addStyleName(0, C_STAR, Gerrit.RESOURCES.css().iconHeader()); |
| fmt.addStyleName(0, C_ID, Gerrit.RESOURCES.css().cID()); |
| for (int i = C_ID; i < columns; i++) { |
| fmt.addStyleName(0, i, Gerrit.RESOURCES.css().dataHeader()); |
| } |
| |
| table.addClickHandler(new ClickHandler() { |
| @Override |
| public void onClick(final ClickEvent event) { |
| final Cell cell = table.getCellForEvent(event); |
| if (cell == null) { |
| return; |
| } |
| if (cell.getCellIndex() == C_STAR) { |
| onStarClick(cell.getRowIndex()); |
| } else if (cell.getCellIndex() == C_OWNER) { |
| // Don't do anything. |
| } else if (getRowItem(cell.getRowIndex()) != null) { |
| movePointerTo(cell.getRowIndex()); |
| } |
| } |
| }); |
| } |
| |
| protected void onStarClick(final int row) { |
| final ChangeInfo c = getRowItem(row); |
| if (c != null && Gerrit.isSignedIn()) { |
| final boolean prior = c.isStarred(); |
| c.setStarred(!prior); |
| setStar(row, c); |
| |
| final ToggleStarRequest req = new ToggleStarRequest(); |
| req.toggle(c.getId(), c.isStarred()); |
| Util.LIST_SVC.toggleStars(req, new GerritCallback<VoidResult>() { |
| public void onSuccess(final VoidResult result) { |
| } |
| |
| @Override |
| public void onFailure(final Throwable caught) { |
| super.onFailure(caught); |
| c.setStarred(prior); |
| setStar(row, c); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| protected Object getRowItemKey(final ChangeInfo item) { |
| return item.getId(); |
| } |
| |
| @Override |
| protected void onOpenRow(final int row) { |
| final ChangeInfo c = getRowItem(row); |
| Gerrit.display(PageLinks.toChange(c), new ChangeScreen(c)); |
| } |
| |
| private void insertNoneRow(final int row) { |
| insertRow(row); |
| table.setText(row, 0, Util.C.changeTableNone()); |
| final FlexCellFormatter fmt = table.getFlexCellFormatter(); |
| fmt.setColSpan(row, 0, columns); |
| fmt.setStyleName(row, 0, Gerrit.RESOURCES.css().emptySection()); |
| } |
| |
| private void insertChangeRow(final int row) { |
| insertRow(row); |
| applyDataRowStyle(row); |
| } |
| |
| @Override |
| protected void applyDataRowStyle(final int row) { |
| super.applyDataRowStyle(row); |
| final CellFormatter fmt = table.getCellFormatter(); |
| fmt.addStyleName(row, C_STAR, Gerrit.RESOURCES.css().iconCell()); |
| for (int i = C_ID; i < columns; i++) { |
| fmt.addStyleName(row, i, Gerrit.RESOURCES.css().dataCell()); |
| } |
| fmt.addStyleName(row, C_ID, Gerrit.RESOURCES.css().cID()); |
| fmt.addStyleName(row, C_SUBJECT, Gerrit.RESOURCES.css().cSUBJECT()); |
| fmt.addStyleName(row, C_PROJECT, Gerrit.RESOURCES.css().cPROJECT()); |
| fmt.addStyleName(row, C_BRANCH, Gerrit.RESOURCES.css().cPROJECT()); |
| fmt.addStyleName(row, C_LAST_UPDATE, Gerrit.RESOURCES.css().cLastUpdate()); |
| for (int i = BASE_COLUMNS; i < columns; i++) { |
| fmt.addStyleName(row, i, Gerrit.RESOURCES.css().cAPPROVAL()); |
| } |
| } |
| |
| private void populateChangeRow(final int row, final ChangeInfo c) { |
| final String idstr = c.getKey().abbreviate(); |
| table.setWidget(row, C_ARROW, null); |
| if (Gerrit.isSignedIn()) { |
| setStar(row, c); |
| } |
| table.setWidget(row, C_ID, new TableChangeLink(idstr, c)); |
| |
| String s = c.getSubject(); |
| if (s.length() > 80) { |
| s = s.substring(0, 80); |
| } |
| if (c.getStatus() != null && c.getStatus() != Change.Status.NEW) { |
| s += " (" + c.getStatus().name() + ")"; |
| } |
| table.setWidget(row, C_SUBJECT, new TableChangeLink(s, c)); |
| table.setWidget(row, C_OWNER, link(c.getOwner())); |
| table.setWidget(row, C_PROJECT, new ProjectLink(c.getProject().getKey(), c |
| .getStatus())); |
| table.setWidget(row, C_BRANCH, new BranchLink(c.getProject().getKey(), c |
| .getStatus(), c.getBranch(), c.getTopic())); |
| table.setText(row, C_LAST_UPDATE, shortFormat(c.getLastUpdatedOn())); |
| setRowItem(row, c); |
| } |
| |
| private AccountDashboardLink link(final Account.Id id) { |
| return AccountDashboardLink.link(accountCache, id); |
| } |
| |
| private void setStar(final int row, final ChangeInfo c) { |
| final ImageResource star; |
| if (c.isStarred()) { |
| star = Gerrit.RESOURCES.starFilled(); |
| } else { |
| star = Gerrit.RESOURCES.starOpen(); |
| } |
| |
| final Widget i = table.getWidget(row, C_STAR); |
| if (i instanceof Image) { |
| ((Image) i).setResource(star); |
| } else { |
| table.setWidget(row, C_STAR, new Image(star)); |
| } |
| } |
| |
| public void addSection(final Section s) { |
| assert s.parent == null; |
| |
| if (s.titleText != null) { |
| s.titleRow = table.getRowCount(); |
| table.setText(s.titleRow, 0, s.titleText); |
| final FlexCellFormatter fmt = table.getFlexCellFormatter(); |
| fmt.setColSpan(s.titleRow, 0, columns); |
| fmt.addStyleName(s.titleRow, 0, Gerrit.RESOURCES.css().sectionHeader()); |
| } else { |
| s.titleRow = -1; |
| } |
| |
| s.parent = this; |
| s.dataBegin = table.getRowCount(); |
| insertNoneRow(s.dataBegin); |
| sections.add(s); |
| } |
| |
| public void setAccountInfoCache(final AccountInfoCache aic) { |
| assert aic != null; |
| accountCache = aic; |
| } |
| |
| private int insertRow(final int beforeRow) { |
| for (final Section s : sections) { |
| if (beforeRow <= s.titleRow) { |
| s.titleRow++; |
| } |
| if (beforeRow < s.dataBegin) { |
| s.dataBegin++; |
| } |
| } |
| return table.insertRow(beforeRow); |
| } |
| |
| private void removeRow(final int row) { |
| for (final Section s : sections) { |
| if (row < s.titleRow) { |
| s.titleRow--; |
| } |
| if (row < s.dataBegin) { |
| s.dataBegin--; |
| } |
| } |
| table.removeRow(row); |
| } |
| |
| private void displayApprovals(final int row, final ApprovalSummary summary, |
| final AccountInfoCache aic, final boolean highlightUnreviewed) { |
| final CellFormatter fmt = table.getCellFormatter(); |
| final Map<ApprovalCategory.Id, PatchSetApproval> approvals = |
| summary.getApprovalMap(); |
| int col = BASE_COLUMNS; |
| boolean haveReview = false; |
| |
| for (final ApprovalType type : approvalTypes) { |
| final PatchSetApproval ca = approvals.get(type.getCategory().getId()); |
| |
| fmt.removeStyleName(row, col, Gerrit.RESOURCES.css().negscore()); |
| fmt.removeStyleName(row, col, Gerrit.RESOURCES.css().posscore()); |
| |
| if (ca == null || ca.getValue() == 0) { |
| table.clearCell(row, col); |
| |
| } else { |
| haveReview = true; |
| |
| if (type.isMaxNegative(ca)) { |
| table.setWidget(row, col, new Image(Gerrit.RESOURCES.redNot())); |
| |
| } else if (type.isMaxPositive(ca)) { |
| table.setWidget(row, col, new Image(Gerrit.RESOURCES.greenCheck())); |
| |
| } else { |
| String vstr = String.valueOf(ca.getValue()); |
| if (ca.getValue() > 0) { |
| vstr = "+" + vstr; |
| fmt.addStyleName(row, col, Gerrit.RESOURCES.css().posscore()); |
| } else { |
| fmt.addStyleName(row, col, Gerrit.RESOURCES.css().negscore()); |
| } |
| table.setText(row, col, vstr); |
| } |
| |
| final ApprovalCategoryValue acv = type.getValue(ca); |
| final AccountInfo ai = aic.get(ca.getAccountId()); |
| |
| // Some web browsers ignore the embedded newline; some like it; |
| // so we include a space before the newline to accommodate both. |
| // |
| fmt.getElement(row, col).setTitle( |
| acv.getName() + " \nby " + FormatUtil.nameEmail(ai)); |
| } |
| |
| col++; |
| } |
| |
| final Element tr = DOM.getParent(fmt.getElement(row, 0)); |
| UIObject.setStyleName(tr, Gerrit.RESOURCES.css().needsReview(), !haveReview |
| && highlightUnreviewed); |
| } |
| |
| GerritCallback<ApprovalSummarySet> approvalFormatter(final int dataBegin, |
| final int rows, final boolean highlightUnreviewed) { |
| return new GerritCallback<ApprovalSummarySet>() { |
| @Override |
| public void onSuccess(final ApprovalSummarySet as) { |
| Map<Change.Id, ApprovalSummary> ids = as.getSummaryMap(); |
| AccountInfoCache aic = as.getAccountInfoCache(); |
| for (int row = dataBegin; row < dataBegin + rows; row++) { |
| final ChangeInfo c = getRowItem(row); |
| if (ids.containsKey(c.getId())) { |
| displayApprovals(row, ids.get(c.getId()), aic, highlightUnreviewed); |
| } |
| } |
| } |
| }; |
| } |
| |
| public class StarKeyCommand extends NeedsSignInKeyCommand { |
| public StarKeyCommand(int mask, char key, String help) { |
| super(mask, key, help); |
| } |
| |
| @Override |
| public void onKeyPress(final KeyPressEvent event) { |
| onStarClick(getCurrentRow()); |
| } |
| } |
| |
| private final class TableChangeLink extends ChangeLink { |
| private TableChangeLink(final String text, final ChangeInfo c) { |
| super(text, c); |
| } |
| |
| @Override |
| public void go() { |
| movePointerTo(id); |
| super.go(); |
| } |
| } |
| |
| public enum ApprovalViewType { |
| NONE, USER, STRONGEST |
| } |
| |
| public static class Section { |
| String titleText; |
| |
| ChangeTable parent; |
| final ApprovalViewType viewType; |
| final Account.Id ownerId; |
| int titleRow = -1; |
| int dataBegin; |
| int rows; |
| |
| public Section() { |
| this(null, ApprovalViewType.NONE, null); |
| } |
| |
| public Section(final String titleText) { |
| this(titleText, ApprovalViewType.NONE, null); |
| } |
| |
| public Section(final String titleText, final ApprovalViewType view, |
| final Account.Id owner) { |
| setTitleText(titleText); |
| viewType = view; |
| ownerId = owner; |
| } |
| |
| public void setTitleText(final String text) { |
| titleText = text; |
| if (titleRow >= 0) { |
| parent.table.setText(titleRow, 0, titleText); |
| } |
| } |
| |
| public void display(final List<ChangeInfo> changeList) { |
| final int sz = changeList != null ? changeList.size() : 0; |
| final boolean hadData = rows > 0; |
| |
| if (hadData) { |
| while (sz < rows) { |
| parent.removeRow(dataBegin); |
| rows--; |
| } |
| } |
| |
| if (sz == 0) { |
| if (hadData) { |
| parent.insertNoneRow(dataBegin); |
| } |
| } else { |
| Set<Change.Id> cids = new HashSet<Change.Id>(); |
| |
| if (!hadData) { |
| parent.removeRow(dataBegin); |
| } |
| |
| while (rows < sz) { |
| parent.insertChangeRow(dataBegin + rows); |
| rows++; |
| } |
| |
| for (int i = 0; i < sz; i++) { |
| ChangeInfo c = changeList.get(i); |
| parent.populateChangeRow(dataBegin + i, c); |
| cids.add(c.getId()); |
| } |
| |
| switch (viewType) { |
| case NONE: |
| break; |
| case USER: |
| PatchUtil.DETAIL_SVC.userApprovals(cids, ownerId, parent |
| .approvalFormatter(dataBegin, rows, true)); |
| break; |
| case STRONGEST: |
| PatchUtil.DETAIL_SVC.strongestApprovals(cids, parent |
| .approvalFormatter(dataBegin, rows, false)); |
| break; |
| } |
| } |
| } |
| } |
| } |