1package autotest.common.table;
2
3import autotest.common.DomUtils;
4import autotest.common.ui.RightClickTable;
5
6import com.google.gwt.event.dom.client.ClickEvent;
7import com.google.gwt.user.client.DOM;
8import com.google.gwt.user.client.Element;
9import com.google.gwt.user.client.Event;
10import com.google.gwt.user.client.ui.HTMLTable;
11
12import java.util.ArrayList;
13import java.util.List;
14
15/**
16 * Customized table class supporting multiple tbody elements.  It is modified to support input
17 * handling, getRowCount(), getCellCount(), and getCellFormatter().getElement().  getElement()
18 * also works.  Calls to other methods aren't guaranteed to work.
19 */
20public class FragmentedTable extends RightClickTable {
21    public class FragmentedCellFormatter extends HTMLTable.CellFormatter {
22        @Override
23        public Element getElement(int row, int column) {
24            checkCellBounds(row, column);
25            Element bodyElem = bodyElems.get(getFragmentIndex(row));
26            return getCellElement(bodyElem, getRowWithinFragment(row), column);
27        }
28
29        /**
30         * Native method to efficiently get a td element from a tbody. Copied from GWT's
31         * HTMLTable.java.
32         */
33        private native Element getCellElement(Element tbody, int row, int col) /*-{
34            return tbody.rows[row].cells[col];
35        }-*/;
36    }
37
38    private List<Element> bodyElems = new ArrayList<Element>();
39    private int totalRowCount;
40    private int rowsPerFragment;
41
42    public FragmentedTable() {
43        super();
44        setCellFormatter(new FragmentedCellFormatter());
45
46        // Reset the FragmentedTable to clear out elements that were added by the HTMLTable and
47        // FlexTable constructors
48        reset();
49    }
50
51    /**
52     * This method must be called after added or removing tbody elements and before using other
53     * functionality (accessing cell elements, input handling, etc.).
54     */
55    public void updateBodyElems() {
56        totalRowCount = 0;
57        Element tbody = DOM.getFirstChild(getElement());
58        for(; tbody != null; tbody = DOM.getNextSibling(tbody)) {
59            assert tbody.getTagName().equalsIgnoreCase("tbody");
60            bodyElems.add(tbody);
61            totalRowCount += getRowCount(tbody);
62        }
63    }
64
65    public void reset() {
66        bodyElems.clear();
67        DomUtils.clearDomChildren(getElement());
68    }
69
70    private int getRowWithinFragment(int row) {
71        return row % rowsPerFragment;
72    }
73
74    private int getFragmentIndex(int row) {
75        return row / rowsPerFragment;
76    }
77
78    @Override
79    public HTMLTable.Cell getCellForEvent(ClickEvent event) {
80        return getCellForDomEvent(event);
81    }
82
83    @Override
84    protected RowColumn getCellPosition(Element td) {
85        Element tr = DOM.getParent(td);
86        Element body = DOM.getParent(tr);
87        int fragmentIndex = DOM.getChildIndex(getElement(), body);
88        int rowWithinFragment = DOM.getChildIndex(body, tr);
89        int row = fragmentIndex * rowsPerFragment + rowWithinFragment;
90        int column = DOM.getChildIndex(tr, td);
91        return new RowColumn(row, column);
92    }
93
94    /**
95     * This is a modified version of getEventTargetCell() from HTMLTable.java.
96     */
97    @Override
98    protected Element getEventTargetCell(Event event) {
99        Element td = DOM.eventGetTarget(event);
100        for (; td != null; td = DOM.getParent(td)) {
101            // If it's a TD, it might be the one we're looking for.
102            if (DOM.getElementProperty(td, "tagName").equalsIgnoreCase("td")) {
103                // Make sure it's directly a part of this table before returning
104                // it.
105                Element tr = DOM.getParent(td);
106                Element body = DOM.getParent(tr);
107                Element tableElem = DOM.getParent(body);
108                if (tableElem == getElement()) {
109                    return td;
110                }
111            }
112            // If we run into this table's element, we're out of options.
113            if (td == getElement()) {
114                return null;
115            }
116        }
117        return null;
118    }
119
120    @Override
121    public int getCellCount(int row) {
122        Element bodyElem = bodyElems.get(getFragmentIndex(row));
123        return getCellCount(bodyElem, getRowWithinFragment(row));
124    }
125
126    @Override
127    public int getRowCount() {
128        return totalRowCount;
129    }
130
131    private native int getRowCount(Element tbody) /*-{
132        return tbody.rows.length;
133    }-*/;
134
135    private native int getCellCount(Element tbody, int row) /*-{
136        return tbody.rows[row].cells.length;
137    }-*/;
138
139    /**
140     * This must be called before using other functionality (accessing cell elements, input
141     * handling, etc.).
142     * @param rowsPerFragment  The number of rows in each tbody.  The last tbody may have fewer
143     * rows.  All others must have exactly this number of rows.
144     */
145    public void setRowsPerFragment(int rowsPerFragment) {
146        this.rowsPerFragment = rowsPerFragment;
147    }
148}
149