Java tutorial
/* * Copyright 2010 JBoss, a divison Red Hat, Inc * * 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 org.jboss.errai.widgets.client; import com.google.gwt.dom.client.Element; import com.google.gwt.event.dom.client.*; import com.google.gwt.user.client.*; import com.google.gwt.user.client.Timer; import com.google.gwt.user.client.ui.*; import org.jboss.errai.widgets.client.format.WSCellFormatter; import org.jboss.errai.widgets.client.format.WSCellSimpleTextCell; import org.jboss.errai.widgets.client.format.WSCellTitle; import org.jboss.errai.widgets.client.listeners.CellChangeEvent; import java.util.*; import static com.google.gwt.user.client.DOM.setStyleAttribute; import static com.google.gwt.user.client.ui.RootPanel.getBodyElement; import static java.lang.Double.parseDouble; /** * A Grid/Table implementation for working with structured data. */ @SuppressWarnings({ "UnusedDeclaration", "ConstantConditions" }) public class WSGrid extends Composite implements RequiresResize { private static final int CELL_HEIGHT_PX = 18; private FocusPanel focusPanel; private VerticalPanel innerPanel; private WSAbstractGrid titleBar; private WSAbstractGrid dataGrid; private int cols; private Stack<WSCell> selectionList = new Stack<WSCell>(); private ArrayList<Integer> colSizes = new ArrayList<Integer>(); private Map<Integer, Boolean> sortedColumns = new HashMap<Integer, Boolean>(); private WSCell sortedColumnHeader; private FocusManager fm = new DefaultFocusManager(this); private int fillX; private int fillY; private boolean forwardDirY = true; private boolean forwardDirX = true; private boolean currentFocusColumn; private boolean _leftGrow = false; private boolean _resizeArmed = false; private boolean _resizing = false; private boolean _rangeSelect = false; private boolean _mergedCells = false; private boolean resizeOnAttach = false; private boolean rowSelectionOnly = false; private WSGrid wsGrid = this; private PopupPanel resizeLine = new PopupPanel() { @Override public void onBrowserEvent(Event event) { wsGrid.onBrowserEvent(event); } }; private List<ChangeHandler> cellChangeHandlers = new LinkedList<ChangeHandler>(); private List<ChangeHandler> afterCellChangeHandlers = new LinkedList<ChangeHandler>(); public WSGrid() { this(true, true); this.fm = new DefaultFocusManager(this); } public WSGrid(FocusManager fm) { this(true, true); this.fm = fm; } private int _startpos = 0; public WSGrid(boolean scrollable, boolean editable) { innerPanel = new VerticalPanel(); innerPanel.setSpacing(0); focusPanel = new FocusPanel(innerPanel); initWidget(focusPanel); titleBar = new WSAbstractGrid(false, GridType.TITLEBAR); innerPanel.add(titleBar); innerPanel.setCellVerticalAlignment(titleBar, HasVerticalAlignment.ALIGN_BOTTOM); titleBar.setStylePrimaryName("WSGrid-header"); if (editable) dataGrid = new WSAbstractGrid(scrollable, GridType.EDITABLE_GRID); else dataGrid = new WSAbstractGrid(scrollable, GridType.NONEDITABLE_GRID); innerPanel.add(dataGrid); innerPanel.setCellVerticalAlignment(dataGrid, HasVerticalAlignment.ALIGN_TOP); dataGrid.setStylePrimaryName("WSGrid-datagrid"); resizeLine.setWidth("5px"); resizeLine.setHeight("800px"); resizeLine.setStyleName("WSGrid-resize-line"); resizeLine.sinkEvents(Event.MOUSEEVENTS); dataGrid.getScrollPanel().addScrollHandler(new ScrollHandler() { public void onScroll(ScrollEvent event) { titleBar.getScrollPanel() .setHorizontalScrollPosition(dataGrid.getScrollPanel().getHorizontalScrollPosition()); } }); /** * This is the handler that is responsible for resizing columns. */ focusPanel.addMouseDownHandler(new MouseDownHandler() { public void onMouseDown(MouseDownEvent event) { if (!selectionList.isEmpty() && selectionList.lastElement().isEdit()) { return; } if (_resizeArmed) { if (!_resizing) { _resizing = true; disableTextSelection(getBodyElement(), true); _startpos = event.getClientX(); resizeLine.show(); resizeLine.setPopupPosition(event.getClientX(), 0); } } } }); /** * This handler traces the mouse movement, drawing the vertical resizing * indicator. */ focusPanel.addMouseMoveHandler(new MouseMoveHandler() { public void onMouseMove(MouseMoveEvent event) { if (_resizing) { setStyleAttribute(resizeLine.getElement(), "left", (event.getClientX()) + "px"); } } }); /** * This is the mouse release handler that resizes a column once the left * mouse button is released in a resizing operation. */ focusPanel.addMouseUpHandler(new MouseUpHandler() { public void onMouseUp(MouseUpEvent event) { if (_resizing) { cancelResize(); WSCell currentFocus = selectionList.isEmpty() ? null : selectionList.lastElement(); if (currentFocus == null) return; int selCol = (_leftGrow ? currentFocus.col - 1 : currentFocus.col); if (selCol == -1) return; int width = colSizes.get(selCol); int offset = event.getClientX(); /** * If we didn't move at all, don't calculate a new size. */ if (offset - _startpos == 0) return; width -= (_startpos -= event.getClientX()); setColumnWidth(selCol, width); } _rangeSelect = false; } }); /** * This handler is responsible for all the keyboard input handlers for * the grid. */ focusPanel.addKeyDownHandler(new KeyDownHandler() { public void onKeyDown(KeyDownEvent event) { final WSCell currentFocus; int offsetX; int offsetY; /** * Is there currently anything selected? */ if (selectionList.isEmpty()) { /** * No. */ return; } else { /** * Set currentFocus to the last element in the * selectionList. */ currentFocus = selectionList.lastElement(); /** * Yes. Let's check to see if this cell spans. If so, we * will set the offsetX value to the colspan value so * navigation behaves. */ offsetX = currentFocus.isColSpan() ? currentFocus.getColspan() : 1; offsetY = currentFocus.isRowSpan() ? currentFocus.getRowspan() : 1; } /** * If we're currently editing a cell, then we ignore these * operations. */ if (currentFocus.edit) { return; } event.preventDefault(); switch (event.getNativeKeyCode()) { /** * If tab is pressed, we want to advance to the next cell. If we * are at the end of the line, go to the first cell of the next * line. If shift is depressed, then perform the inverse. */ case KeyCodes.KEY_TAB: blurAll(); if (event.getNativeEvent().getShiftKey()) { if (currentFocus.getCol() == 0 && currentFocus.getRow() > 0) { dataGrid.tableIndex.get(currentFocus.getRow() - offsetX).get(cols - offsetX).focus(); } else { dataGrid.tableIndex.get(currentFocus.getRow()) .get(currentFocus.getCol() - (currentFocus.isColSpan() ? currentFocus.getLeftwareColspan() : 1)) .focus(); } } else { if (currentFocus.getCol() == cols - offsetX && currentFocus.getRow() < dataGrid.tableIndex.size()) { dataGrid.tableIndex.get(currentFocus.getRow() + offsetX).get(0).focus(); } else { dataGrid.tableIndex.get(currentFocus.getRow()).get(currentFocus.getCol() + offsetX) .focus(); } } break; /** * If the up key is pressed, we should advance to the cell * directly above the currently selected cell. If we are at the * top of the grid, then nothing should happen. */ case 63232: case KeyCodes.KEY_UP: if (currentFocus.getRow() > 0) { if (!event.getNativeEvent().getShiftKey()) { blurAll(); } if (fm.isInitialised()) { fm.moveUpwards(); } else { dataGrid.tableIndex .get(currentFocus.getRow() - (currentFocus.isRowSpan() ? currentFocus.getUpwardRowspan() : 1)) .get(currentFocus.getCol()).focus(); } } break; /** * If the right key is pressed, we should advance to the cell * directly to the right of the currently selected cell. If we * are at the rightmost part of the grid, then nothing should * happen. */ case 63235: case KeyCodes.KEY_RIGHT: if (currentFocus.getCol() < cols - offsetX) { /** * Blur all columns if shift is not being depressed. */ if (!event.getNativeEvent().getShiftKey()) { blurAll(); } if (currentFocusColumn) { titleBar.tableIndex.get(currentFocus.getRow()).get(currentFocus.getCol() + offsetX) .focus(); } else { if (fm.isInitialised()) { fm.moveRight(); } else { dataGrid.tableIndex.get(currentFocus.getRow()).get(currentFocus.getCol() + offsetX) .focus(); } } } break; /** * If either "Enter" or the down key is pressed we should * advance to the cell directly below the current cell. If we * are at the bottom of the grid, depending on the mode of the * grid, either nothing should happen, or a new line should be * added. */ case 63233: case KeyCodes.KEY_ENTER: case KeyCodes.KEY_DOWN: if (currentFocus.getRow() < dataGrid.tableIndex.size() - offsetX) { if (!event.getNativeEvent().getShiftKey()) { blurAll(); } if (fm.isInitialised()) { fm.moveDownwards(); } else { dataGrid.tableIndex.get(currentFocus.getRow() + offsetY).get(currentFocus.getCol()) .focus(); } } break; /** * If the left key is pressed we should advance to the cell * directly to the left of the currently selected cell. If we * are at the leftmost part of the grid, nothing should happen. */ case 63234: case KeyCodes.KEY_LEFT: if (currentFocus.getCol() > 0) { if (!event.getNativeEvent().getShiftKey()) { blurAll(); } if (currentFocusColumn) { titleBar.tableIndex.get(currentFocus.getRow()).get(currentFocus.getCol() - offsetX) .focus(); } else { if (fm.isInitialised()) { fm.moveLeft(); } else { int delta = currentFocus.getCol() - (currentFocus.isColSpan() ? currentFocus.getLeftwareColspan() : 1); if (delta < 0) { currentFocus.focus(); } else { dataGrid.tableIndex.get(currentFocus.getRow()).get(delta).focus(); } } } } break; case KeyCodes.KEY_CTRL: case 16: case 91: break; case KeyCodes.KEY_BACKSPACE: case 63272: case KeyCodes.KEY_DELETE: if (currentFocus.grid.type != GridType.TITLEBAR) { for (WSCell c : selectionList) { c.setValue(""); } } else { /** * Wipe the whole column. */ for (int i = 0; i < dataGrid.tableIndex.size(); i++) { dataGrid.tableIndex.get(i).get(currentFocus.getCol()).setValue(""); } } break; case 32: // spacebar Timer t = new Timer() { public void run() { currentFocus.edit(); } }; t.schedule(15); break; default: currentFocus.setValue(""); currentFocus.edit(); } } }); focusPanel.addMouseOutHandler(new MouseOutHandler() { public void onMouseOut(MouseOutEvent mouseOutEvent) { _rangeSelect = false; } }); } public void setScrollable(boolean scrollable) { dataGrid.setScrollable(scrollable); } public void setEditable(boolean editable) { if (editable) dataGrid.setType(GridType.EDITABLE_GRID); else dataGrid.setType(GridType.NONEDITABLE_GRID); } public int getRowCount() { return dataGrid.getRowCount(); } public void removeRow(int row) { dataGrid.removeRow(row); } public void setColumnHeader(int row, int column, String html) { cols = titleBar.ensureRowsAndCols(row + 1, column + 1); WSCell wsc = titleBar.getTableIndex().get(row).get(column); wsc.setValue(new WSCellTitle(wsc, html)); } public void setCell(int row, int column, String html) { int col = dataGrid.ensureRowsAndCols(row + 1, column + 1); if (col > cols) cols = col; dataGrid.getTableIndex().get(row).get(column).setValue(html); } public void setCell(int row, int column, WSCellFormatter formatter) { int col = dataGrid.ensureRowsAndCols(row + 1, column + 1); if (col > cols) cols = col; dataGrid.getTableIndex().get(row).get(column).setValue(formatter); } /** * Return the total number of columns in the grid. * * @return - */ public int getCols() { return cols; } /** * Blur all currently selected columns. */ public void blurAll() { Stack<WSCell> stk = new Stack<WSCell>(); stk.addAll(selectionList); for (WSCell cell : stk) { cell.blur(); } selectionList.clear(); fillX = 0; fillY = 0; forwardDirX = forwardDirY = true; fm.reset(); } public void setRowHeight(int row, int height) { dataGrid.setRowHeight(row, height); } /** * Sets a column width (in pixels). Columns start from 0. * * @param column - the column * @param width - the width in pixels */ public void setColumnWidth(int column, int width) { colSizes.set(column, width); if (column >= cols) return; if (isAttached()) { titleBar.getTable().getColumnFormatter().setWidth(column, width + "px"); dataGrid.getTable().getColumnFormatter().setWidth(column, width + "px"); titleBar.tableIndex.get(0).get(column).setWidth(width + "px"); WSCell c; for (int cX = 0; cX < dataGrid.tableIndex.size(); cX++) { if (dataGrid.tableIndex.size() == 0) { // Classic problem in the hosted mode break; } if (dataGrid.tableIndex.get(cX).size() == 0) { // Classic problem in the hosted mode break; } c = dataGrid.tableIndex.get(cX).get(column); /** * Check to see if this cell is merged with other cells. If so, * we need to accumulate the proper column width. */ if (c.isColSpan()) { int spanSize = width + 1; for (int span = c.getColspan() - 1; span > 0; span--) { spanSize += colSizes.get(column + span) + 2; } c.setWidth((spanSize + 1) + "px"); } else { c.setWidth(width + "px"); } } } else { resizeOnAttach = true; } } private void updateColumnSizes() { int col = 0; for (Integer size : colSizes) { setColumnWidth(col++, size); } } /** * Returns an instance of the WSCell based on the row and col specified. * * @param row - the row * @param col - the column * @return Instance of WSCell. */ public WSCell getCell(int row, int col) { return dataGrid.getCell(row, col); } /** * Highlights a vertical column. * * @param col - the column */ private void selectColumn(int col) { for (ArrayList<WSCell> row : titleBar.getTableIndex()) { row.get(col).addStyleDependentName("hcolselect"); } for (ArrayList<WSCell> row : dataGrid.getTableIndex()) { row.get(col).addStyleDependentName("colselect"); } } /** * Blurs a vertical column. * * @param col - the column */ private void blurColumn(int col) { sortedColumns.put(col, false); for (ArrayList<WSCell> row : titleBar.getTableIndex()) { row.get(col).removeStyleDependentName("hcolselect"); } for (ArrayList<WSCell> row : dataGrid.getTableIndex()) { row.get(col).removeStyleDependentName("colselect"); } } public void clear() { cols = 0; this.titleBar.clear(); this.dataGrid.clear(); this.selectionList.clear(); this.colSizes.clear(); this.sortedColumns.clear(); this._mergedCells = false; } /** * This is the actual grid implementation. */ public class WSAbstractGrid extends Composite { private ScrollPanel scrollPanel; private FlexTable table; private ArrayList<ArrayList<WSCell>> tableIndex; private GridType type; public WSAbstractGrid() { this(false, GridType.EDITABLE_GRID); } public WSAbstractGrid(GridType type) { this(false, type); } public WSAbstractGrid(boolean scrollable, GridType type) { this.type = type; table = new FlexTable(); table.setStylePrimaryName("WSGrid"); table.insertRow(0); scrollPanel = new ScrollPanel(); initWidget(scrollPanel); scrollPanel.setAlwaysShowScrollBars(scrollable); setScrollable(scrollable); scrollPanel.add(table); tableIndex = new ArrayList<ArrayList<WSCell>>(); tableIndex.add(new ArrayList<WSCell>()); setHeight("20px"); } public void setType(GridType type) { this.type = type; } public void setScrollable(boolean scrollable) { if (!scrollable) { setStyleAttribute(scrollPanel.getElement(), "overflow", "hidden"); scrollPanel.setHeight("20px"); table.setHeight("20px"); } else { setStyleAttribute(scrollPanel.getElement(), "overflow", "scroll"); } } public void clear() { scrollPanel.remove(table); table = new FlexTable(); table.setStylePrimaryName("WSGrid"); table.insertRow(0); scrollPanel.add(table); tableIndex.clear(); tableIndex.add(new ArrayList<WSCell>()); } public void addCell(int row, String w) { int currentColSize = table.getCellCount(row); table.addCell(row); table.setWidget(row, currentColSize, new WSCell(this, new WSCellSimpleTextCell(w), row, currentColSize)); } public int getRowCount() { return table.getRowCount(); } public void addRow() { table.insertRow(table.getRowCount()); for (int i = 0; i < cols; i++) { addCell(table.getRowCount() - 1, ""); } } public void removeRow(int row) { table.removeRow(row); tableIndex.remove(row); // Need to update the rows for all the cells following the deleted // row. int size = tableIndex.size(); for (int i = row; i < size; i++) { ArrayList<WSCell> currRow = tableIndex.get(i); int numCols = currRow.size(); for (int j = 0; j < numCols; j++) currRow.get(j).row--; } } public int ensureRowsAndCols(int rows, int cols) { if (colSizes.size() < cols) { for (int i = 0; i < cols; i++) { colSizes.add(125); } } if (table.getRowCount() == 0) { addRow(); } while (table.getRowCount() < rows) { addRow(); } for (int r = 0; r < table.getRowCount(); r++) { if (table.getCellCount(r) < cols) { int growthDelta = cols - table.getCellCount(r); for (int c = 0; c < growthDelta; c++) { table.getColumnFormatter().setWidth(c, colSizes.get(c) + "px"); addCell(r, ""); } assert table.getCellCount(r) == cols : "New size is wrong: " + table.getCellCount(r); } } return cols == 0 ? 1 : cols; } public FlexTable getTable() { return table; } public void setHeight(String height) { if (scrollPanel != null) scrollPanel.setHeight(height); } public void setWidth(String width) { if (scrollPanel != null) scrollPanel.setWidth(width); } public int getOffsetHeight() { if (scrollPanel != null) return scrollPanel.getOffsetHeight(); else return table.getOffsetHeight(); } public int getOffsetWidth() { if (scrollPanel != null) return scrollPanel.getOffsetWidth(); else return table.getOffsetWidth(); } @Override protected void onAttach() { super.onAttach(); } public ArrayList<ArrayList<WSCell>> getTableIndex() { return tableIndex; } public void setRowHeight(int row, int height) { if (row > tableIndex.size()) return; ArrayList<WSCell> rowData = tableIndex.get(row); String ht = height + "px"; for (int i = 0; i < cols; i++) { rowData.get(i).setHeight(ht); } } public WSCell getCell(int row, int col) { return tableIndex.get(row).get(col); } public void sort(int col, boolean ascending) { boolean secondPass = isEmpty(valueAt(0, col)); _sort(col, ascending, 0, tableIndex.size() - 1); if (secondPass) { // resort _sort(col, ascending, 0, tableIndex.size() - 1); } } private void _sort(int col, boolean ascending, int low, int high) { if (low < high) { int p = _sort_partition(col, ascending, low, high); _sort(col, ascending, low, p); _sort(col, ascending, p + 1, high); } } private int _sort_partition(int col, boolean ascending, int low, int high) { WSCell pvtStr = cellAt(low, col); int i = low - 1; int j = high + 1; if (ascending) { while (i < j) { i++; while (_sort_lt(cellAt(i, col), pvtStr)) { i++; } j--; while (_sort_gt(cellAt(j, col), pvtStr)) { j--; } if (i < j) _sort_swap(i, j); } } else { while (i < j) { i++; while (_sort_gt(cellAt(i, col), pvtStr)) { i++; } j--; while (_sort_lt(cellAt(j, col), pvtStr)) { j--; } if (i < j) _sort_swap(i, j); } } return j; } private boolean _sort_gt(WSCell l, WSCell r) { if (l == null) return false; if (l.numeric && r.numeric) { return parseDouble(l.getValue()) > parseDouble(r.getValue()); } else { String ll = l.getValue(); String rr = r.getValue(); // Internet Explorer is very annoying if (_msie_compatibility) { ll = ll.equals(" ") ? "" : ll; rr = rr.equals(" ") ? "" : rr; } for (int i = 0; i < ll.length() && i < rr.length(); i++) { if (ll.charAt(i) > rr.charAt(i)) { return true; } else if (ll.charAt(i) < rr.charAt(i)) { return false; } } return isEmpty(ll) && !isEmpty(rr); } } private boolean _sort_lt(WSCell l, WSCell r) { if (l == null) return false; if (l.numeric && r.numeric) { return parseDouble(l.getValue()) < parseDouble(r.getValue()); } else { String ll = l.getValue(); String rr = r.getValue(); // Internet Explorer is very annoying if (_msie_compatibility) { ll = ll.equals(" ") ? "" : ll; rr = rr.equals(" ") ? "" : rr; } for (int i = 0; i < ll.length() && i < rr.length(); i++) { if (ll.charAt(i) < rr.charAt(i)) { return true; } else if (ll.charAt(i) > rr.charAt(i)) { return false; } } return isEmpty(ll) && !isEmpty(rr); } } private void _sort_swap(int i, int j) { WSCellFormatter t; WSCell c1; WSCell c2; for (int z = 0; z < cols; z++) { c1 = cellAt(i, z); c2 = cellAt(j, z); t = c1.getCellFormat(); c1.setValue(c2.getCellFormat()); c2.setValue(t); c1.setOriginalRow(j); c2.setOriginalRow(i); } } public WSCell cellAt(int row, int col) { return tableIndex.size() > row ? tableIndex.get(row).get(col) : null; } public WSCellFormatter cellFmtAt(int row, int col) { return cellAt(row, col).cellFormat; } public String valueAt(int row, int col) { return tableIndex.get(row).get(col).cellFormat.getTextValue(); } public void setValueAt(int row, int col, String html) { cellAt(row, col).setValue(html); } public void setValueAt(int row, int col, WSCellFormatter cellFmt) { cellAt(row, col).setValue(cellFmt); } public ScrollPanel getScrollPanel() { return scrollPanel; } } // end WSAbstractGrid private boolean _msie_compatibility = getUserAgent().contains("msie"); public class WSCell extends Composite { protected SimplePanel panel; protected WSCellFormatter cellFormat; protected boolean numeric; protected boolean edit; protected int originalRow; protected int row; protected int col; protected int rowspan = 1; protected int colspan = 1; protected WSAbstractGrid grid; public WSCell(WSAbstractGrid grid, WSCellFormatter cellFormat, int row, int column) { this.grid = grid; panel = new SimplePanel(); panel.setStyleName("WSCell-panel"); if (grid.tableIndex.size() - 1 < row) { while (grid.tableIndex.size() - 1 < row) { grid.tableIndex.add(new ArrayList<WSCell>()); } } ArrayList<WSCell> cols = grid.tableIndex.get(row); if (cols.size() == 0 || cols.size() - 1 < column) { cols.add(this); } else { cols.set(column, this); } this.cellFormat = cellFormat; panel.add(cellFormat.getWidget(wsGrid)); this.row = this.originalRow = row; this.col = column; initWidget(panel); setWidth(colSizes.get(column) + "px"); setStyleName("WSCell"); sinkEvents(Event.MOUSEEVENTS | Event.FOCUSEVENTS | Event.ONCLICK | Event.ONDBLCLICK); if (_msie_compatibility) { if (cellFormat.getTextValue() == null || cellFormat.getTextValue().equals("")) { cellFormat.setValue(" "); } } disableTextSelectInternal(panel.getElement(), true); } public WSCell() { } /** * Calling this method will place the cell into edit mode. */ public void edit() { String text = cellFormat.getTextValue(); if (_msie_compatibility && text.equals(" ")) cellFormat.setValue(""); edit = cellFormat.edit(this); } /** * Ends a current edit operation. */ public void stopedit() { if (edit) { edit = false; if (!_msie_compatibility) focusPanel.setFocus(true); } } public void cancelEdit() { cellFormat.cancelEdit(); } /** * Blurs the current cell. */ public void blur() { if (edit) cellFormat.stopedit(); removeStyleDependentName("selected"); if (rowSelectionOnly) { for (int i = 0; i < cols; i++) { WSCell c = grid.getCell(row, i); if (i == 0) { c.removeStyleDependentName("rowselect-left"); } else if (i + 1 == cols) { c.removeStyleDependentName("rowselect-right"); } else { c.removeStyleDependentName("rowselect"); } } } if (currentFocusColumn) { blurColumn(col); currentFocusColumn = false; } else { selectionList.remove(this); } } public void notifyCellUpdate(Object newValue) { fireAllCellChangeHandlers(this, newValue); } public void notifyCellAfterUpdate() { fireAllAfterCellChangeHandlers(this); } /** * Focuses the current cell. */ public void focus() { WSCell currentFocus = selectionList.isEmpty() ? null : selectionList.lastElement(); boolean isFocus = currentFocus == this; if (selectionList.isEmpty()) { fm.setStartCell(this); } if (!selectionList.contains(this)) selectionList.add(this); if (grid.type == GridType.TITLEBAR) { currentFocusColumn = true; if (!isFocus) { selectColumn(col); } } else { int scrollPos = grid.getScrollPanel().getScrollPosition(); int scrollPosH = grid.getScrollPanel().getHorizontalScrollPosition(); int cellHeight = getOffsetHeight(); int cellWidth = getOffsetWidth(); int bottomCell = DOM.getAbsoluteTop(getElement()) + cellHeight - DOM.getAbsoluteTop(grid.getScrollPanel().getElement()) + scrollPos; int rightCell = DOM.getAbsoluteLeft(getElement()) + cellWidth - DOM.getAbsoluteLeft(grid.getScrollPanel().getElement()) + scrollPosH; int bottomVisible = grid.getScrollPanel().getOffsetHeight() + scrollPos - 19; int topVisible = bottomVisible - grid.getScrollPanel().getOffsetHeight() + 2; int rightVisible = grid.getScrollPanel().getOffsetWidth() + scrollPosH - 19; int leftVisible = rightVisible - grid.getScrollPanel().getOffsetWidth() + 2; if (bottomCell >= bottomVisible) { final int startPos = scrollPos; scrollPos += (bottomCell - bottomVisible) + 18; final int endPos = scrollPos; final int multiplier = ((endPos - startPos) / 100); final int threshold = endPos - 50 - (multiplier * 10); final double decelRate = ((double) (endPos - startPos)) / (250 + (multiplier * 100)); Timer smoothScroll = new Timer() { int i = startPos; double vel = 5.0 + multiplier; int absoluteVel = (int) Math.round(vel); double decel = decelRate; @Override public void run() { if ((i += absoluteVel) >= endPos) { i = endPos; cancel(); } if (i > threshold) { if (vel > 1) { vel -= decelRate; absoluteVel = (int) Math.round(vel); if (absoluteVel < 1) absoluteVel = 1; } } grid.scrollPanel.setScrollPosition(i); } }; smoothScroll.scheduleRepeating(1); } else if (bottomCell - cellHeight <= (topVisible)) { final int startPos = scrollPos; scrollPos -= getOffsetHeight(); final int endPos = scrollPos; final int multiplier = ((endPos - startPos) / 100); final double decelRate = ((double) (startPos - endPos)) / (250 + (multiplier * 100)); final int threshold = endPos + 50 + (multiplier * 10); Timer smoothScroll = new Timer() { int i = startPos; double vel = 5.0 + multiplier; int absoluteVel = (int) Math.round(vel); double decel = decelRate; @Override public void run() { if ((i -= absoluteVel) <= endPos) { i = endPos; cancel(); } if (i < threshold) { if (vel > 1) { vel -= decelRate; absoluteVel = (int) Math.round(vel); if (absoluteVel < 1) absoluteVel = 1; } } grid.scrollPanel.setScrollPosition(i); } }; smoothScroll.scheduleRepeating(1); } else if (rightCell >= (rightVisible)) { if (scrollPosH % cellWidth != 0) { scrollPosH += (scrollPosH % cellWidth); } grid.getScrollPanel().setHorizontalScrollPosition(scrollPosH + getOffsetWidth()); } else if (rightCell - cellWidth <= (leftVisible)) { if (scrollPosH % cellWidth != 0) { scrollPosH -= (scrollPosH % cellWidth); } grid.getScrollPanel().setHorizontalScrollPosition(scrollPosH - getOffsetWidth()); } } if (grid.type != GridType.TITLEBAR && rowSelectionOnly) { for (int i = 0; i < cols; i++) { WSCell c = grid.getCell(row, i); if (!selectionList.contains(c)) { if (i == 0) { c.addStyleDependentName("rowselect-left"); } else if (i + 1 == cols) { c.addStyleDependentName("rowselect-right"); } else { c.addStyleDependentName("rowselect"); } } } if (col == 0) { addStyleDependentName("rowselect-left"); } else if (col + 1 == cols) { addStyleDependentName("rowselect-right"); } else { addStyleDependentName("rowselect"); } } else { addStyleDependentName("selected"); } } /** * Focuses a range between this cell and the currently selected cell. */ public void focusRange() { WSCell cell = fm.getStartCell(); int startSelX = cell.col; int startSelY = cell.row; fillX = col - startSelX; fillY = row - startSelY; int startX = startSelX; int startY = startSelY; if (fillX < 0) { startX = startSelX + fillX; fillX *= -1; forwardDirX = false; } if (fillY < 0) { startY = startSelY + fillY; fillY *= -1; forwardDirY = false; } int endX = startX + fillX; int endY = startY + fillY; int x = startX; while (x < endX) { if (forwardDirX) { x = x + fm.moveRight(); } else { x = x + fm.moveLeft(); } } ; int y = startY; while (y < endY) { if (forwardDirY) { y = y + fm.moveDownwards(); } else { y = y + fm.moveUpwards(); } } ; } public int getOriginalRow() { return originalRow; } public void setOriginalRow(int originalRow) { this.originalRow = originalRow; } public int getRow() { return row; } public int getCol() { return col; } public boolean isEdit() { return edit; } public void setValue(String html) { html = html.trim(); if (_msie_compatibility && html.length() == 0) { cellFormat.setValue(" "); } else { cellFormat.setValue(html); panel.clear(); panel.add(cellFormat.getWidget(wsGrid)); } numeric = isNumeric(html); } public void setValue(WSCellFormatter formatter) { if (_msie_compatibility && (formatter.getTextValue() == null || formatter.getTextValue().length() == 0)) { formatter.setValue(" "); } this.cellFormat = formatter; panel.clear(); Widget w = formatter.getWidget(wsGrid); panel.add(w); numeric = isNumeric(formatter.getTextValue()); } public String getValue() { return cellFormat.getTextValue(); } public WSCellFormatter getCellFormat() { return cellFormat; } public void mergeColumns(int cols) { if (cols < 2) return; _mergedCells = true; final int _row = row; for (int i = 1; i < cols; i++) { grid.table.removeCell(row, col + 1); final WSCell cell = this; final int _col = col + i; grid.tableIndex.get(row).set(col + i, new WSGrid.WSCell() { { this.grid = cell.grid; this.col = _col; this.row = _row; initWidget(new HTML()); } @Override public void blur() { cell.blur(); } @Override public void focus() { cell.focus(); } @Override public int getColspan() { return cell.colspan - (col - cell.col); } @Override public int getRowspan() { return cell.rowspan - (row - cell.row); } @Override public int getUpwardRowspan() { return row - cell.row + 1; } @Override public int getLeftwareColspan() { return col - cell.col + 1; } @Override public boolean isColSpan() { return cell.isColSpan(); } @Override public boolean isRowSpan() { return cell.isRowSpan(); } }); } grid.table.getFlexCellFormatter().setColSpan(row, col, cols); colspan = cols; updateColumnSizes(); } public void mergeRows(int rows) { if (rows < 2) return; _mergedCells = true; FlexTable table = grid.table; for (int i = 1; i < rows; i++) { for (int c = col; c < (col + colspan); c++) { final WSCell cell = this; final int _row = row + i; table.removeCell(_row, col); final int _col = c; grid.tableIndex.get(_row).set(c, new WSCell() { { this.grid = cell.grid; this.col = _col; this.row = _row; initWidget(new HTML()); } @Override public void blur() { cell.blur(); } @Override public void focus() { cell.focus(); } @Override public int getColspan() { return cell.colspan - (col - cell.col); } @Override public int getRowspan() { return cell.rowspan - (row - cell.row); } @Override public int getUpwardRowspan() { return row - cell.row + 1; } @Override public int getLeftwareColspan() { return col - cell.col + 1; } @Override public boolean isColSpan() { return cell.isColSpan(); } @Override public boolean isRowSpan() { return cell.isRowSpan(); } }); grid.table.getFlexCellFormatter().setRowSpan(row, col, rows); rowspan = rows; setHeight(((rows * CELL_HEIGHT_PX)) + "px"); updateColumnSizes(); } } } public boolean isColSpan() { return colspan > 1; } public int getColspan() { return colspan; } public boolean isRowSpan() { return rowspan > 1; } public int getRowspan() { return rowspan; } public int getLeftwareColspan() { return 1; } public int getUpwardRowspan() { return 1; } public void setHeight(String height) { super.setHeight(height); panel.setHeight(height); cellFormat.setHeight(height); grid.table.getCellFormatter().setHeight(row, col, height); } @Override public void onBrowserEvent(Event event) { int leftG = getAbsoluteLeft() + 10; int rightG = getAbsoluteLeft() + colSizes.get(col) - 10; switch (event.getTypeInt()) { case Event.ONMOUSEOVER: if (!_resizing) { // addStyleDependentName("hover"); if (_rangeSelect) { focusRange(); } } break; case Event.ONMOUSEOUT: // if (!_resizing) removeStyleDependentName("hover"); if (grid.type == GridType.TITLEBAR) _resizeArmed = false; break; case Event.ONMOUSEMOVE: if (!_resizing && grid.type == GridType.TITLEBAR) { if (event.getClientX() < leftG) { addStyleDependentName("resize-left"); _resizeArmed = true; _leftGrow = true; } else if (event.getClientX() > rightG) { addStyleDependentName("resize-right"); _resizeArmed = true; _leftGrow = false; } else { removeStyleDependentName("resize-left"); removeStyleDependentName("resize-right"); _resizeArmed = false; } } break; case Event.ONMOUSEDOWN: if (edit) { return; } _rangeSelect = true; if (event.getShiftKey()) { focusRange(); break; } else if (!event.getMetaKey() && !event.getCtrlKey() && !selectionList.isEmpty() && selectionList.lastElement() != this) { blurAll(); } focus(); break; case Event.ONMOUSEUP: _rangeSelect = false; break; case Event.ONFOCUS: break; case Event.ONCLICK: if (grid.type == GridType.TITLEBAR) { if (_mergedCells) { WSModalDialog dialog = new WSModalDialog("Unable to sort"); dialog.getCancelButton().setVisible(false); dialog.ask("The table cannot be sorted because it contains merged cells", null); dialog.showModal(); return; } boolean asc = getColumnSortOrder(col); if (sortedColumnHeader != null) { WSCell old = sortedColumnHeader; sortedColumnHeader = this; old.cellFormat.getWidget(wsGrid); } else { sortedColumnHeader = this; } cellFormat.getWidget(wsGrid); sortedColumns.put(col, !asc); dataGrid.sort(col, asc); } break; case Event.ONDBLCLICK: switch (grid.type) { case EDITABLE_GRID: edit(); break; case TITLEBAR: break; } break; } } } /** * Ends resizing operation. */ private void cancelResize() { resizeLine.hide(); _resizing = _resizeArmed = false; } /** * Returns the offsite high of the title row * * @return The offset height in pixels */ public int getTitlebarOffsetHeight() { return titleBar.getOffsetHeight(); } /** * Sets the height of the grid. * * @param height CSS height string. */ public void setHeight(String height) { focusPanel.setHeight(height); } /** * Sets the height of the grid in pixels * * @param height The height in pixels */ public void setPreciseHeight(int height) { int offsetHeight = height - getTitlebarOffsetHeight() - 10; setHeight(height + "px"); if (offsetHeight > 20) { dataGrid.getScrollPanel().setHeight((offsetHeight - 20) + "px"); } // ERRAI-72 else if (offsetHeight < 0) { dataGrid.getScrollPanel().setHeight(20 + "px"); } else { dataGrid.getScrollPanel().setHeight(offsetHeight + "px"); } } /** * Set thes the width of the grid. * * @param width The CSS width string. */ public void setWidth(String width) { focusPanel.setWidth(width); } /** * Sets the width of the grid in pixels. * * @param width The width in pixels. */ public void setPreciseWidth(int width) { setWidth(width + "px"); titleBar.getScrollPanel().setWidth(width - 20 + "px"); dataGrid.getScrollPanel().setWidth(width + "px"); } @Override public void setPixelSize(int width, int height) { setPreciseWidth(width); setPreciseHeight(height); } public void sizeToParent() { DeferredCommand.addCommand(new Command() { public void execute() { if (getParent().isVisible()) setPixelSize(getParent().getOffsetWidth(), getParent().getOffsetHeight()); } }); } /** * Increase or decrease the width by a relative amount. A positive value * will increase the size of the grid, while a negative value will shrink * the size. * * @param amount Size in pixels. */ public void growWidth(int amount) { int newWidth = dataGrid.getScrollPanel().getOffsetWidth() + amount; setWidth(newWidth + "px"); titleBar.getScrollPanel().setWidth(newWidth - 20 + "px"); dataGrid.getScrollPanel().setWidth(newWidth + "px"); } public Map<Integer, Boolean> getSortedColumns() { return sortedColumns; } /** * If there is a sorted column, this will return an instance of the header * cell for that sorted column. * * @return An instance of WSCell. */ public WSCell getSortedColumnHeader() { return sortedColumnHeader; } /** * Returns the sort order of the specified column. The boolean value * <tt>true</tt> if the order is ascending, <tt>false</em> if it's decending or not sorted at all. * * @param col The column. * @return true if ascending */ public boolean getColumnSortOrder(int col) { if (sortedColumns.containsKey(col)) { return sortedColumns.get(col); } else { sortedColumns.put(col, true); return true; } } /** * Registers a {@link ChangeHandler} with the grid. * * @param handler - */ public void addCellChangeHandler(ChangeHandler handler) { cellChangeHandlers.add(handler); } public void addAfterCellChangeHandler(ChangeHandler handler) { afterCellChangeHandlers.add(handler); } /** * Removes a {@link ChangeHandler} from the grid. * * @param handler - */ public void removeCellChangeHandler(ChangeHandler handler) { cellChangeHandlers.remove(handler); } public void removeAfterCellChangeHandler(ChangeHandler handler) { cellChangeHandlers.remove(handler); } private void fireAllCellChangeHandlers(WSCell cell, Object newValue) { for (ChangeHandler c : cellChangeHandlers) c.onChange(new CellChangeEvent(cell, newValue)); } private void fireAllAfterCellChangeHandlers(WSCell cell) { for (ChangeHandler c : afterCellChangeHandlers) c.onChange(new CellChangeEvent(cell, cell.getCellFormat().getValue())); } public void mergeSelected() { WSCell start; // TODO Not sure this should use Focus Manager int cellX = fm.getStartCell().col; int cellY = fm.getStartCell().row; if (!forwardDirX) { cellX -= fillX; } if (!forwardDirY) { cellY -= fillY; } start = dataGrid.cellAt(cellY, cellX); start.mergeColumns(fillX + 1); start.mergeRows(fillY + 1); blurAll(); } @Override protected void onAttach() { super.onAttach(); int titleHeight = titleBar.getOffsetHeight(); if (titleHeight > 0) // workaround // https://jira.jboss.org/jira/browse/ERRAI-52 innerPanel.setCellHeight(titleBar, titleHeight + "px"); if (resizeOnAttach) { for (int i = 0; i < colSizes.size(); i++) { setColumnWidth(i, colSizes.get(i)); } } } public void onResize() { setPixelSize(getParent().getOffsetWidth(), getParent().getOffsetHeight()); } private static final int NONEDITABLE = 0; private static final int EDITABLE = 1; private static final int TITLEGRID = 1 << 1; public enum GridType { NONEDITABLE_GRID(NONEDITABLE), EDITABLE_GRID(EDITABLE), TITLEBAR(TITLEGRID); private int options; GridType(int options) { this.options = options; } public boolean isEditable() { return (EDITABLE & options) != 0; } public boolean isTitleGrid() { return (TITLEGRID & options) != 0; } } public static void disableTextSelection(Element elem, boolean disable) { disableTextSelectInternal(elem, disable); } public Stack<WSCell> getSelectionList() { return selectionList; } public boolean isRowSelectionOnly() { return rowSelectionOnly; } public void setRowSelectionOnly(boolean rowSelectionOnly) { this.rowSelectionOnly = rowSelectionOnly; } private native static void disableTextSelectInternal(Element e, boolean disable)/*-{ if (disable) { e.ondrag = function () { return false; }; e.onselectstart = function () { return false; }; } else { e.ondrag = null; e.onselectstart = null; } }-*/; public static native String getUserAgent() /*-{ return navigator.userAgent.toLowerCase(); }-*/; public static native boolean isNumeric(String input) /*-{ return (input - 0) == input && input.length > 0; }-*/; public static native boolean isEmpty(String input) /*-{ return input == null || input == ""; }-*/; }