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

net.sf.cuf.model.ui.MultiSelectionTableModelAdapter Maven / Gradle / Ivy

The newest version!
package net.sf.cuf.model.ui;

import net.sf.cuf.model.AspectAdapter;
import net.sf.cuf.model.DelegateAccess;
import net.sf.cuf.model.MultiSelectionInList;
import net.sf.cuf.model.ui.ListTableMapper.Mapping;
import net.sf.cuf.ui.builder.SwingXMLBuilder;
import net.sf.cuf.ui.table.TableFilterPlugin;

import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.ListSelectionModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.EventListenerList;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.TableModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static net.sf.cuf.model.ui.CufTableRowSorter.convertRowIndexToModel;
import static net.sf.cuf.model.ui.CufTableRowSorter.convertRowIndexToView;
import static net.sf.cuf.ui.table.CufSorterUtil.attachCufSorterWithHeaderRenderer;

/**
 * This class maps the content of List of a {@link MultiSelectionInList} ValueModel to a
 * JTable TableModel and ListSelectionModel.
* This is the multi selection version of the {@link ListTableMapper}. *

* Each entry in the list of the {@link MultiSelectionInList} ValueModel describes a * row, all entries are assumed to be objects from the same class/interface. * Whenever the either the list or the selection changes, the table is * adjusted accordingly. * The table data is read-only, when the user changes the selection in the * table, the selection of the {@link MultiSelectionInList} is changed accordingly. *

* The initial selection is taken from the {@link MultiSelectionInList} value model. * * FIXME: currently we never unregister from the MultiSelectionInList value model, * this might lead to memory leaks. */ public class MultiSelectionTableModelAdapter implements TableModel, ListSelectionListener { // NOTE: // Most of the code is copied from ListTableMapper and ListTableMapperBase. // Everything around the Adapters between combos, lists, tables and value models // feels like it needs a major overhaul or at least some face lifting. // Comments in the other classes are not accurate and code is very 'grown'. /** our JTable, never null */ private JTable mTable; /** value model holding the list, never null */ protected MultiSelectionInList mMultiSelectionInList; /** helper for the TableModel callback handling */ private EventListenerList mListenerList; /** marker if we are inside a selection change, we then ignore selection * changes from the "other" model */ private boolean mInSelectionChange; /** List of Mapping objects, contains a DelegateAccess object * or each column, never null */ private List mColumnMapping; /** our table filter plugin */ private TableFilterPlugin mTableFilterPlugin; /** shared no params array */ private static final Class[] NO_PARAMS = {}; /** shared no args array */ private static final Object[] NO_ARGS = {}; /** * Create a new adaption object between a JTable and a {@link MultiSelectionInList} value model. * This object is also set as the JTable's TableModel. * @param pTable the table for which we provide TableModel behaviour and connect to the {@link ListSelectionModel} * @param pValueModel the value model that drives the table (data, selection) * and gets updated by the table (selection only) * @param pMapping non-null List containing {@link Mapping} objects * @param pSortable true if table is sortable * @throws IllegalArgumentException if a parameter is bogus */ public MultiSelectionTableModelAdapter(final JTable pTable, final MultiSelectionInList pValueModel, final List pMapping, final boolean pSortable) { this(pTable, pValueModel, pMapping, pSortable, -1); } /** * Create a new adaption object between a JTable and a {@link MultiSelectionInList} value model. * This object is also set as the JTable's TableModel. * @param pTable the table for which we provide TableModel behaviour and connect to the {@link ListSelectionModel} * @param pValueModel the value model that drives the table (data, selection) * and gets updated by the table (selection only) * @param pMapping non-null List containing {@link Mapping} objects * @param pSortable true if table is sortable * @param pColumnForInitialSorting -1 or the column index for initial sorting * @throws IllegalArgumentException if a parameter is bogus */ public MultiSelectionTableModelAdapter(final JTable pTable, final MultiSelectionInList pValueModel, final List pMapping, final boolean pSortable, final int pColumnForInitialSorting) { this(pTable, pValueModel, pMapping, pSortable, pColumnForInitialSorting, null); } /** * Create a new adaption object between a JTable and a {@link MultiSelectionInList} value model. * This object is also set as the JTable's TableModel. * @param pTable the table for which we provide TableModel behaviour and connect to the {@link ListSelectionModel} * @param pValueModel the value model that drives the table (data, selection) * and gets updated by the table (selection only) * @param pMapping non-null List containing {@link Mapping} objects * @param pSortable true if table is sortable * @param pColumnForInitialSorting -1 or the column index for initial sorting * @param pTableFilterPlugin our table filter plugin, may be null * @throws IllegalArgumentException if a parameter is bogus */ public MultiSelectionTableModelAdapter(final JTable pTable, final MultiSelectionInList pValueModel, final List pMapping, final boolean pSortable, final int pColumnForInitialSorting, final TableFilterPlugin pTableFilterPlugin) { mTableFilterPlugin= pTableFilterPlugin; mColumnMapping= Collections.emptyList(); init(pTable, pValueModel); setColumnMapping(pTable, pMapping, pSortable, pColumnForInitialSorting); } /** * Getter for TableFilterPlugin * @return mTableFilterPlugin */ public TableFilterPlugin getTableFilterPlugin() { return mTableFilterPlugin; } /* * "our" handling code (=no Swing or ValueModel callback/handling stuff) */ /** * Common construction stuff, it creates a new adaption between * a JTable and a {@link MultiSelectionInList} value model. * This object is also set as the JTable's TableModel. * @param pTable the table for which we provide TableModel behaviour and connect to the {@link ListSelectionModel} * @param pValueModel the value model that drives the table (data, selection) * and gets updated by the table (selection only) * @throws IllegalArgumentException if a parameter is bogus */ protected void init(final JTable pTable, final MultiSelectionInList pValueModel) { if (pTable==null) { throw new IllegalArgumentException("the table must not be null"); } if (pValueModel==null) { throw new IllegalArgumentException("the value model must not be null"); } mTable = pTable; mMultiSelectionInList = pValueModel; mListenerList = new EventListenerList(); mInSelectionChange = false; // this must stay at the end so that everything is setup first mMultiSelectionInList.onChangeSend (this, "vmDataChanged"); mMultiSelectionInList.getSelectedIndexSetValueModel().onChangeSend(this, "vmSelectionChanged"); pTable.setModel (this); pTable.getSelectionModel().addListSelectionListener(this); } /** * The method maps the attributes of our List entry class to column names * in the table and the alignments of the columns. * @param pTable the table for which we set the column alignments * @param pMapping non-null List containing {@link Mapping} objects * @param pSortable true if table is sortable * @param pColumnForInitialSorting -1 or the column index for initial sorting * @throws IllegalArgumentException if pMapping is null or contains invalid mappings */ private void setColumnMapping(final JTable pTable, final List pMapping, final boolean pSortable, final int pColumnForInitialSorting) { if (pMapping==null) { throw new IllegalArgumentException("mapping must not be null"); } List columnMapping= new ArrayList<>(pMapping.size()); for (Mapping mapping : pMapping) { Mapping myMapping= new Mapping(mapping); columnMapping.add(myMapping); } mColumnMapping= columnMapping; // notify the table to re-create the columns TableModelEvent e= new TableModelEvent(this, TableModelEvent.HEADER_ROW); fireTableChanged(e); // make table sortable with a CufTableRowSorter if (pSortable) { // set sorter and header renderer CufTableRowSorter sorter = attachCufSorterWithHeaderRenderer(pTable); // add Comparator for sorting, if necessary for (int i = 0, n = pMapping.size(); i < n; i++) { Mapping mapping= pMapping.get(i); if (mapping.getComparatorClass() != null) { try { Comparator myComparator = (Comparator) mapping .getComparatorClass() .getConstructor(NO_PARAMS) .newInstance(NO_ARGS); sorter.setColumnComparator(i, myComparator); } catch (Exception e1) { throw SwingXMLBuilder.createException("could not instantiate ComparatorClass " + mapping.getComparatorClass() + " for " + mapping.getColumnClass(), e1); } } } // force initial sorting if (pColumnForInitialSorting >= 0 && pColumnForInitialSorting < pTable.getModel().getColumnCount()) { sorter.forceInitialSorting(pColumnForInitialSorting); } } for (int i = 0, n = pMapping.size(); i < n; i++) { Mapping mapping = pMapping.get(i); // set defined column alignment if(mapping.getColumnAlignment() != null) { pTable.getColumnModel().getColumn(i).setCellRenderer( new ColumnAlignmentRenderer(mapping.getColumnAlignment())); } else { // if no alignment defined: set default-alignment per column-class if(mapping.getColumnClass() != null && Boolean.class.isAssignableFrom(mapping.getColumnClass())) { pTable.getColumnModel().getColumn(i).setCellRenderer( new ColumnAlignmentRenderer(ColumnAlignmentRenderer.ALIGN_CENTER)); } else if(mapping.getColumnClass() != null && (Number.class.isAssignableFrom(mapping.getColumnClass()) || Date.class.isAssignableFrom(mapping.getColumnClass()))) { pTable.getColumnModel().getColumn(i).setCellRenderer( new ColumnAlignmentRenderer(ColumnAlignmentRenderer.ALIGN_RIGHT)); } else { pTable.getColumnModel().getColumn(i).setCellRenderer( new ColumnAlignmentRenderer(ColumnAlignmentRenderer.ALIGN_LEFT)); } } // set prefered width for table columns if(mapping.getColumnPrefWidthIntValue() > 0) { // if we don't disable the auto resize, preferred with has no consequences pTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF); JTextField dummyTextField = new JTextField(mapping.getColumnPrefWidthIntValue()); pTable.getColumnModel().getColumn(i).setPreferredWidth(dummyTextField.getPreferredSize().width); } } } /******************************************************************/ /* * TableModel callbacks */ public int getColumnCount() { return mColumnMapping.size(); } public String getColumnName(final int pColumnIndex) { return mColumnMapping.get(pColumnIndex).getColumnTitle(); } public Class getColumnClass(final int pColumnIndex) { Class clazz= mColumnMapping.get(pColumnIndex).getColumnClass(); if (clazz==null) clazz= Object.class; return clazz; } public Object getValueAt(final int pRowIndex, final int pColumnIndex) { Mapping mapping = mColumnMapping.get(pColumnIndex); DelegateAccess valueModel= mapping.getValueModel(); List list = mMultiSelectionInList.getValue(); Object value = list.get(pRowIndex); return valueModel.getValue(value); } public Object getRawValueAt(final int pRowIndex, final int pColumnIndex) { List list = mMultiSelectionInList.getValue(); return list.get(pRowIndex); } public Object getValueForSortingAt(final int pRowIndex, final int pColumnIndex) { Mapping mapping = mColumnMapping.get(pColumnIndex); DelegateAccess valueModelForSorting= mapping.getValueModelForSorting(); List list = mMultiSelectionInList.getValue(); Object value = list.get(pRowIndex); return valueModelForSorting.getValue(value); } public boolean isColumnSortable(final int pColumnIndex) { Mapping mapping= mColumnMapping.get(pColumnIndex); return mapping.isSortable(); } /******************************************************************/ /* Event handling code from the model */ /******************************************************************/ /** * Called whenever the data of the {@link MultiSelectionInList} changes. * @param pEvent not used */ public void vmDataChanged(final ChangeEvent pEvent) { // ignore the changes we triggered if (mInSelectionChange) { return; } // notify the table to re-read the data, this will clear the selection, // so we re-set the selection afterwards mInSelectionChange= true; try { TableModelEvent e= new TableModelEvent(this); fireTableChanged(e); updateSelectionFromModel(); } finally { mInSelectionChange= false; } } /** * Called whenever the selection of the SelectionInList changes. * @param pEvent not used */ public void vmSelectionChanged(final ChangeEvent pEvent) { // ignore the changes we triggered if (mInSelectionChange) { return; } mInSelectionChange= true; try { updateSelectionFromModel(); } finally { mInSelectionChange= false; } } /** * Helper method to update the selection in the table * based on the current selection in the model. * The call to this method should be wrapped in * checking {@link #mInSelectionChange}. */ private void updateSelectionFromModel() { // Note: the code here is helpless when the model selection // is incompatible with the selection mode of the table. // (Since it can't correct the selection of the model.) // Note: because of the internal behaviour of the // DefaultListSelectionModel it is nearly impossible // to keep anchor and lead index the same. // Right now we don't even try. Set selectedIndexes = mMultiSelectionInList.getSelectedIndexSetValueModel().getValue(); ListSelectionModel selectionModel = mTable.getSelectionModel(); selectionModel.clearSelection(); for (final Object selectedIndexe : selectedIndexes) { Integer selectedIndex = (Integer) selectedIndexe; int viewIndex = convertRowIndexToView(mTable, selectedIndex); if (0 <= viewIndex && viewIndex < mTable.getRowCount()) { selectionModel.addSelectionInterval(viewIndex, viewIndex); } } } /******************************************************************/ /* Event handling code from the table */ /******************************************************************/ /** * Called whenever the selection of the JTable changes, we update * our SelectionInList index. * @param pEvent event describing the new selection */ public void valueChanged(final ListSelectionEvent pEvent) { // ignore the changes we triggered if (mInSelectionChange) { return; } // ignore intermediate changes if (pEvent.getValueIsAdjusting()) { return; } mInSelectionChange= true; try { Set selectionIndexSet = new HashSet<>(); int [] selectedViewRows = mTable.getSelectedRows(); for (int selectedViewRow : selectedViewRows) { int tableModelIndex = convertRowIndexToModel(mTable, selectedViewRow); selectionIndexSet.add(tableModelIndex); } // now update in one call mMultiSelectionInList.setSelectedIndexes(selectionIndexSet); } finally { mInSelectionChange= false; } } /* * Swing TableModel methods */ public int getRowCount() { List list= mMultiSelectionInList.getValue(); return list==null ? 0 : list.size(); } /** * Checks if the cell is editable * @param pRowIndex the row index * @param pColumnIndex the column index * @return the value */ @Override public boolean isCellEditable(final int pRowIndex, final int pColumnIndex) { final ListTableMapper.Mapping columnMapping= mColumnMapping.get(pColumnIndex); return columnMapping.isEditable(); } /** * Sets value to the cell * @param pValue the value * @param pRowIndex the row index * @param pColumnIndex the column index */ @Override public void setValueAt(final Object pValue, final int pRowIndex, final int pColumnIndex) { final List list = mMultiSelectionInList.getValue(); final Object listEntry = list.get(pRowIndex); final ListTableMapper.Mapping columnMapping = mColumnMapping.get(pColumnIndex); // cast will work, columnMapping.isEditable() will otherwise return false ((AspectAdapter)columnMapping.getValueModel()).getAccessAdapter().setValue(listEntry, pValue); // and now notify the list of the change mMultiSelectionInList.signalExternalUpdate(); } public void addTableModelListener(final TableModelListener pTableModelListener) { mListenerList.add(TableModelListener.class, pTableModelListener); } public void removeTableModelListener(final TableModelListener pTableModelListener) { mListenerList.remove(TableModelListener.class, pTableModelListener); } protected void fireTableChanged(final TableModelEvent peEvent) { Object[] listeners = mListenerList.getListenerList(); for (int i= listeners.length-2; i>=0; i-=2) { if (listeners[i]==TableModelListener.class) { ((TableModelListener)listeners[i+1]).tableChanged(peEvent); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy