fr.ird.observe.client.util.table.EditableTableModelSupport Maven / Gradle / Ivy
package fr.ird.observe.client.util.table;
/*
* #%L
* ObServe Toolkit :: Common Client
* %%
* Copyright (C) 2008 - 2017 IRD, Ultreia.io
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program. If not, see
* .
* #L%
*/
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jdesktop.swingx.autocomplete.ComboBoxCellEditor;
import org.nuiton.jaxx.runtime.swing.JTables;
import org.nuiton.jaxx.widgets.number.NumberCellEditor;
/**
* Created on 12/3/14.
*
* @author Tony Chemit - [email protected]
* @since 3.8
*/
public abstract class EditableTableModelSupport extends AbstractTableModel {
/** Logger. */
private static final Log log = LogFactory.getLog(EditableTableModelSupport.class);
public static final String EDITABLE_PROPERTY = "editable";
public static final String EMPTY_PROPERTY = "empty";
public static final String MODIFIED_PROPERTY = "modified";
public static final String VALID_PROPERTY = "valid";
public static final String SELECTED_ROW_INDEX_PROPERTY = "selectedRowIndex";
public static final String SELECTED_ROW_PROPERTY = "selectedRow";
public static final String SELECTION_EMPTY_PROPERTY = "selectionEmpty";
private static final long serialVersionUID = 1L;
/** pour la propagation des modifications d'états */
protected final PropertyChangeSupport pcs = new PropertyChangeSupport(this);
/** un drapeau pour savoir si le modèle est éditable */
protected boolean editable;
/** un drapeau pour savoir si le modèle est valide */
protected boolean valid;
/** un drapeau pour savoir si le modèle est modifié */
protected boolean modified;
/** la ligne sélectionnée et en cours d'édition. */
protected int selectedRowIndex = -1; /* -1 = pas de selection */
/** la liste des données du modèle */
protected final List data = new ArrayList<>();
private JTable table;
private transient ListSelectionModel selectionModel;
private final boolean canAddRow;
protected transient ListSelectionListener whenSelectionModelChanged;
protected EditableTableModelSupport(boolean canAddRow) {
this.canAddRow = canAddRow;
}
public boolean isCanAddRow() {
return canAddRow;
}
protected abstract D createNewRow();
public abstract boolean isRowNotEmpty(D valid);
protected abstract boolean isRowValid(D valid);
protected boolean isCanCreateNewRow(int rowIndex) {
boolean canCreateNewRow = canAddRow;
if (canAddRow) {
D row = getData(rowIndex);
canCreateNewRow = isRowNotEmpty(row) && isRowValid(row);
}
return canCreateNewRow;
}
public List getNotEmptyData() {
List result = new ArrayList<>();
for (D row : data) {
if (isRowNotEmpty(row)) {
result.add(row);
}
}
return result;
}
public List getData() {
return data;
}
public D getData(int rowIndex) {
return data.get(rowIndex);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
@Override
public int getRowCount() {
return data.size();
}
public void setData(List data) {
this.data.clear();
this.data.addAll(data);
if (this.data.isEmpty() && editable && canAddRow) {
// add an empty row (no fire and select: will be done just below)
addNewRow(0, false);
}
// reset previous selection
selectedRowIndex = -1;
fireTableDataChanged();
// on selectionne la première ligne (ou supprime la selection si plus de donnes)
setSelectedRowIndex(isEmpty() ? -1 : 0, true);
fireEmpty();
}
public void removeData(int selectedRow) {
this.data.remove(selectedRow);
fireTableRowsDeleted(selectedRow, selectedRow);
// on selectionne la ligne précédente (ou supprime la selection si plus de donnes)
setSelectedRowIndex(isEmpty() ? -1 : selectedRow - 1, true);
fireEmpty();
}
public void removeSelectedRow() {
// get selected row
int selectedRowIndex1 = getSelectedRowIndex();
// clear selection (to avoid any trouble while trying to access previous selected values which does not exists
// any long in data list)
setSelectedRowIndex(-1, true);
// safe remove data
removeData(selectedRowIndex1);
}
public void addNewRow() {
int row = getRowCount();
addNewRow(row, true);
}
public void insertBeforeSelectedRow() {
int currentRow = getSelectedRowIndex();
setSelectedRowIndex(-1); // clear selection
int insertRow = currentRow;
if (insertRow < 0) {
insertRow = 0;
}
if (log.isInfoEnabled()) {
log.info("Insert before selected row: " + currentRow + " :: " + insertRow);
}
addNewRow(insertRow, true);
}
public void insertAfterSelectedRow() {
int currentRow = getSelectedRowIndex();
setSelectedRowIndex(-1); // clear selection
int insertRow = currentRow == getRowCount() ? currentRow : currentRow + 1;
if (log.isInfoEnabled()) {
log.info("Insert after selected row: " + currentRow + " :: " + insertRow);
}
addNewRow(insertRow, true);
}
public void addNewRow(int row, boolean fireAndSelectNewRow) {
ensureEditable();
// on est autorise a ajouter une nouvelle entrée
D bean = createNewRow();
if (row == getRowCount()) {
// add new row
data.add(bean);
} else {
// insert new row (but not at the end)
data.add(row, bean);
}
if (fireAndSelectNewRow) {
fireTableRowsInserted(row, row);
fireEmpty();
// la nouvelle ligne est celle en cours d'edition
setSelectedRowIndex(row, true);
}
fireEmpty();
}
public void clear() {
setSelectedRowIndex(-1);
setData(Collections.emptyList());
validate();
setModified(false);
fireEmpty();
}
public boolean isEmpty() {
return getRowCount() == 0;
}
public boolean isSelectionEmpty() {
return getSelectedRowIndex() == -1;
}
public D getSelectedRow() {
return isSelectionEmpty() ? null : getData(getSelectedRowIndex());
}
public int getSelectedRowIndex() {
return selectedRowIndex;
}
public void setSelectedRowIndex(int selectedRowIndex) {
setSelectedRowIndex(selectedRowIndex, false);
}
private boolean selectionIsAdjusting;
public void setSelectedRowIndex(int selectedRowIndex, boolean pushToSelectionModel) {
if (!selectionIsAdjusting) {
selectionIsAdjusting = true;
try {
int oldSelectedRowIndex = getSelectedRowIndex();
//boolean oldSelectionEmpty = isSelectionEmpty();
D oldSelectedRow = getSelectedRow();
this.selectedRowIndex = selectedRowIndex;
if (pushToSelectionModel && getSelectionModel() != null) {
getSelectionModel().setSelectionInterval(selectedRowIndex, selectedRowIndex);
}
firePropertyChange(SELECTED_ROW_INDEX_PROPERTY, oldSelectedRowIndex, selectedRowIndex);
firePropertyChange(SELECTION_EMPTY_PROPERTY, null /* to force fire */, isSelectionEmpty());
firePropertyChange(SELECTED_ROW_PROPERTY, oldSelectedRow, getSelectedRow());
} finally {
selectionIsAdjusting = false;
}
}
}
public boolean isModified() {
return modified;
}
public void setModified(boolean modified) {
this.modified = modified;
firePropertyChange(MODIFIED_PROPERTY, null, modified);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
pcs.addPropertyChangeListener(listener);
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcs.addPropertyChangeListener(propertyName, listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
pcs.removePropertyChangeListener(listener);
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
pcs.removePropertyChangeListener(propertyName, listener);
}
public void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
pcs.firePropertyChange(propertyName, oldValue, newValue);
}
public void fireEmpty() {
firePropertyChange(EMPTY_PROPERTY, null, isEmpty());
}
protected void ensureEditable() throws IllegalStateException {
if (!editable) {
throw new IllegalStateException("can not edit this model since it is marked as none editable " + this);
}
}
public void setEditable(Boolean editable) {
this.editable = editable;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
firePropertyChange(VALID_PROPERTY, null, isValid());
}
public void validate() {
boolean newValidValue = computeValidState();
setValid(newValidValue);
}
protected boolean computeValidState() {
boolean newValidValue = true;
for (D row : data) {
boolean rowValid = !isRowNotEmpty(row) || isRowValid(row);
if (!rowValid) {
newValidValue = false;
break;
}
}
return newValidValue;
}
public void installSelectionListener(JTable table) {
// always try to uninstall it before to avoid any memory leak
uninstallSelectionListener(table);
this.table = table;
ListSelectionListener listener = getWhenSelectionModelChanged();
getSelectionModel().addListSelectionListener(listener);
}
public ListSelectionModel getSelectionModel() {
if (selectionModel == null) {
selectionModel = table == null ? null : table.getSelectionModel();
}
return selectionModel;
}
public void uninstallSelectionListener(JTable table) {
ListSelectionListener listener = getWhenSelectionModelChanged();
table.getSelectionModel().removeListSelectionListener(listener);
this.table = null;
this.selectionModel = null;
}
private static final String OBSERVE_KEY_ADAPTER = "ObServeKeyAdapter";
private static final String OBSERVE_FOCUS_ADAPTER = "ObServeFocusAdapter";
public void installTableFocusListener(final JTable table) {
// always try to uninstall it before to avoid any memory leak
uninstallTableFocusListener(table);
FocusAdapter focusAdapter = new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
JTables.stopEditing(table);
}
};
Enumeration columns = table.getColumnModel().getColumns();
while (columns.hasMoreElements()) {
TableColumn tableColumn = columns.nextElement();
TableCellEditor cellEditor = tableColumn.getCellEditor();
if (cellEditor instanceof NumberCellEditor) {
NumberCellEditor editor = (NumberCellEditor) cellEditor;
editor.getNumberEditor().getTextField().addFocusListener(focusAdapter);
} else if (cellEditor instanceof ComboBoxCellEditor) {
// ComboBoxCellEditor editor = (ComboBoxCellEditor) cellEditor;
//FIXME should also use the focus listener here
}
}
if (log.isDebugEnabled()) {
log.debug("Intall " + focusAdapter);
}
table.putClientProperty(OBSERVE_FOCUS_ADAPTER, focusAdapter);
}
public void uninstallTableFocusListener(final JTable table) {
FocusAdapter focusAdapter = (FocusAdapter) table.getClientProperty(OBSERVE_FOCUS_ADAPTER);
if (focusAdapter != null) {
if (log.isDebugEnabled()) {
log.debug("Desintall " + focusAdapter);
}
TableColumnModel columnModel = table.getColumnModel();
Enumeration columns = columnModel.getColumns();
while (columns.hasMoreElements()) {
TableColumn tableColumn = columns.nextElement();
TableCellEditor cellEditor = tableColumn.getCellEditor();
if (cellEditor instanceof NumberCellEditor) {
NumberCellEditor editor = (NumberCellEditor) cellEditor;
editor.getNumberEditor().getTextField().removeFocusListener(focusAdapter);
}
}
table.putClientProperty(OBSERVE_KEY_ADAPTER, null);
}
}
public void installTableKeyListener(final JTable table) {
// always try to uninstall it before to avoid any memory leak
uninstallTableKeyListener(table);
EditableTableModelSupport model = (EditableTableModelSupport) table.getModel();
final MoveToNextEditableCellAction nextCellAction = MoveToNextEditableCellAction.newAction(model, table);
final MoveToPreviousEditableCellAction previousCellAction = MoveToPreviousEditableCellAction.newAction(model, table);
final MoveToNextEditableRowAction nextRowAction = MoveToNextEditableRowAction.newAction(model, table);
final MoveToPreviousEditableRowAction previousRowAction = MoveToPreviousEditableRowAction.newAction(model, table);
KeyAdapter keyAdapter = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
TableCellEditor editor = table.getCellEditor();
int keyCode = e.getKeyCode();
if (keyCode == KeyEvent.VK_LEFT ||
(keyCode == KeyEvent.VK_TAB && e.isShiftDown())) {
e.consume();
if (editor != null) {
editor.stopCellEditing();
}
previousCellAction.actionPerformed(null);
} else if (keyCode == KeyEvent.VK_RIGHT ||
keyCode == KeyEvent.VK_TAB) {
e.consume();
if (editor != null) {
editor.stopCellEditing();
}
nextCellAction.actionPerformed(null);
} else if (keyCode == KeyEvent.VK_UP ||
(keyCode == KeyEvent.VK_ENTER && e.isShiftDown())) {
e.consume();
if (editor != null) {
editor.stopCellEditing();
}
previousRowAction.actionPerformed(null);
} else if (e.getKeyCode() == KeyEvent.VK_ENTER ||
keyCode == KeyEvent.VK_DOWN) {
e.consume();
if (editor != null) {
editor.stopCellEditing();
}
nextRowAction.actionPerformed(null);
}
}
};
Enumeration columns = table.getColumnModel().getColumns();
while (columns.hasMoreElements()) {
TableColumn tableColumn = columns.nextElement();
TableCellEditor cellEditor = tableColumn.getCellEditor();
if (cellEditor instanceof NumberCellEditor) {
NumberCellEditor editor = (NumberCellEditor) cellEditor;
editor.getNumberEditor().getTextField().addKeyListener(keyAdapter);
}
}
if (log.isDebugEnabled()) {
log.debug("Intall " + keyAdapter);
}
table.addKeyListener(keyAdapter);
table.putClientProperty(OBSERVE_KEY_ADAPTER, keyAdapter);
}
public void uninstallTableKeyListener(final JTable table) {
KeyAdapter keyAdapter = (KeyAdapter) table.getClientProperty(OBSERVE_KEY_ADAPTER);
if (keyAdapter != null) {
if (log.isDebugEnabled()) {
log.debug("Desintall " + keyAdapter);
}
table.removeKeyListener(keyAdapter);
TableColumnModel columnModel = table.getColumnModel();
Enumeration columns = columnModel.getColumns();
while (columns.hasMoreElements()) {
TableColumn tableColumn = columns.nextElement();
TableCellEditor cellEditor = tableColumn.getCellEditor();
if (cellEditor instanceof NumberCellEditor) {
NumberCellEditor editor = (NumberCellEditor) cellEditor;
editor.getNumberEditor().getTextField().removeKeyListener(keyAdapter);
}
}
table.putClientProperty(OBSERVE_KEY_ADAPTER, null);
}
}
protected ListSelectionListener getWhenSelectionModelChanged() {
if (whenSelectionModelChanged == null) {
whenSelectionModelChanged = e -> {
if (!e.getValueIsAdjusting()) {
int selectedRow = table.getSelectedRow();
if (selectedRow >= getRowCount()) {
selectedRow = getRowCount() - 1;
if (log.isInfoEnabled()) {
log.info("Decrease selectedRow!!! to " + selectedRow);
}
}
setSelectedRowIndex(selectedRow);
}
};
}
return whenSelectionModelChanged;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy