/*
* Ext GWT - Ext for GWT
* Copyright(c) 2007-2009, Ext JS, LLC.
* licensing@extjs.com
*
* http://extjs.com/license
*/
package com.extjs.gxt.ui.client.widget.grid;
import java.util.Arrays;
import com.extjs.gxt.ui.client.Style.SelectionMode;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.event.BaseEvent;
import com.extjs.gxt.ui.client.event.DomEvent;
import com.extjs.gxt.ui.client.event.EventType;
import com.extjs.gxt.ui.client.event.Events;
import com.extjs.gxt.ui.client.event.GridEvent;
import com.extjs.gxt.ui.client.event.Listener;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.util.KeyNav;
import com.extjs.gxt.ui.client.widget.selection.AbstractStoreSelectionModel;
import com.google.gwt.event.dom.client.KeyCodes;
/**
* Grid selection model.
*
* <dl>
* <dt>Inherited Events:</dt>
* <dd>AbstractStoreSelectionModel BeforeSelect</dd>
* <dd>AbstractStoreSelectionModel SelectionChange</dd>
* </dl>
*/
public class GridSelectionModel<M extends ModelData> extends
AbstractStoreSelectionModel<M> implements Listener<BaseEvent> {
@SuppressWarnings("unchecked")
class Callback {
private GridSelectionModel sm;
public Callback(GridSelectionModel sm) {
this.sm = sm;
}
public boolean isSelectable(int row, int cell, boolean acceptsNav) {
return sm.isSelectable(row, cell, acceptsNav);
}
}
protected Grid<M> grid;
protected ListStore<M> listStore;
protected KeyNav<GridEvent<M>> keyNav = new KeyNav<GridEvent<M>>() {
@Override
public void onDown(GridEvent<M> e) {
onKeyDown(e);
}
@Override
public void onKeyPress(GridEvent<M> ce) {
GridSelectionModel.this.onKeyPress(ce);
}
@Override
public void onLeft(GridEvent<M> ce) {
onKeyLeft(ce);
}
@Override
public void onRight(GridEvent<M> ce) {
onKeyRight(ce);
}
@Override
public void onUp(GridEvent<M> e) {
onKeyUp(e);
}
};
private Callback callback = new Callback(this);
private boolean moveEditorOnEnter;
@SuppressWarnings("unchecked")
public void bindGrid(Grid grid) {
if (this.grid != null) {
this.grid.removeListener(Events.RowMouseDown, this);
this.grid.removeListener(Events.ContextMenu, this);
this.grid.getView().removeListener(Events.RowUpdated, this);
this.grid.getView().removeListener(Events.Refresh, this);
keyNav.bind(null);
bind(null);
}
this.grid = grid;
if (grid != null) {
grid.addListener(Events.RowMouseDown, this);
grid.addListener(Events.ContextMenu, this);
grid.getView().addListener(Events.RowUpdated, this);
grid.getView().addListener(Events.Refresh, this);
keyNav.bind(grid);
bind(grid.getStore());
this.listStore = (ListStore) grid.getStore();
}
bind(grid != null ? grid.getStore() : null);
}
@SuppressWarnings("unchecked")
public void handleEvent(BaseEvent e) {
EventType type = e.getType();
if (type == Events.RowMouseDown) {
handleMouseDown((GridEvent) e);
} else if (type == Events.RowUpdated) {
onRowUpdated((GridEvent) e);
} else if (type == Events.Refresh) {
refresh();
}
}
public boolean isLocked() {
return false;
}
/**
* Returns true of the editor moves on enter.
*
* @return true if editor moves on enter
*/
public boolean isMoveEditorOnEnter() {
return moveEditorOnEnter;
}
/**
* Selects the next row.
*
* @param keepexisting true to keep existing selections
*/
public void selectNext(boolean keepexisting) {
if (hasNext()) {
int idx = listStore.indexOf(lastSelected) + 1;
select(idx, keepexisting);
grid.getView().focusRow(idx);
}
}
/**
* Selects the previous row.
*
* @param keepexisting true to keep existing selections
*/
public void selectPrevious(boolean keepexisting) {
if (hasPrevious()) {
int idx = listStore.indexOf(lastSelected) - 1;
select(idx, keepexisting);
grid.getView().focusRow(idx);
}
}
/**
* Set this to true to move the editor to the next editable cell on pressing
* enter.
*
* @param moveEditorOnEnter true to move the editor on pressing enter.
*/
public void setMoveEditorOnEnter(boolean moveEditorOnEnter) {
this.moveEditorOnEnter = moveEditorOnEnter;
}
protected void doFocus(int row, int cell) {
GridView view = grid.getView();
view.focusRow(row);
}
@SuppressWarnings("unchecked")
protected void handleMouseDown(GridEvent<M> e) {
if (isLocked()) {
return;
}
if (e.isRightClick()) {
if (e.getRowIndex() != -1) {
if (isSelected(listStore.getAt(e.getRowIndex()))
&& selectionMode != SelectionMode.SINGLE) {
return;
}
select(e.getRowIndex(), false);
}
} else {
GridView view = grid.getView();
M sel = listStore.getAt(e.getRowIndex());
if (selectionMode == SelectionMode.SINGLE) {
if (isSelected(sel) && e.isControlKey()) {
deselect(sel);
} else if (!isSelected(sel)) {
select(sel, false);
view.focusCell(e.getRowIndex(), e.getColIndex(), true);
}
} else {
if (e.isShiftKey() && lastSelected != null) {
int last = listStore.indexOf(lastSelected);
int index = e.getRowIndex();
int a = (last > index) ? index : last;
int b = (last < index) ? index : last;
select(a, b, e.isControlKey());
lastSelected = listStore.getAt(last);
view.focusCell(index, e.getColIndex(), true);
} else if (isSelected(sel) && e.isControlKey()) {
doDeselect(Arrays.asList(sel), false);
} else {
doSelect(Arrays.asList(sel), e.isControlKey(), false);
view.focusCell(e.getRowIndex(), e.getColIndex(), true);
}
}
}
}
protected boolean hasNext() {
return lastSelected != null
&& listStore.indexOf(lastSelected) < (listStore.getCount() - 1);
}
protected boolean hasPrevious() {
return lastSelected != null && listStore.indexOf(lastSelected) > 0;
}
protected boolean isSelectable(int row, int cell, boolean acceptsNav) {
if (acceptsNav) {
return !grid.getColumnModel().isHidden(cell)
&& grid.getColumnModel().isCellEditable(cell);
} else {
return !grid.getColumnModel().isHidden(cell);
}
}
public void onEditorKey(DomEvent e) {
EditSupport editGrid = (EditSupport) grid;
int k = e.getKeyCode();
Cell newCell = null;
CellEditor editor = editGrid.getActiveEditor();
switch (k) {
case KeyCodes.KEY_ENTER:
case KeyCodes.KEY_TAB:
e.stopEvent();
editor.completeEdit();
if ((k == KeyCodes.KEY_ENTER && moveEditorOnEnter) || k == KeyCodes.KEY_TAB) {
if (e.isShiftKey()) {
newCell = grid.walkCells(editor.row, editor.col - 1, -1, callback, true);
} else {
newCell = grid.walkCells(editor.row, editor.col + 1, 1, callback, true);
}
}
break;
case KeyCodes.KEY_ESCAPE:
editor.cancelEdit();
break;
}
if (newCell != null) {
editGrid.startEditing(newCell.row, newCell.cell);
} else {
if (k == KeyCodes.KEY_ENTER || k == KeyCodes.KEY_TAB || k == KeyCodes.KEY_ESCAPE) {
grid.getView().focusCell(editor.row, editor.col, false);
}
}
}
protected void onKeyDown(GridEvent<M> e) {
selectNext(e.isShiftKey());
e.preventDefault();
}
protected void onKeyLeft(GridEvent<M> ce) {
}
protected void onKeyPress(GridEvent<M> e) {
}
protected void onKeyRight(GridEvent<M> ce) {
}
protected void onKeyUp(GridEvent<M> e) {
selectPrevious(e.isShiftKey());
e.preventDefault();
}
protected void onRowUpdated(GridEvent<M> ge) {
if (isSelected(ge.getModel())) {
grid.getView().onRowSelect(ge.getRowIndex());
}
}
@Override
protected void onSelectChange(M model, boolean select) {
int idx = listStore.indexOf(model);
if (idx == -1) {
return;
}
if (select) {
grid.getView().onRowSelect(listStore.indexOf(model));
} else {
grid.getView().onRowDeselect(listStore.indexOf(model));
}
}
}
class Cell {
public int row;
public int cell;
public Cell(int row, int cell) {
this.row = row;
this.cell = cell;
}
}