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

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

Powered by Google App Engine
This is Rietveld 408576698