Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(86)

Side by Side Diff: frontend/client/src/autotest/common/spreadsheet/Spreadsheet.java

Issue 1595019: Merge remote branch 'origin/upstream' into tempbranch (Closed)
Patch Set: Created 10 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 package autotest.common.spreadsheet;
2
3 import autotest.common.UnmodifiableSublistView;
4 import autotest.common.Utils;
5 import autotest.common.table.FragmentedTable;
6 import autotest.common.table.TableRenderer;
7 import autotest.common.ui.RightClickTable;
8
9 import com.google.gwt.dom.client.Element;
10 import com.google.gwt.event.dom.client.ClickEvent;
11 import com.google.gwt.event.dom.client.ClickHandler;
12 import com.google.gwt.event.dom.client.ContextMenuEvent;
13 import com.google.gwt.event.dom.client.ContextMenuHandler;
14 import com.google.gwt.event.dom.client.DomEvent;
15 import com.google.gwt.event.dom.client.ScrollEvent;
16 import com.google.gwt.event.dom.client.ScrollHandler;
17 import com.google.gwt.user.client.DeferredCommand;
18 import com.google.gwt.user.client.IncrementalCommand;
19 import com.google.gwt.user.client.Window;
20 import com.google.gwt.user.client.ui.Composite;
21 import com.google.gwt.user.client.ui.FlexTable;
22 import com.google.gwt.user.client.ui.HTMLTable;
23 import com.google.gwt.user.client.ui.Panel;
24 import com.google.gwt.user.client.ui.ScrollPanel;
25 import com.google.gwt.user.client.ui.SimplePanel;
26 import com.google.gwt.user.client.ui.Widget;
27
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33
34 public class Spreadsheet extends Composite
35 implements ScrollHandler, ClickHandler, ContextMenuHandler {
36
37 private static final int MIN_TABLE_SIZE_PX = 90;
38 private static final int WINDOW_BORDER_PX = 15;
39 private static final int SCROLLBAR_FUDGE = 16;
40 private static final String BLANK_STRING = "(empty)";
41 private static final int CELL_PADDING_PX = 2;
42 private static final int TD_BORDER_PX = 1;
43 private static final String HIGHLIGHTED_CLASS = "highlighted";
44 private static final int CELLS_PER_ITERATION = 1000;
45
46 private Header rowFields, columnFields;
47 private List<Header> rowHeaderValues = new ArrayList<Header>();
48 private List<Header> columnHeaderValues = new ArrayList<Header>();
49 private Map<Header, Integer> rowHeaderMap = new HashMap<Header, Integer>();
50 private Map<Header, Integer> columnHeaderMap = new HashMap<Header, Integer>( );
51 protected CellInfo[][] dataCells, rowHeaderCells, columnHeaderCells;
52 private RightClickTable rowHeaders = new RightClickTable();
53 private RightClickTable columnHeaders = new RightClickTable();
54 private FlexTable parentTable = new FlexTable();
55 private FragmentedTable dataTable = new FragmentedTable();
56 private int rowsPerIteration;
57 private Panel rowHeadersClipPanel, columnHeadersClipPanel;
58 private ScrollPanel scrollPanel = new ScrollPanel(dataTable);
59 private TableRenderer renderer = new TableRenderer();
60
61 private SpreadsheetListener listener;
62
63 public interface SpreadsheetListener {
64 public void onCellClicked(CellInfo cellInfo, boolean isRightClick);
65 }
66
67 public static interface Header extends List<String> {}
68 public static class HeaderImpl extends ArrayList<String> implements Header {
69 public HeaderImpl() {
70 }
71
72 public HeaderImpl(Collection<? extends String> arg0) {
73 super(arg0);
74 }
75
76 public static Header fromBaseType(List<String> baseType) {
77 return new HeaderImpl(baseType);
78 }
79 }
80
81 public static class CellInfo {
82 public Header row, column;
83 public String contents;
84 public String color;
85 public Integer widthPx, heightPx;
86 public int rowSpan = 1, colSpan = 1;
87 public int testCount = 0;
88 public int testIndex;
89
90 public CellInfo(Header row, Header column, String contents) {
91 this.row = row;
92 this.column = column;
93 this.contents = contents;
94 }
95
96 public boolean isHeader() {
97 return !isEmpty() && (row == null || column == null);
98 }
99
100 public boolean isEmpty() {
101 return row == null && column == null;
102 }
103 }
104
105 private class RenderCommand implements IncrementalCommand {
106 private int state = 0;
107 private int rowIndex = 0;
108 private IncrementalCommand onFinished;
109
110 public RenderCommand(IncrementalCommand onFinished) {
111 this.onFinished = onFinished;
112 }
113
114 private void renderSomeRows() {
115 renderer.renderRowsAndAppend(dataTable, dataCells,
116 rowIndex, rowsPerIteration, true);
117 rowIndex += rowsPerIteration;
118 if (rowIndex > dataCells.length) {
119 state++;
120 }
121 }
122
123 public boolean execute() {
124 switch (state) {
125 case 0:
126 computeRowsPerIteration();
127 computeHeaderCells();
128 break;
129 case 1:
130 renderHeaders();
131 expandRowHeaders();
132 break;
133 case 2:
134 // resize everything to the max dimensions (the window size)
135 fillWindow(false);
136 break;
137 case 3:
138 // set main table to match header sizes
139 matchRowHeights(rowHeaders, dataCells);
140 matchColumnWidths(columnHeaders, dataCells);
141 dataTable.setVisible(false);
142 break;
143 case 4:
144 // render the main data table
145 renderSomeRows();
146 return true;
147 case 5:
148 dataTable.updateBodyElems();
149 dataTable.setVisible(true);
150 break;
151 case 6:
152 // now expand headers as necessary
153 // this can be very slow, so put it in it's own cycle
154 matchRowHeights(dataTable, rowHeaderCells);
155 break;
156 case 7:
157 matchColumnWidths(dataTable, columnHeaderCells);
158 renderHeaders();
159 break;
160 case 8:
161 // shrink the scroller if the table ended up smaller than th e window
162 fillWindow(true);
163 DeferredCommand.addCommand(onFinished);
164 return false;
165 }
166
167 state++;
168 return true;
169 }
170 }
171
172 public Spreadsheet() {
173 dataTable.setStyleName("spreadsheet-data");
174 killPaddingAndSpacing(dataTable);
175
176 rowHeaders.setStyleName("spreadsheet-headers");
177 killPaddingAndSpacing(rowHeaders);
178 rowHeadersClipPanel = wrapWithClipper(rowHeaders);
179
180 columnHeaders.setStyleName("spreadsheet-headers");
181 killPaddingAndSpacing(columnHeaders);
182 columnHeadersClipPanel = wrapWithClipper(columnHeaders);
183
184 scrollPanel.setStyleName("spreadsheet-scroller");
185 scrollPanel.setAlwaysShowScrollBars(true);
186 scrollPanel.addScrollHandler(this);
187
188 parentTable.setStyleName("spreadsheet-parent");
189 killPaddingAndSpacing(parentTable);
190 parentTable.setWidget(0, 1, columnHeadersClipPanel);
191 parentTable.setWidget(1, 0, rowHeadersClipPanel);
192 parentTable.setWidget(1, 1, scrollPanel);
193
194 setupTableInput(dataTable);
195 setupTableInput(rowHeaders);
196 setupTableInput(columnHeaders);
197
198 initWidget(parentTable);
199 }
200
201 private void setupTableInput(RightClickTable table) {
202 table.addContextMenuHandler(this);
203 table.addClickHandler(this);
204 }
205
206 protected void killPaddingAndSpacing(HTMLTable table) {
207 table.setCellSpacing(0);
208 table.setCellPadding(0);
209 }
210
211 /*
212 * Wrap a widget with a panel that will clip its contents rather than grow
213 * too much.
214 */
215 protected Panel wrapWithClipper(Widget w) {
216 SimplePanel wrapper = new SimplePanel();
217 wrapper.add(w);
218 wrapper.setStyleName("clipper");
219 return wrapper;
220 }
221
222 public void setHeaderFields(Header rowFields, Header columnFields) {
223 this.rowFields = rowFields;
224 this.columnFields = columnFields;
225 }
226
227 private void addHeader(List<Header> headerList, Map<Header, Integer> headerM ap,
228 List<String> header) {
229 Header headerObject = HeaderImpl.fromBaseType(header);
230 assert !headerMap.containsKey(headerObject);
231 headerList.add(headerObject);
232 headerMap.put(headerObject, headerMap.size());
233 }
234
235 public void addRowHeader(List<String> header) {
236 addHeader(rowHeaderValues, rowHeaderMap, header);
237 }
238
239 public void addColumnHeader(List<String> header) {
240 addHeader(columnHeaderValues, columnHeaderMap, header);
241 }
242
243 private int getHeaderPosition(Map<Header, Integer> headerMap, Header header) {
244 assert headerMap.containsKey(header);
245 return headerMap.get(header);
246 }
247
248 private int getRowPosition(Header rowHeader) {
249 return getHeaderPosition(rowHeaderMap, rowHeader);
250 }
251
252 private int getColumnPosition(Header columnHeader) {
253 return getHeaderPosition(columnHeaderMap, columnHeader);
254 }
255
256 /**
257 * Must be called after adding headers but before adding data
258 */
259 public void prepareForData() {
260 dataCells = new CellInfo[rowHeaderValues.size()][columnHeaderValues.size ()];
261 }
262
263 public CellInfo getCellInfo(int row, int column) {
264 Header rowHeader = rowHeaderValues.get(row);
265 Header columnHeader = columnHeaderValues.get(column);
266 if (dataCells[row][column] == null) {
267 dataCells[row][column] = new CellInfo(rowHeader, columnHeader, "");
268 }
269 return dataCells[row][column];
270 }
271
272 private CellInfo getCellInfo(CellInfo[][] cells, int row, int column) {
273 if (cells[row][column] == null) {
274 cells[row][column] = new CellInfo(null, null, " ");
275 }
276 return cells[row][column];
277 }
278
279 /**
280 * Render the data into HTML tables. Done through a deferred command.
281 */
282 public void render(IncrementalCommand onFinished) {
283 DeferredCommand.addCommand(new RenderCommand(onFinished));
284 }
285
286 private void renderHeaders() {
287 renderer.renderRows(rowHeaders, rowHeaderCells, false);
288 renderer.renderRows(columnHeaders, columnHeaderCells, false);
289 }
290
291 public void computeRowsPerIteration() {
292 int cellsPerRow = columnHeaderValues.size();
293 rowsPerIteration = Math.max(CELLS_PER_ITERATION / cellsPerRow, 1);
294 dataTable.setRowsPerFragment(rowsPerIteration);
295 }
296
297 private void computeHeaderCells() {
298 rowHeaderCells = new CellInfo[rowHeaderValues.size()][rowFields.size()];
299 fillHeaderCells(rowHeaderCells, rowFields, rowHeaderValues, true);
300
301 columnHeaderCells = new CellInfo[columnFields.size()][columnHeaderValues .size()];
302 fillHeaderCells(columnHeaderCells, columnFields, columnHeaderValues, fal se);
303 }
304
305 /**
306 * TODO (post-1.0) - this method needs good cleanup and documentation
307 */
308 private void fillHeaderCells(CellInfo[][] cells, Header fields, List<Header> headerValues,
309 boolean isRows) {
310 int headerSize = fields.size();
311 String[] lastFieldValue = new String[headerSize];
312 CellInfo[] lastCellInfo = new CellInfo[headerSize];
313 int[] counter = new int[headerSize];
314 boolean newHeader;
315 for (int headerIndex = 0; headerIndex < headerValues.size(); headerIndex ++) {
316 Header header = headerValues.get(headerIndex);
317 newHeader = false;
318 for (int fieldIndex = 0; fieldIndex < headerSize; fieldIndex++) {
319 String fieldValue = header.get(fieldIndex);
320 if (newHeader || !fieldValue.equals(lastFieldValue[fieldIndex])) {
321 newHeader = true;
322 Header currentHeader = getSubHeader(header, fieldIndex + 1);
323 String cellContents = formatHeader(fields.get(fieldIndex), f ieldValue);
324 CellInfo cellInfo;
325 if (isRows) {
326 cellInfo = new CellInfo(currentHeader, null, cellContent s);
327 cells[headerIndex][fieldIndex] = cellInfo;
328 } else {
329 cellInfo = new CellInfo(null, currentHeader, cellContent s);
330 cells[fieldIndex][counter[fieldIndex]] = cellInfo;
331 counter[fieldIndex]++;
332 }
333 lastFieldValue[fieldIndex] = fieldValue;
334 lastCellInfo[fieldIndex] = cellInfo;
335 } else {
336 incrementSpan(lastCellInfo[fieldIndex], isRows);
337 }
338 }
339 }
340 }
341
342 private String formatHeader(String field, String value) {
343 if (value.equals("")) {
344 return BLANK_STRING;
345 }
346 value = Utils.escape(value);
347 if (field.equals("kernel")) {
348 // line break after each /, for long paths
349 value = value.replace("/", "/<br>").replace("/<br>/<br>", "//");
350 }
351 return value;
352 }
353
354 private void incrementSpan(CellInfo cellInfo, boolean isRows) {
355 if (isRows) {
356 cellInfo.rowSpan++;
357 } else {
358 cellInfo.colSpan++;
359 }
360 }
361
362 private Header getSubHeader(Header header, int length) {
363 if (length == header.size()) {
364 return header;
365 }
366 List<String> subHeader = new UnmodifiableSublistView<String>(header, 0, length);
367 return new HeaderImpl(subHeader);
368 }
369
370 private void matchRowHeights(HTMLTable from, CellInfo[][] to) {
371 int lastColumn = to[0].length - 1;
372 int rowCount = from.getRowCount();
373 for (int row = 0; row < rowCount; row++) {
374 int height = getRowHeight(from, row);
375 getCellInfo(to, row, lastColumn).heightPx = height - 2 * CELL_PADDIN G_PX;
376 }
377 }
378
379 private void matchColumnWidths(HTMLTable from, CellInfo[][] to) {
380 int lastToRow = to.length - 1;
381 int lastFromRow = from.getRowCount() - 1;
382 for (int column = 0; column < from.getCellCount(lastFromRow); column++) {
383 int width = getColumnWidth(from, column);
384 getCellInfo(to, lastToRow, column).widthPx = width - 2 * CELL_PADDIN G_PX;
385 }
386 }
387
388 protected String getTableCellText(HTMLTable table, int row, int column) {
389 Element td = table.getCellFormatter().getElement(row, column);
390 Element div = td.getFirstChildElement();
391 if (div == null)
392 return null;
393 String contents = Utils.unescape(div.getInnerHTML());
394 if (contents.equals(BLANK_STRING))
395 contents = "";
396 return contents;
397 }
398
399 public void clear() {
400 rowHeaderValues.clear();
401 columnHeaderValues.clear();
402 rowHeaderMap.clear();
403 columnHeaderMap.clear();
404 dataCells = rowHeaderCells = columnHeaderCells = null;
405 dataTable.reset();
406
407 setRowHeadersOffset(0);
408 setColumnHeadersOffset(0);
409 }
410
411 /**
412 * Make the spreadsheet fill the available window space to the right and bot tom
413 * of its position.
414 */
415 public void fillWindow(boolean useTableSize) {
416 int newHeightPx = Window.getClientHeight() - (columnHeaders.getAbsoluteT op() +
417 columnHeaders.getOffsetHei ght());
418 newHeightPx = adjustMaxDimension(newHeightPx);
419 int newWidthPx = Window.getClientWidth() - (rowHeaders.getAbsoluteLeft() +
420 rowHeaders.getOffsetWidth()) ;
421 newWidthPx = adjustMaxDimension(newWidthPx);
422 if (useTableSize) {
423 newHeightPx = Math.min(newHeightPx, rowHeaders.getOffsetHeight());
424 newWidthPx = Math.min(newWidthPx, columnHeaders.getOffsetWidth());
425 }
426
427 // apply the changes all together
428 rowHeadersClipPanel.setHeight(getSizePxString(newHeightPx));
429 columnHeadersClipPanel.setWidth(getSizePxString(newWidthPx));
430 scrollPanel.setSize(getSizePxString(newWidthPx + SCROLLBAR_FUDGE),
431 getSizePxString(newHeightPx + SCROLLBAR_FUDGE));
432 }
433
434 /**
435 * Adjust a maximum table dimension to allow room for edge decoration and
436 * always maintain a minimum height
437 */
438 protected int adjustMaxDimension(int maxDimensionPx) {
439 return Math.max(maxDimensionPx - WINDOW_BORDER_PX - SCROLLBAR_FUDGE,
440 MIN_TABLE_SIZE_PX);
441 }
442
443 protected String getSizePxString(int sizePx) {
444 return sizePx + "px";
445 }
446
447 /**
448 * Ensure the row header clip panel allows the full width of the row headers
449 * to display.
450 */
451 protected void expandRowHeaders() {
452 int width = rowHeaders.getOffsetWidth();
453 rowHeadersClipPanel.setWidth(getSizePxString(width));
454 }
455
456 private Element getCellElement(HTMLTable table, int row, int column) {
457 return table.getCellFormatter().getElement(row, column);
458 }
459
460 private Element getCellElement(CellInfo cellInfo) {
461 assert cellInfo.row != null || cellInfo.column != null;
462 Element tdElement;
463 if (cellInfo.row == null) {
464 tdElement = getCellElement(columnHeaders, 0, getColumnPosition(cellI nfo.column));
465 } else if (cellInfo.column == null) {
466 tdElement = getCellElement(rowHeaders, getRowPosition(cellInfo.row), 0);
467 } else {
468 tdElement = getCellElement(dataTable, getRowPosition(cellInfo.row),
469 getColumnPosition(cellInfo.col umn));
470 }
471 Element cellElement = tdElement.getFirstChildElement();
472 assert cellElement != null;
473 return cellElement;
474 }
475
476 protected int getColumnWidth(HTMLTable table, int column) {
477 // using the column formatter doesn't seem to work
478 int numRows = table.getRowCount();
479 return table.getCellFormatter().getElement(numRows - 1, column).getOffse tWidth() -
480 TD_BORDER_PX;
481 }
482
483 protected int getRowHeight(HTMLTable table, int row) {
484 // see getColumnWidth()
485 int numCols = table.getCellCount(row);
486 return table.getCellFormatter().getElement(row, numCols - 1).getOffsetHe ight() -
487 TD_BORDER_PX;
488 }
489
490 /**
491 * Update floating headers.
492 */
493 @Override
494 public void onScroll(ScrollEvent event) {
495 int scrollLeft = scrollPanel.getHorizontalScrollPosition();
496 int scrollTop = scrollPanel.getScrollPosition();
497
498 setColumnHeadersOffset(-scrollLeft);
499 setRowHeadersOffset(-scrollTop);
500 }
501
502 protected void setRowHeadersOffset(int offset) {
503 rowHeaders.getElement().getStyle().setPropertyPx("top", offset);
504 }
505
506 protected void setColumnHeadersOffset(int offset) {
507 columnHeaders.getElement().getStyle().setPropertyPx("left", offset);
508 }
509
510 @Override
511 public void onClick(ClickEvent event) {
512 handleEvent(event, false);
513 }
514
515 @Override
516 public void onContextMenu(ContextMenuEvent event) {
517 handleEvent(event, true);
518 }
519
520 private void handleEvent(DomEvent<?> event, boolean isRightClick) {
521 if (listener == null)
522 return;
523
524 assert event.getSource() instanceof RightClickTable;
525 HTMLTable.Cell tableCell = ((RightClickTable) event.getSource()).getCell ForDomEvent(event);
526 int row = tableCell.getRowIndex();
527 int column = tableCell.getCellIndex();
528
529 CellInfo[][] cells;
530 if (event.getSource() == rowHeaders) {
531 cells = rowHeaderCells;
532 column = adjustRowHeaderColumnIndex(row, column);
533 }
534 else if (event.getSource() == columnHeaders) {
535 cells = columnHeaderCells;
536 }
537 else {
538 assert event.getSource() == dataTable;
539 cells = dataCells;
540 }
541 CellInfo cell = cells[row][column];
542 if (cell == null || cell.isEmpty())
543 return; // don't report clicks on empty cells
544
545 listener.onCellClicked(cell, isRightClick);
546 }
547
548 /**
549 * In HTMLTables, a cell with rowspan > 1 won't count in column indices for the extra rows it
550 * spans, which will mess up column indices for other cells in those rows. This method adjusts
551 * the column index passed to onCellClicked() to account for that.
552 */
553 private int adjustRowHeaderColumnIndex(int row, int column) {
554 for (int i = 0; i < rowFields.size(); i++) {
555 if (rowHeaderCells[row][i] != null) {
556 return i + column;
557 }
558 }
559
560 throw new RuntimeException("Failed to find non-null cell");
561 }
562
563 public void setListener(SpreadsheetListener listener) {
564 this.listener = listener;
565 }
566
567 public void setHighlighted(CellInfo cell, boolean highlighted) {
568 Element cellElement = getCellElement(cell);
569 if (highlighted) {
570 cellElement.setClassName(HIGHLIGHTED_CLASS);
571 } else {
572 cellElement.setClassName("");
573 }
574 }
575
576 public List<Integer> getAllTestIndices() {
577 List<Integer> testIndices = new ArrayList<Integer>();
578
579 for (CellInfo[] row : dataCells) {
580 for (CellInfo cellInfo : row) {
581 if (cellInfo != null && !cellInfo.isEmpty()) {
582 testIndices.add(cellInfo.testIndex);
583 }
584 }
585 }
586
587 return testIndices;
588 }
589 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698