All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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