/*
This file belongs to the Servoy development and deployment environment, Copyright (C) 1997-2010 Servoy BV
This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Affero General Public License as published by the Free
Software Foundation; either version 3 of the License, or (at your option) any
later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along
with this program; if not, see http://www.gnu.org/licenses or write to the Free
Software Foundation,Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
*/
package com.servoy.j2db.smart.dataui;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.table.TableColumn;
import com.servoy.j2db.util.Debug;
import com.servoy.j2db.util.ISupplyFocusChildren;
public class TableTabSequenceHandler
{
private Action defaultTabAction = null;
private Action defaultShiftTabAction = null;
private Action newTabAction = null;
private Action newShiftTabAction = null;
private List<TableColumn> columnTabSequence;
private final JTable table;
public TableTabSequenceHandler(JTable table, ArrayList<TableColumn> columnTabSequence)
{
this.table = table;
if (columnTabSequence.size() > 0)
{
this.columnTabSequence = columnTabSequence;
overrideDefaultTabBehavior();
}
else
{
this.columnTabSequence = null;
}
}
public void setNewTabSequence(List<Component> list)
{
boolean isDefaultBehavior = (list == null || list.size() == 0);
ArrayList<TableColumn> newTabSequence = null;
if (!isDefaultBehavior)
{
newTabSequence = new ArrayList<TableColumn>();
// generate column order from component list
for (Component comp : list)
{
Enumeration<TableColumn> allColumns = table.getColumnModel().getColumns();
while (allColumns.hasMoreElements())
{
CellAdapter column = (CellAdapter)allColumns.nextElement();
if (componentIdentifiesColumn(comp, column) && !newTabSequence.contains(column))
{
newTabSequence.add(column);
break;
}
}
}
if (newTabSequence.size() == 0) isDefaultBehavior = true;
}
if (isDefaultBehavior)
{
if (columnTabSequence != null) restoreDefaultTabBehavior();
}
else
{
if (columnTabSequence == null)
{
columnTabSequence = newTabSequence;
overrideDefaultTabBehavior();
}
else
{
columnTabSequence = newTabSequence;
}
}
}
public List<String> getTabSequence()
{
List<String> tabSequence = new ArrayList<String>();
if (columnTabSequence != null)
{
for (TableColumn tc : columnTabSequence)
{
String name = ((CellAdapter)tc).getName();
if (name != null) tabSequence.add(name);
}
}
return tabSequence;
}
private boolean componentIdentifiesColumn(Component comp, CellAdapter column)
{
if (comp == column.getEditor())
{
return true;
}
else if (column.getEditor() instanceof ISupplyFocusChildren)
{
for (Object child : ((ISupplyFocusChildren)column.getEditor()).getFocusChildren())
{
if (child == comp) return true;
}
}
return false;
}
private void overrideDefaultTabBehavior()
{
// take TAB order into consideration (otherwise TAB/SHIFT+TAB will trigger behaviour in BasicTableUI to take cells sequentially - this behaviour is overridden)
InputMap im = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
ActionMap am = table.getActionMap();
KeyStroke shiftTab = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, java.awt.event.InputEvent.SHIFT_DOWN_MASK);
KeyStroke tab = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
if (newTabAction == null)
{
newTabAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
selectNextCell(e);
}
};
newShiftTabAction = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
selectPreviousCell(e);
}
};
defaultTabAction = am.get(im.get(tab));
defaultShiftTabAction = am.get(im.get(shiftTab));
}
am.put(im.get(tab), newTabAction);
am.put(im.get(shiftTab), newShiftTabAction);
}
private void restoreDefaultTabBehavior()
{
if (newTabAction != null)
{
InputMap im = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
ActionMap am = table.getActionMap();
KeyStroke shiftTab = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, java.awt.event.InputEvent.SHIFT_DOWN_MASK);
KeyStroke tab = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
am.put(im.get(tab), defaultTabAction);
am.put(im.get(shiftTab), defaultShiftTabAction);
}
columnTabSequence = null;
}
protected void selectPreviousCell(ActionEvent e)
{
// SHIFT+TAB
JTable t = (JTable)e.getSource();
if (t.getColumnCount() == 0 || t.getRowCount() == 0) return;
if (t.isEditing() && !t.getCellEditor().stopCellEditing()) return;
int column = t.getColumnModel().getSelectionModel().getLeadSelectionIndex();
int row = t.getSelectionModel().getLeadSelectionIndex();
if (column > t.getColumnCount() || column < 0 || row > t.getRowCount() || row < 0)
{
column = 0;
row = 0;
}
else
{
TableColumn currentColumn = t.getColumnModel().getColumn(column);
int tabIndex = columnTabSequence.indexOf(currentColumn);
if (tabIndex != -1)
{
// select previous cell according to specified tab sequence
tabIndex--;
if (tabIndex < 0)
{
tabIndex = columnTabSequence.size() - 1;
row--;
if (row < 0) row = t.getRowCount() - 1;
}
int newColumnIndex = getColumnIndex(columnTabSequence.get(tabIndex), t);
if (newColumnIndex == -1) tabIndex = -1; // just use normal sequencing, cause we can't find the new column's index
else column = newColumnIndex;
}
if (tabIndex == -1)
{
// focus is now on a column that is not part of specified column tab sequence; just select previous cell normally
defaultShiftTabAction.actionPerformed(e);
return;
}
}
t.changeSelection(row, column, false, false);
}
protected void selectNextCell(ActionEvent e)
{
// TAB
JTable t = (JTable)e.getSource();
if (t.getColumnCount() == 0 || t.getRowCount() == 0) return;
if (t.isEditing() && !t.getCellEditor().stopCellEditing()) return;
int column = t.getColumnModel().getSelectionModel().getLeadSelectionIndex();
int row = t.getSelectionModel().getLeadSelectionIndex();
if (column > t.getColumnCount() || column < 0 || row > t.getRowCount() || row < 0)
{
column = 0;
row = 0;
}
else
{
TableColumn currentColumn = t.getColumnModel().getColumn(column);
int tabIndex = columnTabSequence.indexOf(currentColumn);
if (tabIndex != -1)
{
// select previous cell according to specified tab sequence
tabIndex++;
if (tabIndex >= columnTabSequence.size())
{
tabIndex = 0;
row++;
if (row >= t.getRowCount()) row = 0;
}
int newColumnIndex = getColumnIndex(columnTabSequence.get(tabIndex), t);
if (newColumnIndex == -1) tabIndex = -1; // just use normal sequencing, cause we can't find the new column's index
else column = newColumnIndex;
}
if (tabIndex == -1)
{
// focus is now on a column that is not part of specified column tab sequence; just select next cell normally
defaultTabAction.actionPerformed(e);
return;
}
}
t.changeSelection(row, column, false, false);
}
private int getColumnIndex(TableColumn tableColumn, JTable t)
{
for (int i = t.getColumnModel().getColumnCount() - 1; i >= 0; i--)
{
if (tableColumn == t.getColumnModel().getColumn(i))
{
return i;
}
}
Debug.error("Cannot find next/prev column in tab sequence... something is wrong");
return -1;
}
}