| // 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.ui; |
| |
| import com.google.gerrit.client.Gerrit; |
| import com.google.gwt.core.client.GWT; |
| import com.google.gwt.dom.client.Document; |
| import com.google.gwt.user.client.DOM; |
| import com.google.gwt.user.client.Element; |
| import com.google.gwt.user.client.ui.Composite; |
| import com.google.gwt.user.client.ui.FlexTable; |
| import com.google.gwt.user.client.ui.HTMLTable.CellFormatter; |
| import com.google.gwt.user.client.ui.Widget; |
| import com.google.gwtexpui.safehtml.client.SafeHtml; |
| |
| import java.util.Comparator; |
| import java.util.Iterator; |
| |
| public abstract class FancyFlexTable<RowItem> extends Composite { |
| private static final FancyFlexTableImpl impl = |
| GWT.create(FancyFlexTableImpl.class); |
| |
| protected static final int C_ARROW = 0; |
| |
| protected final MyFlexTable table; |
| |
| protected FancyFlexTable() { |
| table = createFlexTable(); |
| table.addStyleName(Gerrit.RESOURCES.css().changeTable()); |
| table.setWidth("100%"); |
| initWidget(table); |
| |
| table.setText(0, C_ARROW, ""); |
| table.getCellFormatter().addStyleName(0, C_ARROW, Gerrit.RESOURCES.css().iconHeader()); |
| } |
| |
| protected MyFlexTable createFlexTable() { |
| return new MyFlexTable(); |
| } |
| |
| protected RowItem getRowItem(final int row) { |
| return FancyFlexTable.<RowItem> getRowItem(table.getCellFormatter() |
| .getElement(row, 0)); |
| } |
| |
| protected void setRowItem(final int row, final RowItem item) { |
| setRowItem(table.getCellFormatter().getElement(row, 0), item); |
| } |
| |
| /** |
| * Finds an item in the table. |
| * |
| * @param comparator comparator by which the items in the table are sorted |
| * @param item the item that should be found |
| * @return if the item is found the number of the row that contains the item; |
| * if the item is not found <code>-1</code> |
| */ |
| protected int findRowItem(Comparator<RowItem> comparator, RowItem item) { |
| int row = lookupRowItem(comparator, item); |
| if (row < table.getRowCount() |
| && comparator.compare(item, getRowItem(row)) == 0) { |
| return row; |
| } |
| return -1; |
| } |
| |
| /** |
| * Finds the number of the row where a new item should be inserted into the |
| * table. |
| * |
| * @param comparator comparator by which the items in the table are sorted |
| * @param item the new item that should be inserted |
| * @return if the item is not yet contained in the table, the number of the |
| * row where the new item should be inserted; if the item is already |
| * contained in the table <code>-1</code> |
| */ |
| protected int getInsertRow(Comparator<RowItem> comparator, RowItem item) { |
| int row = lookupRowItem(comparator, item); |
| if (row >= table.getRowCount() |
| || comparator.compare(item, getRowItem(row)) != 0) { |
| return row; |
| } |
| return -1; |
| } |
| |
| /** |
| * Makes a binary search for the given row item over the table. |
| * |
| * @param comparator comparator by which the items in the table are sorted |
| * @param item the item that should be looked up |
| * @return if the item is found the number of the row that contains the item; |
| * if the item is not found the number of the row where the item |
| * should be inserted according to the given comparator. |
| */ |
| private int lookupRowItem(Comparator<RowItem> comparator, RowItem item) { |
| int left = 1; |
| int right = table.getRowCount() - 1; |
| while (left <= right) { |
| int middle = (left + right) >>> 1; // (left+right)/2 |
| RowItem i = getRowItem(middle); |
| int cmp = comparator.compare(i, item); |
| |
| if (cmp < 0) { |
| left = middle + 1; |
| } else if (cmp > 0) { |
| right = middle - 1; |
| } else { |
| // item is already contained in the table |
| return middle; |
| } |
| } |
| return left; |
| } |
| |
| protected void resetHtml(final SafeHtml body) { |
| for (final Iterator<Widget> i = table.iterator(); i.hasNext();) { |
| i.next(); |
| i.remove(); |
| } |
| impl.resetHtml(table, body); |
| } |
| |
| protected void scrollIntoView(final int topRow, final int endRow) { |
| final CellFormatter fmt = table.getCellFormatter(); |
| final Element top = DOM.getParent(fmt.getElement(topRow, C_ARROW)); |
| final Element end = DOM.getParent(fmt.getElement(endRow, C_ARROW)); |
| |
| final int rTop = top.getAbsoluteTop(); |
| final int rEnd = end.getAbsoluteTop() + end.getOffsetHeight(); |
| final int rHeight = rEnd - rTop; |
| |
| final int sTop = Document.get().getScrollTop(); |
| final int sHeight = Document.get().getClientHeight(); |
| final int sEnd = sTop + sHeight; |
| |
| final int nTop; |
| if (sHeight <= rHeight) { |
| // The region is larger than the visible area, make the top |
| // exactly the top of the region, its the most visible area. |
| // |
| nTop = rTop; |
| } else if (sTop <= rTop && rTop <= sEnd) { |
| // At least part of the region is already visible. |
| // |
| if (rEnd <= sEnd) { |
| // ... actually its all visible. Don't scroll. |
| // |
| return; |
| } |
| |
| // Move only enough to make the end visible. |
| // |
| nTop = sTop + (rHeight - (sEnd - rTop)); |
| } else { |
| // None of the region is visible. Make it visible. |
| // |
| nTop = rTop; |
| } |
| Document.get().setScrollTop(nTop); |
| } |
| |
| protected void applyDataRowStyle(final int newRow) { |
| table.getCellFormatter().addStyleName(newRow, C_ARROW, Gerrit.RESOURCES.css().iconCell()); |
| table.getCellFormatter().addStyleName(newRow, C_ARROW, Gerrit.RESOURCES.css().leftMostCell()); |
| } |
| |
| /** |
| * Get the td element that contains another element. |
| * |
| * @param target the child element whose parent td is required. |
| * @return the td containing element {@code target}; null if {@code target} is |
| * not a member of this table. |
| */ |
| protected Element getParentCell(final Element target) { |
| final Element body = FancyFlexTableImpl.getBodyElement(table); |
| for (Element td = target; td != null && td != body; td = DOM.getParent(td)) { |
| // If it's a TD, it might be the one we're looking for. |
| if ("td".equalsIgnoreCase(td.getTagName())) { |
| // Make sure it's directly a part of this table. |
| Element tr = DOM.getParent(td); |
| if (DOM.getParent(tr) == body) { |
| return td; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** @return the row of the child element; -1 if the child is not in the table. */ |
| protected int rowOf(final Element target) { |
| final Element td = getParentCell(target); |
| if (td == null) { |
| return -1; |
| } |
| final Element tr = DOM.getParent(td); |
| final Element body = DOM.getParent(tr); |
| return DOM.getChildIndex(body, tr); |
| } |
| |
| /** @return the cell of the child element; -1 if the child is not in the table. */ |
| protected int columnOf(final Element target) { |
| final Element td = getParentCell(target); |
| if (td == null) { |
| return -1; |
| } |
| final Element tr = DOM.getParent(td); |
| return DOM.getChildIndex(tr, td); |
| } |
| |
| protected static class MyFlexTable extends FlexTable { |
| } |
| |
| private static final native <ItemType> void setRowItem(Element td, ItemType c) |
| /*-{ td['__gerritRowItem'] = c; }-*/; |
| |
| private static final native <ItemType> ItemType getRowItem(Element td) |
| /*-{ return td['__gerritRowItem']; }-*/; |
| } |