package com.google.gwt.sample.showcase.client.content.cell;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.HasKeyDownHandlers;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyDownHandler;
import com.google.gwt.sample.showcase.client.Settings;
import com.google.gwt.sample.showcase.client.content.cell.ContactDatabase.ContactInfo;
import com.google.gwt.user.cellview.client.AbstractHasData.DefaultKeyboardSelectionHandler;
import com.google.gwt.user.cellview.client.CellList;
import com.google.gwt.user.cellview.client.LoadingStateChangeEvent;
import com.google.gwt.user.cellview.client.LoadingStateChangeEvent.LoadingState;
import com.google.gwt.view.client.CellPreviewEvent;
class CustomKeyboardHandler extends DefaultKeyboardSelectionHandler<ContactInfo> {
interface SelectableWidget extends HasKeyDownHandlers {
void selectWidget();
}
private static final int PAGE_INCREMENT = 11;
private final CellList<ContactInfo> cellList;
private final SelectableWidget widgetAboveList;
private boolean isEndRequestPending = false;
public CustomKeyboardHandler(
CellList<ContactInfo> cellList, SelectableWidget widgetAboveList) {
super(cellList);
this.cellList = cellList;
this.widgetAboveList = widgetAboveList;
cellList.addLoadingStateChangeHandler(new LoadingStateChangeEvent.Handler() {
@Override
public void onLoadingStateChanged(LoadingStateChangeEvent event) {
onLoadingChange(event.getLoadingState());
}
});
widgetAboveList.addKeyDownHandler(new KeyDownHandler() {
@Override
public void onKeyDown(KeyDownEvent event) {
switch (event.getNativeKeyCode()) {
case KeyCodes.KEY_DOWN:
case KeyCodes.KEY_J:
setCurrentRow(0);
event.preventDefault();
break;
}
}
});
}
@Override
public void onCellPreview(CellPreviewEvent<ContactInfo> event) {
if (Settings.get().getKeyHandling()) {
NativeEvent nativeEvent = event.getNativeEvent();
if (nativeEvent.getType() == BrowserEvents.KEYDOWN) { // A key was pushed down
if (nativeEvent.getShiftKey() || nativeEvent.getAltKey()
|| nativeEvent.getCtrlKey() || nativeEvent.getMetaKey()) {
// Ignore if a modifier key is down
return;
}
switch (nativeEvent.getKeyCode()) {
case KeyCodes.KEY_DOWN: // The down arrow key
case KeyCodes.KEY_J:
setCurrentRow(cellList.getKeyboardSelectedRow() + 1);
cancelEvent(event);
break;
case KeyCodes.KEY_UP: // The up arrow key
case KeyCodes.KEY_K:
if (cellList.getKeyboardSelectedRow() < 1) {
goAboveList(event.getValue());
} else {
setCurrentRow(cellList.getKeyboardSelectedRow() - 1);
}
cancelEvent(event);
break;
case KeyCodes.KEY_PAGEDOWN:
case KeyCodes.KEY_SPACE:
setCurrentRow(cellList.getKeyboardSelectedRow() + PAGE_INCREMENT);
cancelEvent(event);
break;
case KeyCodes.KEY_PAGEUP:
setCurrentRow(cellList.getKeyboardSelectedRow() - PAGE_INCREMENT);
cancelEvent(event);
break;
case KeyCodes.KEY_HOME:
setCurrentRow(cellList.getPageStart());
cancelEvent(event);
break;
case KeyCodes.KEY_END:
goToVeryEnd();
cancelEvent(event);
break;
}
// Bypass the default handler (super-class) for all keydown events.
// For keys not handled here, let the browser handle them.
return;
}
}
// Should get here only if event was not handled above. Send the event to
// the default handler.
super.onCellPreview(event);
}
void setCurrentRow(int row) {
cellList.setKeyboardSelectedRow(row);
// Read the current row index back, because the cellList will have clipped
// it to a valid row if we tried to set it to a row that doesn't exist.
int newRow = cellList.getKeyboardSelectedRow();
// Scroll row into view (needed because the default scrolling may be disabled
// so that the view doesn't jump on data updates when the current
// row is out of view). Also useful, if you need custom scrolling, e.g.
// because of fixed elements on the page.
cellList.getRowElement(newRow).scrollIntoView();
}
// Re-implement DefaultKeyboardSelectionHandler.handledEvent because that's
// package-private.
void cancelEvent(CellPreviewEvent<ContactInfo> event) {
event.setCanceled(true);
event.getNativeEvent().preventDefault();
}
void goAboveList(ContactInfo keyboardSelectedValue) {
cellList.getSelectionModel().setSelected(keyboardSelectedValue, false);
// Unselecting the row (above) will steal the focus (doesn't really need to,
// but does), and do this deferred. Defer select here to come after that.
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
widgetAboveList.selectWidget();
}
});
}
// The async data loading broke the end key. The default handler only goes to
// the end of what's loaded. To fix it, update the page size to request more
// data, then wait until it's loaded before selecting the last row.
void goToVeryEnd() {
int totalRows = cellList.getRowCount();
int pageStart = cellList.getPageStart();
int pageSize = cellList.getPageSize();
int renderedRows = cellList.getVisibleItemCount();
if (cellList.isRowCountExact() && totalRows > pageStart + pageSize) {
isEndRequestPending = true;
cellList.setPageSize(totalRows - pageStart);
// We'll select the last row after it's loaded
} else {
// Just go to the end of what's rendered
setCurrentRow(renderedRows - 1);
}
}
void onLoadingChange(LoadingState newState) {
if (isEndRequestPending && newState == LoadingState.LOADED) {
// Loading change events are sent just before any new rows are rendered.
// Defer execution to let them render.
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
int totalRows = cellList.getRowCount();
int pageStart = cellList.getPageStart();
int pageSize = cellList.getPageSize();
int renderedRows = cellList.getVisibleItemCount();
if (pageStart + pageSize < totalRows) {
// Either more rows got added, or the page size got shrunk. Fix it.
cellList.setPageSize(totalRows - pageStart);
} else if (renderedRows == totalRows - pageStart ) {
isEndRequestPending = false;
setCurrentRow(renderedRows - 1);
} else {
// keep waiting
}
}
});
}
}
}