/*
* Copyright (c) 2011, grossmann, Nikolaus Moll
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of the jo-widgets.org nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL jo-widgets.org BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*/
package org.jowidgets.spi.impl.swing.common.widgets;
import java.awt.Color;
import java.awt.Component;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EventObject;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.AbstractCellEditor;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableColumnModelEvent;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.jowidgets.common.color.IColorConstant;
import org.jowidgets.common.image.IImageConstant;
import org.jowidgets.common.model.ITableCell;
import org.jowidgets.common.model.ITableColumnModelListener;
import org.jowidgets.common.model.ITableColumnModelObservable;
import org.jowidgets.common.model.ITableColumnModelSpi;
import org.jowidgets.common.model.ITableColumnSpi;
import org.jowidgets.common.model.ITableDataModel;
import org.jowidgets.common.model.ITableDataModelListener;
import org.jowidgets.common.model.ITableDataModelObservable;
import org.jowidgets.common.types.AlignmentHorizontal;
import org.jowidgets.common.types.Dimension;
import org.jowidgets.common.types.Markup;
import org.jowidgets.common.types.Modifier;
import org.jowidgets.common.types.MouseButton;
import org.jowidgets.common.types.Position;
import org.jowidgets.common.types.TablePackPolicy;
import org.jowidgets.common.types.TableSelectionPolicy;
import org.jowidgets.common.types.VirtualKey;
import org.jowidgets.common.widgets.IControlCommon;
import org.jowidgets.common.widgets.controller.IFocusListener;
import org.jowidgets.common.widgets.controller.IKeyEvent;
import org.jowidgets.common.widgets.controller.IKeyListener;
import org.jowidgets.common.widgets.controller.IPopupDetectionListener;
import org.jowidgets.common.widgets.controller.ITableCellListener;
import org.jowidgets.common.widgets.controller.ITableCellMouseEvent;
import org.jowidgets.common.widgets.controller.ITableCellPopupDetectionListener;
import org.jowidgets.common.widgets.controller.ITableColumnListener;
import org.jowidgets.common.widgets.controller.ITableColumnMouseEvent;
import org.jowidgets.common.widgets.controller.ITableColumnPopupDetectionListener;
import org.jowidgets.common.widgets.controller.ITableSelectionListener;
import org.jowidgets.common.widgets.descriptor.IWidgetDescriptor;
import org.jowidgets.common.widgets.editor.EditActivation;
import org.jowidgets.common.widgets.editor.ITableCellEditor;
import org.jowidgets.common.widgets.editor.ITableCellEditorFactory;
import org.jowidgets.common.widgets.factory.ICustomWidgetFactory;
import org.jowidgets.common.widgets.factory.IGenericWidgetFactory;
import org.jowidgets.spi.impl.controller.FocusObservable;
import org.jowidgets.spi.impl.controller.KeyObservable;
import org.jowidgets.spi.impl.controller.PopupDetectionObservable;
import org.jowidgets.spi.impl.controller.TableCellMouseEvent;
import org.jowidgets.spi.impl.controller.TableCellObservable;
import org.jowidgets.spi.impl.controller.TableCellPopupDetectionObservable;
import org.jowidgets.spi.impl.controller.TableCellPopupEvent;
import org.jowidgets.spi.impl.controller.TableColumnMouseEvent;
import org.jowidgets.spi.impl.controller.TableColumnObservable;
import org.jowidgets.spi.impl.controller.TableColumnPopupDetectionObservable;
import org.jowidgets.spi.impl.controller.TableColumnPopupEvent;
import org.jowidgets.spi.impl.controller.TableColumnResizeEvent;
import org.jowidgets.spi.impl.controller.TableSelectionObservable;
import org.jowidgets.spi.impl.swing.common.image.SwingImageRegistry;
import org.jowidgets.spi.impl.swing.common.options.SwingOptions;
import org.jowidgets.spi.impl.swing.common.util.AlignmentConvert;
import org.jowidgets.spi.impl.swing.common.util.ColorConvert;
import org.jowidgets.spi.impl.swing.common.util.FontProvider;
import org.jowidgets.spi.impl.swing.common.util.MouseUtil;
import org.jowidgets.spi.impl.swing.common.util.PositionConvert;
import org.jowidgets.spi.impl.swing.common.widgets.base.SwingActionWrapper;
import org.jowidgets.spi.impl.swing.common.widgets.base.TableColumnModelAdapter;
import org.jowidgets.spi.impl.swing.common.widgets.event.LazyKeyEventContentFactory;
import org.jowidgets.spi.widgets.ITableSpi;
import org.jowidgets.spi.widgets.setup.ITableSetupSpi;
import org.jowidgets.util.ArrayUtils;
import org.jowidgets.util.Assert;
import org.jowidgets.util.Interval;
import org.jowidgets.util.ValueHolder;
import org.jowidgets.util.event.IObservableCallback;
public class TableImpl extends SwingControl implements ITableSpi {
private final IGenericWidgetFactory factory;
private final ICustomWidgetFactory editorCustomWidgetFactory;
private final JTable table;
private final ITableDataModel dataModel;
private final ITableColumnModelSpi columnModel;
private final CellRenderer cellRenderer;
private final EditorFactoryBasedCellEditor cellEditor;
private final TableCellRenderer headerRenderer;
private final PopupDetectionObservable popupDetectionObservable;
private final TableCellObservable tableCellObservable;
private final TableCellPopupDetectionObservable tableCellPopupDetectionObservable;
private final TableColumnPopupDetectionObservable tableColumnPopupDetectionObservable;
private final TableColumnObservable tableColumnObservable;
private final TableSelectionObservable tableSelectionObservable;
private final FocusObservable focusObservable;
private final KeyObservable keyObservable;
private final KeyListener keyListener;
private final TableColumnResizeListener tableColumnResizeListener;
private final TableSelectionListener tableSelectionListener;
private final TableModelListener tableModelListener;
private final TableColumnModelListener tableColumnModelListener;
private final TableCellMenuDetectListener tableCellMenuDetectListener;
private final TableColumnListener tableColumnListener;
private final TableColumnMoveListener tableColumnMoveListener;
private final AutoFocusMouseListener autoFocusMouseListener;
private final boolean columnsResizeable;
private final boolean hasBorder;
private SwingTableModel swingTableModel;
private ArrayList<Integer> lastColumnPermutation;
private boolean columnMoveOccured;
private boolean setWidthInvokedOnModel;
private boolean editable;
private boolean programmaticClearSelection;
public TableImpl(final IGenericWidgetFactory factory, final ITableSetupSpi setup) {
super(new JScrollPane(new JTable()));
this.factory = factory;
this.editorCustomWidgetFactory = new EditorCustomWidgetFactory();
this.editable = true;
this.cellRenderer = new CellRenderer();
if (setup.getEditor() != null) {
this.cellEditor = new EditorFactoryBasedCellEditor(setup.getEditor());
}
else {
this.cellEditor = null;
}
this.popupDetectionObservable = new PopupDetectionObservable();
this.tableCellObservable = new TableCellObservable();
this.tableCellPopupDetectionObservable = new TableCellPopupDetectionObservable();
this.tableColumnPopupDetectionObservable = new TableColumnPopupDetectionObservable();
this.tableColumnObservable = new TableColumnObservable();
this.tableSelectionObservable = new TableSelectionObservable();
this.focusObservable = new FocusObservable();
this.tableColumnResizeListener = new TableColumnResizeListener();
this.tableSelectionListener = new TableSelectionListener();
this.tableModelListener = new TableModelListener();
this.tableColumnModelListener = new TableColumnModelListener();
this.tableCellMenuDetectListener = new TableCellMenuDetectListener();
this.tableColumnListener = new TableColumnListener();
this.tableColumnMoveListener = new TableColumnMoveListener();
this.autoFocusMouseListener = new AutoFocusMouseListener();
this.columnMoveOccured = false;
this.setWidthInvokedOnModel = false;
this.dataModel = setup.getDataModel();
this.columnModel = setup.getColumnModel();
this.columnsResizeable = setup.getColumnsResizeable();
this.hasBorder = setup.hasBorder();
if (!hasBorder) {
getUiReference().setBorder(BorderFactory.createEmptyBorder());
}
this.table = (JTable) getUiReference().getViewport().getView();
table.setAutoCreateColumnsFromModel(false);
this.headerRenderer = new TableHeaderRenderer();
table.setBorder(BorderFactory.createEmptyBorder());
table.setGridColor(new Color(230, 230, 230));
table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
table.getTableHeader().setReorderingAllowed(setup.getColumnsMoveable());
if (setup.getSelectionPolicy() == TableSelectionPolicy.SINGLE_ROW_SELECTION) {
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
}
else if (setup.getSelectionPolicy() == TableSelectionPolicy.MULTI_ROW_SELECTION) {
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
}
else if (setup.getSelectionPolicy() == TableSelectionPolicy.NO_SELECTION) {
table.setRowSelectionAllowed(false);
table.setColumnSelectionAllowed(false);
}
else {
throw new IllegalArgumentException("SelectionPolicy '" + setup.getSelectionPolicy() + "' is not known");
}
if (!setup.isHeaderVisible()) {
table.getTableHeader().setVisible(false);
table.getTableHeader().setPreferredSize(new java.awt.Dimension(-1, 0));
}
table.getTableHeader().setDefaultRenderer(headerRenderer);
table.getTableHeader().addMouseListener(tableColumnListener);
table.getTableHeader().addMouseListener(new TableColumnMenuDetectListener());
table.getColumnModel().addColumnModelListener(tableColumnMoveListener);
table.getSelectionModel().addListSelectionListener(tableSelectionListener);
table.addMouseListener(new TableCellListener());
table.addMouseListener(tableCellMenuDetectListener);
table.addFocusListener(new FocusListener() {
@Override
public void focusLost(final FocusEvent e) {
focusObservable.focusLost();
}
@Override
public void focusGained(final FocusEvent e) {
focusObservable.focusGained();
}
});
this.keyListener = new KeyAdapter() {
@Override
public void keyReleased(final KeyEvent e) {
keyObservable.fireKeyReleased(new LazyKeyEventContentFactory(e));
}
@Override
public void keyPressed(final KeyEvent e) {
keyObservable.fireKeyPressed(new LazyKeyEventContentFactory(e));
}
};
final IObservableCallback keyObservableCallback = new IObservableCallback() {
@Override
public void onLastUnregistered() {
table.removeKeyListener(keyListener);
table.getTableHeader().removeKeyListener(keyListener);
getUiReference().getViewport().removeKeyListener(keyListener);
getUiReference().getHorizontalScrollBar().removeKeyListener(keyListener);
getUiReference().getVerticalScrollBar().removeKeyListener(keyListener);
}
@Override
public void onFirstRegistered() {
table.addKeyListener(keyListener);
table.getTableHeader().addKeyListener(keyListener);
getUiReference().getViewport().addKeyListener(keyListener);
getUiReference().getHorizontalScrollBar().addKeyListener(keyListener);
getUiReference().getVerticalScrollBar().addKeyListener(keyListener);
}
};
this.keyObservable = new KeyObservable(keyObservableCallback);
if (!SwingOptions.isDefaultTableTransferHandler()) {
table.setTransferHandler(null);
}
table.setSurrendersFocusOnKeystroke(true);
getUiReference().addMouseListener(tableCellMenuDetectListener);
getUiReference().setBorder(BorderFactory.createEmptyBorder());
getUiReference().setViewportBorder(BorderFactory.createEmptyBorder());
//disable default table key actions during editing
final ActionMap actionMap = table.getActionMap();
for (final Object actionKey : actionMap.allKeys()) {
Action originalAction = actionMap.get(actionMap);
if (originalAction == null) {
originalAction = actionMap.getParent().get(actionKey);
}
final Action originalActionFinal = originalAction;
if (originalActionFinal != null) {
actionMap.put(actionKey, new SwingActionWrapper(originalAction) {
private static final long serialVersionUID = -5138227823410592157L;
@Override
public boolean isEnabled() {
if (isEditing()) {
return false;
}
else {
return originalActionFinal.isEnabled();
}
}
});
}
}
final KeyStroke enterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
table.getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(enterKey, "Action.enter");
table.getActionMap().put("Action.enter", new AbstractAction() {
private static final long serialVersionUID = -6159891640633692648L;
@Override
public void actionPerformed(final ActionEvent evt) {
final int selectedRow = table.getSelectedRow();
final int selectedColumn = table.getSelectedColumn();
editCell(selectedRow, table.convertColumnIndexToModel(selectedColumn));
}
});
if (SwingOptions.isTableAutoFocusOnMouseInteraction()) {
table.getTableHeader().addMouseListener(autoFocusMouseListener);
getUiReference().addMouseListener(autoFocusMouseListener);
getUiReference().getHorizontalScrollBar().addMouseListener(autoFocusMouseListener);
getUiReference().getVerticalScrollBar().addMouseListener(autoFocusMouseListener);
}
}
@Override
public JScrollPane getUiReference() {
return (JScrollPane) super.getUiReference();
}
@Override
public boolean editCell(final int row, final int column) {
if (isEditing()) {
stopEditing();
}
final int viewColumnIndex = table.convertColumnIndexToView(column);
final StartEditEvent event = new StartEditEvent(table, row, viewColumnIndex);
return table.editCellAt(row, viewColumnIndex, event);
}
@Override
public void stopEditing() {
if (cellEditor != null) {
cellEditor.stopCellEditing();
table.editingStopped(null);
}
}
@Override
public void cancelEditing() {
if (cellEditor != null) {
cellEditor.cancelCellEditing();
table.editingCanceled(null);
}
}
@Override
public boolean isEditing() {
if (cellEditor != null) {
return cellEditor.isEditing();
}
else {
return false;
}
}
@Override
public void setRowHeight(final int height) {
table.setRowHeight(height);
}
@Override
public void setEditable(final boolean editable) {
this.editable = editable;
}
@Override
public void setEnabled(final boolean enabled) {
super.setEnabled(enabled);
table.setEnabled(enabled);
}
@Override
public Dimension getPreferredSize() {
final java.awt.Dimension preferredSize = table.getPreferredSize();
final java.awt.Dimension headerSize = table.getTableHeader().getPreferredSize();
final Insets insets = table.getBorder().getBorderInsets(table);
int width = preferredSize.width + insets.left + insets.right;
int height = preferredSize.height + insets.top + insets.bottom + headerSize.height;
final Insets spInsets = getUiReference().getInsets();
width = width + spInsets.left + spInsets.right;
height = height + spInsets.bottom + spInsets.top;
return new Dimension(width, height);
}
@Override
public Dimension getMinSize() {
final java.awt.Dimension minSize = getUiReference().getMinimumSize();
final java.awt.Dimension headerSize = table.getTableHeader().getMinimumSize();
final Insets insets = table.getBorder().getBorderInsets(table);
int width = minSize.width + insets.left + insets.right;
int height = minSize.height + insets.top + insets.bottom + (int) headerSize.getHeight();
final Insets spInsets = getUiReference().getInsets();
width = width + spInsets.left + spInsets.right + 17;
height = height + spInsets.bottom + spInsets.top + 17;
return new Dimension(width, height);
}
@Override
public void addPopupDetectionListener(final IPopupDetectionListener listener) {
popupDetectionObservable.addPopupDetectionListener(listener);
}
@Override
public void removePopupDetectionListener(final IPopupDetectionListener listener) {
popupDetectionObservable.removePopupDetectionListener(listener);
}
@Override
public void resetFromModel() {
//unregister listeners
final ITableDataModelObservable dataModelObservable = dataModel.getTableDataModelObservable();
if (dataModelObservable != null) {
dataModelObservable.removeDataModelListener(tableModelListener);
}
final ITableColumnModelObservable columnModelObservable = columnModel.getTableColumnModelObservable();
if (columnModelObservable != null) {
columnModelObservable.removeColumnModelListener(tableColumnModelListener);
}
table.getSelectionModel().removeListSelectionListener(tableSelectionListener);
//remove all old columns
final TableColumnModel swingColumnModel = table.getColumnModel();
final int oldColumnCount = swingColumnModel.getColumnCount();
for (int columnIndex = 0; columnIndex < oldColumnCount; columnIndex++) {
swingColumnModel.removeColumn(swingColumnModel.getColumn(0));
}
//set a new model
this.swingTableModel = new SwingTableModel();
table.setModel(swingTableModel);
//add all new columns
final int columnCount = columnModel.getColumnCount();
lastColumnPermutation = new ArrayList<Integer>(columnCount);
for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) {
table.getColumnModel().addColumn(createColumn(columnIndex));
lastColumnPermutation.add(Integer.valueOf(columnIndex));
}
//set the current selection
setSelection(dataModel.getSelection());
//register listeners
if (dataModelObservable != null) {
dataModelObservable.addDataModelListener(tableModelListener);
}
if (columnModelObservable != null) {
columnModelObservable.addColumnModelListener(tableColumnModelListener);
}
table.getSelectionModel().addListSelectionListener(tableSelectionListener);
//make changes appear in table view
table.repaint();
}
private TableColumn createColumn(final int modelIndex) {
final TableColumn column = new TableColumn();
column.setHeaderValue(columnModel.getColumn(modelIndex));
column.setCellRenderer(cellRenderer);
column.setCellEditor(cellEditor);
column.setResizable(columnsResizeable);
column.addPropertyChangeListener(tableColumnResizeListener);
column.setPreferredWidth(columnModel.getColumn(modelIndex).getWidth());
column.setModelIndex(modelIndex);
return column;
}
@Override
public Position getCellPosition(final int rowIndex, final int columnIndex) {
final Rectangle cellRect = table.getCellRect(rowIndex, columnIndex, true);
return PositionConvert.convert(SwingUtilities.convertPoint(table, cellRect.getLocation(), getUiReference()));
}
@Override
public Dimension getCellSize(final int rowIndex, final int columnIndex) {
final Rectangle cellRect = table.getCellRect(rowIndex, columnIndex, true);
return new Dimension(cellRect.width, cellRect.height);
}
@Override
public int getColumnAtPosition(final Position position) {
final int result = table.columnAtPoint(new Point(position.getX(), position.getY()));
if (result != -1) {
return table.convertColumnIndexToModel(result);
}
else {
return -1;
}
}
@Override
public int getRowAtPosition(final Position position) {
final int result = table.rowAtPoint(new Point(position.getX(), position.getY()));
if (result != -1) {
return table.convertRowIndexToModel(result);
}
else {
return -1;
}
}
@Override
public void pack(final TablePackPolicy policy) {
for (int columnIndex = 0; columnIndex < table.getColumnCount(); columnIndex++) {
pack(columnIndex, getRowRange(policy), policy);
}
}
@Override
public void pack(final int columnIndex, final TablePackPolicy policy) {
pack(columnIndex, getRowRange(policy), policy);
}
private void pack(final int columnIndex, final RowRange rowRange, final TablePackPolicy policy) {
final TableColumn column = table.getColumnModel().getColumn(table.convertColumnIndexToView(columnIndex));
int maxWidth = 0;
if (policy.considerData()) {
final int viewColumn = table.convertColumnIndexToView(columnIndex);
for (int rowIndex = rowRange.getStartIndex(); rowIndex <= rowRange.getEndIndex(); rowIndex++) {
final Object value = table.getValueAt(rowIndex, viewColumn);
final TableCellRenderer renderer = column.getCellRenderer();
final Component comp = renderer.getTableCellRendererComponent(table, value, false, false, rowIndex, columnIndex);
maxWidth = Math.max(maxWidth, (int) comp.getPreferredSize().getWidth());
}
}
if (policy.considerHeader()) {
final Object value = column.getHeaderValue();
final TableCellRenderer renderer = table.getTableHeader().getDefaultRenderer();
final Component comp = renderer.getTableCellRendererComponent(table, value, false, false, 0, columnIndex);
maxWidth = Math.max(maxWidth, (int) comp.getPreferredSize().getWidth());
}
maxWidth = maxWidth + 5;
column.setPreferredWidth(maxWidth);
setWidthInvokedOnModel = true;
columnModel.getColumn(column.getModelIndex()).setWidth(maxWidth);
setWidthInvokedOnModel = false;
tableColumnObservable.fireColumnResized(new TableColumnResizeEvent(column.getModelIndex(), maxWidth));
}
private RowRange getRowRange(final TablePackPolicy policy) {
if (policy.considerAllData()) {
return new RowRange(0, table.getRowCount() - 1);
}
else {
final Rectangle viewRect = getUiReference().getViewport().getViewRect();
final int firstVisibleRowIndex = Math.max(table.rowAtPoint(new Point(0, viewRect.y)), 0);
int lastVisibleRowIndex = table.rowAtPoint(new Point(0, viewRect.y + viewRect.height - 1));
if (lastVisibleRowIndex == -1) {
lastVisibleRowIndex = table.getRowCount() - 1;
}
return new RowRange(firstVisibleRowIndex, lastVisibleRowIndex);
}
}
@Override
public ArrayList<Integer> getColumnPermutation() {
final ArrayList<Integer> result = new ArrayList<Integer>();
final TableColumnModel swingColumnModel = table.getColumnModel();
for (int i = 0; i < swingColumnModel.getColumnCount(); i++) {
result.add(Integer.valueOf(swingColumnModel.getColumn(i).getModelIndex()));
}
return result;
}
@Override
public void setColumnPermutation(final List<Integer> permutation) {
Assert.paramNotNull(permutation, "permutation");
table.getColumnModel().removeColumnModelListener(tableColumnMoveListener);
int columnIndex = 0;
for (final Integer permutatedIndex : permutation) {
table.getColumnModel().moveColumn(table.convertColumnIndexToView(permutatedIndex.intValue()), columnIndex);
columnIndex++;
}
table.getTableHeader().repaint();
final ArrayList<Integer> newPermutation = getColumnPermutation();
if (!newPermutation.equals(lastColumnPermutation)) {
lastColumnPermutation = newPermutation;
tableColumnObservable.fireColumnPermutationChanged();
}
table.getColumnModel().addColumnModelListener(tableColumnMoveListener);
}
@Override
public ArrayList<Integer> getSelection() {
final ArrayList<Integer> result = new ArrayList<Integer>();
final int[] selectedRows = table.getSelectedRows();
for (int i = 0; i < selectedRows.length; i++) {
result.add(Integer.valueOf(selectedRows[i]));
}
return result;
}
@Override
public void setSelection(final List<Integer> selection) {
if (!getSelection().equals(selection)) {
final ListSelectionModel selectionModel = table.getSelectionModel();
if (selection != null && selection.size() > 0) {
programmaticClearSelection = true;
selectionModel.clearSelection();
programmaticClearSelection = false;
for (int i = 0; i < selection.size(); i++) {
if (i == selection.size() - 1) {
selectionModel.setValueIsAdjusting(false);
}
final int selectionIndex = selection.get(i);
selectionModel.addSelectionInterval(selectionIndex, selectionIndex);
}
}
else {
selectionModel.clearSelection();
}
}
}
private void scrollToCell(final int rowIndex, final int columnIndex) {
Assert.paramInBounds(table.getRowCount() - 1, rowIndex, "rowIndex");
Assert.paramInBounds(table.getColumnCount() - 1, columnIndex, "columnIndex");
final Rectangle rectangle = table.getCellRect(rowIndex, columnIndex, false);
final Rectangle visibleRectangle = table.getVisibleRect();
if (!visibleRectangle.contains(rectangle)) {
table.scrollRectToVisible(rectangle);
}
}
@Override
public void scrollToRow(final int rowIndex) {
Assert.paramInBounds(table.getRowCount() - 1, rowIndex, "rowIndex");
final Rectangle rectangle = table.getCellRect(rowIndex, 0, false);
final Rectangle visibleRectangle = table.getVisibleRect();
if (!visibleRectangle.contains(rectangle)) {
table.scrollRectToVisible(rectangle);
}
}
@Override
public boolean isColumnPopupDetectionSupported() {
return true;
}
@Override
public Interval<Integer> getVisibleRows() {
if (dataModel.getRowCount() > 0) {
final Rectangle visibleRect = table.getVisibleRect();
if (visibleRect.height >= table.getRowHeight() - 1) {
final Point firstPoint = new Point(0, visibleRect.y);
final Point secondPoint = new Point(0, visibleRect.y + visibleRect.height - 1);
final int leftBoundary = table.rowAtPoint(firstPoint);
final int rightBoundary = table.rowAtPoint(secondPoint);
return new Interval<Integer>(leftBoundary, rightBoundary);
}
}
return new Interval<Integer>(null, null);
}
@Override
public void addKeyListener(final IKeyListener listener) {
super.addKeyListener(listener);
keyObservable.addKeyListener(listener);
}
@Override
public void removeKeyListener(final IKeyListener listener) {
super.removeKeyListener(listener);
keyObservable.removeKeyListener(listener);
}
@Override
public void addFocusListener(final IFocusListener listener) {
super.addFocusListener(listener);
focusObservable.addFocusListener(listener);
}
@Override
public void removeFocusListener(final IFocusListener listener) {
super.removeFocusListener(listener);
focusObservable.removeFocusListener(listener);
}
@Override
public void addTableCellListener(final ITableCellListener listener) {
tableCellObservable.addTableCellListener(listener);
}
@Override
public void removeTableCellListener(final ITableCellListener listener) {
tableCellObservable.removeTableCellListener(listener);
}
@Override
public void addTableCellPopupDetectionListener(final ITableCellPopupDetectionListener listener) {
tableCellPopupDetectionObservable.addTableCellPopupDetectionListener(listener);
}
@Override
public void removeTableCellPopupDetectionListener(final ITableCellPopupDetectionListener listener) {
tableCellPopupDetectionObservable.removeTableCellPopupDetectionListener(listener);
}
@Override
public void addTableColumnPopupDetectionListener(final ITableColumnPopupDetectionListener listener) {
tableColumnPopupDetectionObservable.addTableColumnPopupDetectionListener(listener);
}
@Override
public void removeTableColumnPopupDetectionListener(final ITableColumnPopupDetectionListener listener) {
tableColumnPopupDetectionObservable.addTableColumnPopupDetectionListener(listener);
}
@Override
public void addTableColumnListener(final ITableColumnListener listener) {
tableColumnObservable.addTableColumnListener(listener);
}
@Override
public void removeTableColumnListener(final ITableColumnListener listener) {
tableColumnObservable.removeTableColumnListener(listener);
}
@Override
public void addTableSelectionListener(final ITableSelectionListener listener) {
tableSelectionObservable.addTableSelectionListener(listener);
}
@Override
public void removeTableSelectionListener(final ITableSelectionListener listener) {
tableSelectionObservable.removeTableSelectionListener(listener);
}
private CellIndices getCellIndices(final MouseEvent event) {
return getCellIndices(new Point(event.getX(), event.getY()));
}
private CellIndices getCellIndices(final Point point) {
final int columnIndex = table.columnAtPoint(point);
final int rowIndex = table.rowAtPoint(point);
if (columnIndex != -1 && rowIndex != -1) {
return new CellIndices(table.convertRowIndexToModel(rowIndex), table.convertColumnIndexToModel(columnIndex));
}
return null;
}
final class TableCellListener extends MouseAdapter {
@Override
public void mouseClicked(final MouseEvent e) {
final ITableCellMouseEvent mouseEvent = getMouseEvent(e, 2);
if (mouseEvent != null) {
tableCellObservable.fireMouseDoubleClicked(mouseEvent);
}
}
@Override
public void mousePressed(final MouseEvent e) {
final ITableCellMouseEvent mouseEvent = getMouseEvent(e, 1);
if (mouseEvent != null) {
tableCellObservable.fireMousePressed(mouseEvent);
}
}
@Override
public void mouseReleased(final MouseEvent e) {
final ITableCellMouseEvent mouseEvent = getMouseEvent(e, 1);
if (mouseEvent != null) {
tableCellObservable.fireMouseReleased(mouseEvent);
}
}
private ITableCellMouseEvent getMouseEvent(final MouseEvent event, final int clickCount) {
if (event.getClickCount() != clickCount) {
return null;
}
final MouseButton mouseButton = MouseUtil.getMouseButton(event);
if (mouseButton == null) {
return null;
}
final CellIndices indices = getCellIndices(event);
if (indices != null) {
return new TableCellMouseEvent(
indices.getRowIndex(),
indices.getColumnIndex(),
mouseButton,
MouseUtil.getModifier(event));
}
return null;
}
}
final class TableCellMenuDetectListener extends MouseAdapter {
@Override
public void mouseReleased(final MouseEvent e) {
fireMenuDetect(e);
}
@Override
public void mousePressed(final MouseEvent e) {
fireMenuDetect(e);
}
private void fireMenuDetect(final MouseEvent e) {
if (e.isPopupTrigger()) {
stopEditing();
final Point point = new Point(e.getX(), e.getY());
final Point popupPosition = new Point(e.getLocationOnScreen());
SwingUtilities.convertPointFromScreen(popupPosition, getUiReference());
final Position position = PositionConvert.convert(popupPosition);
final CellIndices indices = getCellIndices(point);
if (indices != null) {
final int rowIndex = indices.getRowIndex();
final int colIndex = indices.getColumnIndex();
//add default windows selection behavior
if (!table.getSelectionModel().isSelectedIndex(rowIndex) && !e.isControlDown()) {
table.getSelectionModel().setSelectionInterval(rowIndex, rowIndex);
}
tableCellPopupDetectionObservable.firePopupDetected(new TableCellPopupEvent(rowIndex, colIndex, position));
}
else {
popupDetectionObservable.firePopupDetected(position);
}
}
}
}
final class TableColumnListener extends MouseAdapter {
@Override
public void mouseReleased(final MouseEvent e) {
if (columnMoveOccured && SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
columnMoveOccured = false;
final ArrayList<Integer> currentPermutation = getColumnPermutation();
if (!currentPermutation.equals(lastColumnPermutation)) {
lastColumnPermutation = currentPermutation;
tableColumnObservable.fireColumnPermutationChanged();
}
}
}
@Override
public void mouseClicked(final MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 1) {
int columnIndex = table.getTableHeader().getColumnModel().getColumnIndexAtX(e.getX());
columnIndex = table.convertColumnIndexToModel(columnIndex);
final ITableColumnMouseEvent mouseEvent = new TableColumnMouseEvent(columnIndex, MouseUtil.getModifier(e));
tableColumnObservable.fireMouseClicked(mouseEvent);
}
}
}
final class AutoFocusMouseListener extends MouseAdapter {
@Override
public void mousePressed(final MouseEvent e) {
ensureTableFocus();
}
private void ensureTableFocus() {
if (!hasTableOrDescendandFocus()) {
table.requestFocus();
}
}
private boolean hasTableOrDescendandFocus() {
final Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
if (focusOwner == null || !SwingUtilities.isDescendingFrom(focusOwner, getUiReference())) {
return false;
}
else {
return true;
}
}
}
final class TableColumnMoveListener extends TableColumnModelAdapter {
@Override
public void columnMoved(final TableColumnModelEvent e) {
if (e.getFromIndex() != e.getToIndex()) {
columnMoveOccured = true;
}
}
}
final class TableColumnMenuDetectListener extends MouseAdapter {
@Override
public void mouseReleased(final MouseEvent e) {
fireMenuDetect(e);
}
@Override
public void mousePressed(final MouseEvent e) {
fireMenuDetect(e);
}
private void fireMenuDetect(final MouseEvent e) {
if (e.isPopupTrigger()) {
final Point popupPosition = new Point(e.getLocationOnScreen());
SwingUtilities.convertPointFromScreen(popupPosition, getUiReference());
final Position position = PositionConvert.convert(popupPosition);
int columnIndex = table.getTableHeader().getColumnModel().getColumnIndexAtX(e.getX());
columnIndex = table.convertColumnIndexToModel(columnIndex);
if (columnIndex != -1) {
tableColumnPopupDetectionObservable.firePopupDetected(new TableColumnPopupEvent(columnIndex, position));
}
else {
popupDetectionObservable.firePopupDetected(position);
}
}
}
}
final class TableColumnResizeListener implements PropertyChangeListener {
@Override
public void propertyChange(final PropertyChangeEvent evt) {
if ("width".equals(evt.getPropertyName())) {
final TableColumn column = (TableColumn) evt.getSource();
if (column != null) {
final Object newObject = evt.getNewValue();
if (newObject instanceof Integer) {
final int newWidth = ((Integer) newObject).intValue();
setWidthInvokedOnModel = true;
columnModel.getColumn(column.getModelIndex()).setWidth(newWidth);
setWidthInvokedOnModel = false;
tableColumnObservable.fireColumnResized(new TableColumnResizeEvent(column.getModelIndex(), newWidth));
}
}
}
}
}
final class TableSelectionListener implements ListSelectionListener {
@Override
public void valueChanged(final ListSelectionEvent e) {
if (!e.getValueIsAdjusting() && !programmaticClearSelection) {
tableSelectionObservable.fireSelectionChanged();
}
}
}
final class TableModelListener implements ITableDataModelListener {
@Override
public void rowsAdded(final int[] rowIndices) {
if (swingTableModel != null) {
swingTableModel.rowsAdded(rowIndices);
}
}
@Override
public void rowsRemoved(final int[] rowIndices) {
if (swingTableModel != null) {
swingTableModel.rowsRemoved(rowIndices);
}
}
@Override
public void rowsChanged(final int[] rowIndices) {
if (swingTableModel != null) {
swingTableModel.rowsChanged(rowIndices);
}
}
@Override
public void dataChanged() {
if (swingTableModel != null) {
swingTableModel.dataChanged();
}
}
@Override
public void selectionChanged() {
setSelection(dataModel.getSelection());
}
}
final class TableColumnModelListener implements ITableColumnModelListener {
@Override
public void columnsAdded(final int[] columnIndices) {
//Trivial case. If nothing added return
if (columnIndices.length == 0) {
return;
}
//Sort the indices to add
Arrays.sort(columnIndices);
//Merge the new columns into the current columns
int addedColumns = 0;
int nextIndexToAdd = columnIndices[0];
final TableColumn[] columns = getColumnsSortedByModelIndex();
for (int columnIndex = 0; columnIndex < columns.length; columnIndex++) {
if (columnIndex == nextIndexToAdd) {
final TableColumn column = createColumn(columnIndex);
addedColumns++;
table.addColumn(column);
table.moveColumn(table.getColumnCount() - 1, columnIndex);
if (addedColumns < columnIndices.length) {
nextIndexToAdd = columnIndices[addedColumns];
}
else {
nextIndexToAdd = -1;
}
}
//Fix the index in the model. For each previously added row, the
//index must be increased by one.
final TableColumn swingColumn = columns[columnIndex];
swingColumn.setModelIndex(swingColumn.getModelIndex() + addedColumns);
}
//Then append the residual columns
while (nextIndexToAdd != -1) {
final TableColumn column = createColumn(nextIndexToAdd);
addedColumns++;
table.addColumn(column);
if (addedColumns < columnIndices.length) {
nextIndexToAdd = columnIndices[addedColumns];
}
else {
nextIndexToAdd = -1;
}
}
//Make changes appear on table
table.getTableHeader().repaint();
}
@Override
public void columnsRemoved(final int[] columnIndices) {
if (columnIndices.length == 0) {
return;
}
//first sort the columns by model index
final TableColumn[] columns = getColumnsSortedByModelIndex();
//fix the model indices and determine the columns to remove
Arrays.sort(columnIndices);
final Set<TableColumn> columnsToRemove = new HashSet<TableColumn>();
int removedColumns = 0;
int nextIndexToRemove = columnIndices[0];
for (int columnIndex = 0; columnIndex < columns.length; columnIndex++) {
final TableColumn swingColumn = columns[columnIndex];
if (columnIndex == nextIndexToRemove) {
columnsToRemove.add(columns[columnIndex]);
swingColumn.removePropertyChangeListener(tableColumnResizeListener);
removedColumns++;
if (removedColumns < columnIndices.length) {
nextIndexToRemove = columnIndices[removedColumns];
}
}
else {
//fix the index in the model, for each previously removed row, the
//index must be decreased by one
swingColumn.setModelIndex(swingColumn.getModelIndex() - removedColumns);
}
}
//now remove the columns from the model
for (final TableColumn column : columnsToRemove) {
table.removeColumn(column);
}
}
@Override
public void columnsChanged(final int[] columnIndices) {
if (!setWidthInvokedOnModel) {
for (int i = 0; i < columnIndices.length; i++) {
final int columnIndex = columnIndices[i];
final int modelIndex = table.convertColumnIndexToView(columnIndex);
final int viewWidth = table.getColumnModel().getColumn(modelIndex).getPreferredWidth();
final int modelWidth = columnModel.getColumn(columnIndex).getWidth();
if (viewWidth != modelWidth) {
table.getColumnModel().getColumn(modelIndex).setPreferredWidth(modelWidth);
}
}
table.getTableHeader().repaint();
}
}
private TableColumn[] getColumnsSortedByModelIndex() {
final TableColumn[] columns = new TableColumn[table.getColumnCount()];
for (int columnIndex = 0; columnIndex < table.getColumnCount(); columnIndex++) {
final int modelIndex = table.convertColumnIndexToModel(columnIndex);
columns[modelIndex] = table.getColumnModel().getColumn(columnIndex);
}
return columns;
}
}
class SwingTableModel extends AbstractTableModel {
private static final long serialVersionUID = 6096723765272552285L;
SwingTableModel() {
super();
}
@Override
public String getColumnName(final int column) {
return columnModel.getColumn(column).getText();
}
@Override
public Object getValueAt(final int rowIndex, final int columnIndex) {
return dataModel.getCell(rowIndex, columnIndex);
}
@Override
public int getRowCount() {
return dataModel.getRowCount();
}
@Override
public int getColumnCount() {
return table.getColumnModel().getColumnCount();
}
@Override
public boolean isCellEditable(final int rowIndex, final int columnIndex) {
return dataModel.getCell(rowIndex, columnIndex).isEditable();
}
void rowsAdded(final int[] rowIndices) {
if (rowIndices.length == 1) {
fireTableRowsInserted(rowIndices[0], rowIndices[0]);
}
else {
dataChanged();
}
}
void rowsRemoved(final int[] rowIndices) {
if (rowIndices.length == 1) {
fireTableRowsDeleted(rowIndices[0], rowIndices[0]);
}
else {
dataChanged();
}
}
void rowsChanged(final int[] rowIndices) {
fireTableRowsUpdated(ArrayUtils.getMin(rowIndices), ArrayUtils.getMax(rowIndices));
}
void dataChanged() {
table.getSelectionModel().removeListSelectionListener(tableSelectionListener);
fireTableDataChanged();
setSelection(dataModel.getSelection());
table.getSelectionModel().addListSelectionListener(tableSelectionListener);
}
}
class TableHeaderRenderer implements TableCellRenderer {
private final TableCellRenderer headerRenderer = table.getTableHeader().getDefaultRenderer();
@Override
public Component getTableCellRendererComponent(
final JTable table,
final Object value,
final boolean isSelected,
final boolean hasFocus,
final int row,
final int columnIndex) {
final JLabel defaultComponent = (JLabel) headerRenderer
.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, columnIndex);
final ITableColumnSpi column = (ITableColumnSpi) value;
if (column.getIcon() != null) {
defaultComponent.setIcon(SwingImageRegistry.getInstance().getImageIcon(column.getIcon()));
}
defaultComponent.setText(column.getText());
defaultComponent.setToolTipText(column.getToolTipText());
return defaultComponent;
}
};
class CellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1581242312833196056L;
@Override
public Component getTableCellRendererComponent(
final JTable table,
final Object value,
final boolean isSelected,
final boolean hasFocus,
final int row,
final int column) {
final ITableCell cell = (ITableCell) value;
final Component result = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (cell != null) {
final IImageConstant icon = cell.getIcon();
final IColorConstant backgroundColor = cell.getBackgroundColor();
final IColorConstant foregroundColor = cell.getForegroundColor();
final Markup markup = cell.getMarkup();
final AlignmentHorizontal alignment = columnModel.getColumn(table.convertColumnIndexToModel(column))
.getAlignment();
setText(cell.getText());
setToolTipText(cell.getToolTipText());
if (icon != null) {
setIcon(SwingImageRegistry.getInstance().getImageIcon(icon));
}
else {
setIcon(null);
}
if (markup != null) {
setFont(FontProvider.deriveFont(getFont(), markup));
}
else {
setFont(FontProvider.deriveFont(getFont(), Markup.DEFAULT));
}
if (alignment != null) {
setHorizontalAlignment(AlignmentConvert.convert(alignment));
}
else {
setHorizontalAlignment(JLabel.LEFT);
}
if (!isSelected) {
if (backgroundColor != null) {
setBackground(ColorConvert.convert(backgroundColor));
}
else {
setBackground(null);
}
if (foregroundColor != null) {
setForeground(ColorConvert.convert(foregroundColor));
}
else {
setForeground(null);
}
}
}
return result;
}
}
final class EditorFactoryBasedCellEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 3420610701760793714L;
private final ITableCellEditorFactory<? extends ITableCellEditor> editorFactory;
private ITableCellEditor tableCellEditor;
private int column;
private int row;
private long stopEditTimestamp;
EditorFactoryBasedCellEditor(final ITableCellEditorFactory<? extends ITableCellEditor> editorFactory) {
this.editorFactory = editorFactory;
}
public boolean isEditing() {
return tableCellEditor != null;
}
@Override
public Object getCellEditorValue() {
return null;
}
@Override
public boolean isCellEditable(final EventObject event) {
if (event == null) {
return true;
}
else if (event instanceof StartEditEvent) {
final StartEditEvent startEditEvent = (StartEditEvent) event;
final int rowIndex = startEditEvent.getRow();
final int columnIndex = table.convertColumnIndexToModel(startEditEvent.getColum());
final ITableCell cell = dataModel.getCell(rowIndex, columnIndex);
return cell.isEditable() && editable;
}
else if (event instanceof MouseEvent) {
final MouseEvent mouseEvent = (MouseEvent) event;
final CellIndices cellIndices = getCellIndices(mouseEvent);
final ITableCell cell = dataModel.getCell(cellIndices.getRowIndex(), cellIndices.getColumnIndex());
if (!cell.isEditable() || !editable) {
return false;
}
final EditActivation activation = editorFactory.getActivation(
cell,
cellIndices.getRowIndex(),
cellIndices.getColumnIndex(),
isEditing(),
stopEditTimestamp);
if (EditActivation.DOUBLE_CLICK.equals(activation)) {
return SwingUtilities.isLeftMouseButton(mouseEvent) && mouseEvent.getClickCount() == 2;
}
else if (EditActivation.SINGLE_CLICK.equals(activation)) {
return SwingUtilities.isLeftMouseButton(mouseEvent) && mouseEvent.getClickCount() == 1;
}
}
else if (event instanceof KeyEvent) {
if (isEditing() || dataModel.getRowCount() == 0) {
return false;
}
final KeyEvent keyEvent = (KeyEvent) event;
if ((keyEvent.getKeyCode() == KeyEvent.VK_ENTER)) {
return true;
}
}
return false;
}
@Override
public boolean shouldSelectCell(final EventObject anEvent) {
return true;
}
@Override
public boolean stopCellEditing() {
if (tableCellEditor != null && this.row != -1 && this.column != -1) {
tableCellEditor.stopEditing(dataModel.getCell(this.row, column), this.row, this.column);
//Workaround to avoid getting exceptions on comboxes that want to
//show their popup when navigated to another cell vio arrow up or down
((JComponent) tableCellEditor.getUiReference()).setEnabled(false);
}
this.tableCellEditor = null;
this.row = -1;
this.column = -1;
this.stopEditTimestamp = System.currentTimeMillis();
fireEditingStopped();
return true;
}
@Override
public void cancelCellEditing() {
if (tableCellEditor != null && row != -1 && column != -1) {
tableCellEditor.cancelEditing(dataModel.getCell(this.row, column), this.row, this.column);
}
this.tableCellEditor = null;
this.row = -1;
this.column = -1;
this.stopEditTimestamp = 0;
fireEditingCanceled();
}
@Override
public Component getTableCellEditorComponent(
final JTable table,
final Object value,
final boolean isSelected,
final int row,
final int viewColumn) {
if (isEditing()) {
stopCellEditing();
}
this.row = row;
this.column = table.convertColumnIndexToModel(viewColumn);
final ITableCell tableCell = dataModel.getCell(row, column);
this.tableCellEditor = editorFactory.create(tableCell, row, column, editorCustomWidgetFactory);
if (tableCellEditor == null) {
this.row = -1;
this.column = -1;
return null;
}
tableCellEditor.addKeyListener(new IKeyListener() {
@Override
public void keyReleased(final IKeyEvent event) {}
@Override
public void keyPressed(final IKeyEvent event) {
final boolean ctrl = event.getModifier().contains(Modifier.CTRL);
final boolean shift = event.getModifier().contains(Modifier.SHIFT);
final boolean enter = VirtualKey.ENTER.equals(event.getVirtualKey());
final boolean esc = VirtualKey.ESC.equals(event.getVirtualKey());
boolean left = VirtualKey.ARROW_LEFT.equals(event.getVirtualKey()) && shift;
left = left || (VirtualKey.TAB.equals(event.getVirtualKey()) && shift);
boolean right = VirtualKey.ARROW_RIGHT.equals(event.getVirtualKey()) && shift;
right = right || (VirtualKey.TAB.equals(event.getVirtualKey()) && !shift);
final boolean up = VirtualKey.ARROW_UP.equals(event.getVirtualKey()) && shift;
final boolean down = VirtualKey.ARROW_DOWN.equals(event.getVirtualKey()) && shift;
if (enter && ctrl) {
stopEditing();
}
if (enter) {
navigateDownLeft();
}
else if (esc) {
cancelEditing();
}
else if (right) {
navigateRight();
}
else if (left) {
navigateLeft();
}
else if (up) {
navigateUp();
}
else if (down) {
navigateDown();
}
}
});
tableCellEditor.startEditing(tableCell, row, column);
final int height = tableCellEditor.getPreferredSize().getHeight() + 1;
if (table.getRowHeight() < height) {
table.setRowHeight(height);
}
final JComponent component = (JComponent) tableCellEditor.getUiReference();
//ensure that cell editor get the focus when swing auto focus gives the focus to the
//component, but the component is a container that wraps other input controls
final ValueHolder<FocusListener> focusListenerHolder = new ValueHolder<FocusListener>();
final FocusAdapter focusListener = new FocusAdapter() {
@Override
public void focusGained(final FocusEvent e) {
component.removeFocusListener(focusListenerHolder.get());
if (tableCellEditor != null) {
tableCellEditor.requestFocus();
}
}
};
focusListenerHolder.set(focusListener);
component.addFocusListener(focusListener);
return component;
}
private boolean navigateRight() {
if (isEditing()) {
return navigateRight(row, row, convertColumnIndexToView(column));
}
else {
return false;
}
}
private boolean navigateDown() {
if (isEditing()) {
return navigateDown(row, row, convertColumnIndexToView(column));
}
else {
return false;
}
}
private boolean navigateLeft() {
if (isEditing()) {
return navigateLeft(row, row, convertColumnIndexToView(column));
}
else {
return false;
}
}
private boolean navigateUp() {
if (isEditing()) {
return navigateUp(row, row, convertColumnIndexToView(column));
}
else {
return false;
}
}
private boolean navigateDownLeft() {
if (isEditing()) {
return navigateDown(row, row, 0);
}
else {
return false;
}
}
private boolean navigateRight(final int startRow, final int row, final int viewColumnIndex) {
if (viewColumnIndex + 1 < columnModel.getColumnCount()) {
if (editCell(row, convertColumnIndexToModel(viewColumnIndex + 1))) {
scrollToCell(row, convertColumnIndexToModel(viewColumnIndex + 1));
setSelection(Collections.singletonList(Integer.valueOf(row)));
return true;
}
else {
return navigateRight(startRow, row, viewColumnIndex + 1);
}
}
else if (row - startRow < 2) {
return navigateDown(startRow, row, 0);
}
else {
return false;
}
}
private boolean navigateDown(final int startRow, final int row, final int viewColumnIndex) {
if (dataModel.getRowCount() > row + 1) {
if (editCell(row + 1, convertColumnIndexToModel(viewColumnIndex))) {
scrollToCell(row + 1, convertColumnIndexToModel(viewColumnIndex));
setSelection(Collections.singletonList(Integer.valueOf(row + 1)));
return true;
}
else if (row - startRow < 2) {
setSelection(Collections.singletonList(Integer.valueOf(row + 1)));
return navigateRight(startRow, row + 1, viewColumnIndex);
}
else {
return false;
}
}
else {
return false;
}
}
private boolean navigateLeft(final int startRow, final int row, final int viewColumnIndex) {
if (viewColumnIndex > 0) {
if (editCell(row, convertColumnIndexToModel(viewColumnIndex - 1))) {
scrollToCell(row, convertColumnIndexToModel(viewColumnIndex - 1));
setSelection(Collections.singletonList(Integer.valueOf(row)));
return true;
}
else {
return navigateLeft(startRow, row, viewColumnIndex - 1);
}
}
else if (startRow - row < 2) {
return navigateUp(startRow, row, columnModel.getColumnCount() - 1);
}
else {
return false;
}
}
private boolean navigateUp(final int startRow, final int row, final int viewColumnIndex) {
if (row > 0) {
if (editCell(row - 1, convertColumnIndexToModel(viewColumnIndex))) {
scrollToCell(row - 1, convertColumnIndexToModel(viewColumnIndex));
setSelection(Collections.singletonList(Integer.valueOf(row - 1)));
return true;
}
else if (startRow - row < 2) {
return navigateLeft(startRow, row - 1, viewColumnIndex);
}
else {
return false;
}
}
else {
return false;
}
}
private int convertColumnIndexToView(final int modelIndex) {
return table.convertColumnIndexToView(modelIndex);
}
private int convertColumnIndexToModel(final int viewIndex) {
return table.convertColumnIndexToModel(viewIndex);
}
}
private final class EditorCustomWidgetFactory implements ICustomWidgetFactory {
@Override
public <WIDGET_TYPE extends IControlCommon> WIDGET_TYPE create(
final IWidgetDescriptor<? extends WIDGET_TYPE> descriptor) {
return factory.create(table, descriptor);
}
}
private final class StartEditEvent extends EventObject {
private static final long serialVersionUID = -1143486782609023655L;
private final int row;
private final int colum;
private StartEditEvent(final Object source, final int row, final int colum) {
super(source);
this.row = row;
this.colum = colum;
}
private int getRow() {
return row;
}
private int getColum() {
return colum;
}
}
final class CellIndices {
private final int rowIndex;
private final int columnIndex;
CellIndices(final int rowIndex, final int columnIndex) {
super();
this.rowIndex = rowIndex;
this.columnIndex = columnIndex;
}
public int getRowIndex() {
return rowIndex;
}
public int getColumnIndex() {
return columnIndex;
}
}
final class RowRange {
private final int startIndex;
private final int endIndex;
RowRange(final int startIndex, final int endIndex) {
super();
this.startIndex = startIndex;
this.endIndex = endIndex;
}
public int getStartIndex() {
return startIndex;
}
public int getEndIndex() {
return endIndex;
}
}
}