Java tutorial
/* * MultiSelectCellTable.java * * Copyright (C) 2009-12 by RStudio, Inc. * * Unless you have received this program directly from RStudio pursuant * to the terms of a commercial license agreement with RStudio, then * this program is licensed to you under the terms of version 3 of the * GNU Affero General Public License. This program is distributed WITHOUT * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT, * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details. * */ package org.rstudio.core.client.widget; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.EventTarget; import com.google.gwt.dom.client.NativeEvent; import com.google.gwt.dom.client.TableCellElement; import com.google.gwt.dom.client.TableElement; import com.google.gwt.dom.client.TableRowElement; import com.google.gwt.dom.client.TableSectionElement; import com.google.gwt.event.dom.client.*; import com.google.gwt.event.shared.HandlerManager; import com.google.gwt.event.shared.HandlerRegistration; import com.google.gwt.user.cellview.client.CellTable; import com.google.gwt.user.client.ui.Widget; import com.google.gwt.view.client.MultiSelectionModel; import com.google.gwt.view.client.ProvidesKey; import org.rstudio.core.client.BrowseCap; import org.rstudio.core.client.command.KeyboardShortcut; import org.rstudio.core.client.dom.DomUtils; public class MultiSelectCellTable<T> extends CellTable<T> implements HasKeyDownHandlers, HasClickHandlers, HasMouseDownHandlers, HasContextMenuHandlers { public MultiSelectCellTable() { commonInit(); } public MultiSelectCellTable(int pageSize) { super(pageSize); commonInit(); } public MultiSelectCellTable(ProvidesKey<T> keyProvider) { super(keyProvider); commonInit(); } public MultiSelectCellTable(int pageSize, Resources resources) { super(pageSize, resources); commonInit(); } public MultiSelectCellTable(int pageSize, ProvidesKey<T> keyProvider) { super(pageSize, keyProvider); commonInit(); } public MultiSelectCellTable(int pageSize, Resources resources, ProvidesKey<T> keyProvider) { super(pageSize, resources, keyProvider); commonInit(); } public MultiSelectCellTable(int pageSize, Resources resources, ProvidesKey<T> keyProvider, Widget loadingIndicator) { super(pageSize, resources, keyProvider, loadingIndicator); commonInit(); } @Override public void setFocus(boolean focused) { if (focused) DomUtils.focus(getElement(), false); } private void commonInit() { setKeyboardSelectionPolicy(KeyboardSelectionPolicy.DISABLED); getElement().setTabIndex(-1); addDomHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { // Note: this formerly used DomUtils.focus, but the implementation // of DomUtils used on IE11 focuses the window afterwards, which // blurs the element. Since we don't need to drive selection, we // now just use native focus here. getElement().focus(); } }, ClickEvent.getType()); addKeyDownHandler(new KeyDownHandler() { @Override public void onKeyDown(KeyDownEvent event) { MultiSelectCellTable.this.handleKeyDown(event); } }); addDomHandler(new ContextMenuHandler() { @Override public void onContextMenu(ContextMenuEvent event) { MultiSelectCellTable.this.handleContextMenu(event); } }, ContextMenuEvent.getType()); } @Override protected boolean isKeyboardNavigationSuppressed() { return true; } @SuppressWarnings("rawtypes") private void clearSelection() { if (getSelectionModel() instanceof MultiSelectionModel) ((MultiSelectionModel) getSelectionModel()).clear(); } private void handleKeyDown(KeyDownEvent event) { int modifiers = KeyboardShortcut.getModifierValue(event.getNativeEvent()); switch (event.getNativeKeyCode()) { // TODO: Handle home/end, pageup/pagedown case KeyCodes.KEY_UP: case KeyCodes.KEY_DOWN: event.preventDefault(); event.stopPropagation(); switch (modifiers) { case 0: case KeyboardShortcut.SHIFT: break; default: return; } moveSelection(event.getNativeKeyCode() == KeyCodes.KEY_UP, modifiers == KeyboardShortcut.SHIFT); break; case 'A': if (modifiers == (BrowseCap.hasMetaKey() ? KeyboardShortcut.META : KeyboardShortcut.CTRL)) { if (getSelectionModel() instanceof MultiSelectionModel) { event.preventDefault(); event.stopPropagation(); for (T item : getVisibleItems()) getSelectionModel().setSelected(item, true); } } break; } } // handle context-menu clicks. this implementation (specifically the // detection of table rows from dom events) is based on the code in // AbstractCellTable.onBrowserEvent2. we first determine if the click // applies to a row in the table -- if it does then we squelch the // standard handling of the event and update the selection and then // forward the event on to any external listeners private void handleContextMenu(ContextMenuEvent cmEvent) { // bail if there are no context menu handlers if (handlerManager_.getHandlerCount(ContextMenuEvent.getType()) == 0) return; // Get the event target. NativeEvent event = cmEvent.getNativeEvent(); EventTarget eventTarget = event.getEventTarget(); if (!Element.is(eventTarget)) return; final Element target = event.getEventTarget().cast(); // always squelch default handling (when there is a handler) event.stopPropagation(); event.preventDefault(); // find the table cell element then get its parent and cast to row TableCellElement tableCell = findNearestParentCell(target); if (tableCell == null) return; Element trElem = tableCell.getParentElement(); if (trElem == null) return; TableRowElement tr = TableRowElement.as(trElem); // get the section of the row and confirm it is a tbody (as opposed // to a thead or tfoot) Element sectionElem = tr.getParentElement(); if (sectionElem == null) return; TableSectionElement section = TableSectionElement.as(sectionElem); if (section != getTableBodyElement()) return; // determine the row/item target int row = tr.getSectionRowIndex(); T item = getVisibleItem(row); // if this row isn't already selected then clear the existing selection if (!getSelectionModel().isSelected(item)) clearSelection(); // select the clicked on item getSelectionModel().setSelected(item, true); // forward the event DomEvent.fireNativeEvent(event, handlerManager_); } // forked from private AbstractCellTable.findNearestParentCell private TableCellElement findNearestParentCell(Element elem) { while ((elem != null) && (elem != getElement())) { // TODO: We need is() implementations in all Element subclasses. // This would allow us to use TableCellElement.is() -- much cleaner. String tagName = elem.getTagName(); if ("td".equalsIgnoreCase(tagName) || "th".equalsIgnoreCase(tagName)) { return elem.cast(); } elem = elem.getParentElement(); } return null; } @Override public HandlerRegistration addKeyDownHandler(KeyDownHandler handler) { return addDomHandler(handler, KeyDownEvent.getType()); } public void moveSelection(boolean up, boolean extend) { if (getVisibleItemCount() == 0) return; int min = getVisibleItemCount(); int max = -1; for (int i = 0; i < getVisibleItemCount(); i++) { if (getSelectionModel().isSelected(getVisibleItem(i))) { max = i; if (min > i) min = i; } } if (up) { int row = Math.max(0, min - 1); if (!canSelectVisibleRow(row)) row = min; if (!extend) clearSelection(); getSelectionModel().setSelected(getVisibleItem(row), true); ensureRowVisible(row, true); } else { int row = Math.min(getVisibleItemCount() - 1, max + 1); if (!canSelectVisibleRow(row)) row = max; if (!extend) clearSelection(); getSelectionModel().setSelected(getVisibleItem(row), true); ensureRowVisible(row, false); } } private void ensureRowVisible(int row, boolean alignWithTop) { Element el; if (row == 0 && alignWithTop) el = (getElement().<TableElement>cast()).getRows().getItem(0); else el = getRowElement(row); if (el != null) DomUtils.scrollIntoViewVert(el); } protected boolean canSelectVisibleRow(int visibleRow) { return true; } @Override public HandlerRegistration addClickHandler(ClickHandler handler) { return addDomHandler(handler, ClickEvent.getType()); } @Override public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) { return addDomHandler(handler, MouseDownEvent.getType()); } @Override public HandlerRegistration addContextMenuHandler(ContextMenuHandler handler) { return handlerManager_.addHandler(ContextMenuEvent.getType(), handler); } // we have our own HandlerManager so that we can fire the ContextMenuEvent // to listeners after we have done our own handling of it (specifically // we need to nix the browser context menu and update the selection). private final HandlerManager handlerManager_ = new HandlerManager(this); }