package data.swing; import java.util.*; import java.io.*; import java.beans.*; import util.*; import util.swing.*; import data.*; import data.events.*; /** * A {@link javax.swing.table.TableModel} that models the contents of a {@link Catalog}. * * @author Steffen Zschaler * @version 2.0 23/08/1999 * @since v2.0 */ public class CatalogTableModel extends AbstractTableModel implements HelpableListener, CatalogChangeListener, PropertyChangeListener, Serializable { /** * The DataBasket used to determine visibility. * * @serial */ protected DataBasket m_dbBasket; /** * The Catalog that is being modelled. * * @serial */ protected Catalog m_cModel; /** * The Comparator that defines the sorting order of records in the model. It compares * {@link CatalogItem CatalogItems}. * * @serial */ protected Comparator m_cmpComparator = new NaturalComparator(); /** * The internal model. A list of the CatalogItems' keys. * * @serial */ protected List m_lKeys; /** * Create a new CatalogTableModel. * * @param c the Catalog to be modelled. May be {@link data.filters.CatalogFilter filtered}. * @param db the DataBasket to be used to determine visibility. * @param cmp a Comparator defining the sort order of the records. If <code>null</code>, records are ordered * according to the natural ordering of the CatalogItems. * @param ted a TableEntryDescriptor that can split individual CatalogItems into a table's cells. */ public CatalogTableModel (Catalog c, DataBasket db, Comparator cmp, TableEntryDescriptor ted) { super (ted); m_dbBasket = db; m_cModel = c; if (cmp != null) { m_cmpComparator = cmp; } listenerList = new ListenerHelper (this); updateModel(); } /** * Get the record at the given index. * * @param row the index for which to retrieve the record. Element of [0, {@link #getRowCount}). * @return the {@link CatalogItem} to be displayed at the given index. May return <code>null</code> if * either there is no record at the indicated position or an exception occurs. * * @override Never */ public Object getRecord (int row) { ((ListenerHelper) listenerList).needModelUpdate(); try { if ((row > -1) && (row < getRowCount())) { return m_cModel.get ((String) m_lKeys.get (row), m_dbBasket, false); } else { return null; } } catch (VetoException ex) { return null; } } /** * Get the number of records in this model. * * @override Never */ public int getRowCount() { ((ListenerHelper) listenerList).needModelUpdate(); return m_lKeys.size(); } // HelpableListener interface methods /** * Subscribe as a listener to the model. If the modelled {@link Catalog} is a {@link ListenableCatalog}, * subscribe as a listener. * * @override Never */ public void subscribe() { if (m_cModel instanceof ListenableCatalog) { ((ListenableCatalog) m_cModel).addCatalogChangeListener (this); } } /** * Un-Subscribe as a listener from the model. If the modelled {@link Catalog} is a {@link ListenableCatalog}, * un-subscribe as a listener. * * @override Never */ public void unsubscribe() { if (m_cModel instanceof ListenableCatalog) { ((ListenableCatalog) m_cModel).removeCatalogChangeListener (this); } } /** * Update the internal model based on the modelled {@link Catalog}. * * @override Never */ public synchronized void updateModel() { List lKeys = new LinkedList (m_cModel.keySet (m_dbBasket)); Collections.sort (lKeys, new Comparator() { public int compare (Object o1, Object o2) { try { return m_cmpComparator.compare (m_cModel.get ((String) o1, m_dbBasket, false), m_cModel.get ((String) o2, m_dbBasket, false)); } catch (VetoException ex) { return 0; } } }); m_lKeys = lKeys; } // CatalogChangeListener interface methods /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void addedCatalogItem (CatalogChangeEvent e) { if ((e.getBasket() == m_dbBasket) || (e.getBasket() == null)) { checkAdd (e.getAffectedItem()); } } /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void commitedAddCatalogItem (CatalogChangeEvent e) { checkAdd (e.getAffectedItem()); } /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void rolledbackAddCatalogItem (CatalogChangeEvent e) { if (e.getBasket() == m_dbBasket) { checkRemove (e.getAffectedItem()); } } /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void canRemoveCatalogItem (CatalogChangeEvent e) throws VetoException {} /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void noRemoveCatalogItem (CatalogChangeEvent e) {} /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void removedCatalogItem (CatalogChangeEvent e) { checkRemove (e.getAffectedItem()); } /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void commitedRemoveCatalogItem (CatalogChangeEvent e) {} /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void rolledbackRemoveCatalogItem (CatalogChangeEvent e) { checkAdd (e.getAffectedItem()); } /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void canEditCatalogItem (CatalogChangeEvent e) throws VetoException {} /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void noEditCatalogItem (CatalogChangeEvent e) {} /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void editingCatalogItem (CatalogChangeEvent e) { if (e.getBasket() != m_dbBasket) { checkRemove (e.getAffectedItem()); } else { e.getAffectedItem().addPropertyChangeListener (this); } } /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void commitEditCatalogItem (CatalogChangeEvent e) { if (e.getBasket() != m_dbBasket) { checkAdd (e.getAffectedItem()); } else { e.getAffectedItem().removePropertyChangeListener (this); updateModel(); fireTableDataChanged(); } } /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void rollbackEditCatalogItem (CatalogChangeEvent e) { if (e.getBasket() != m_dbBasket) { checkAdd (e.getAffectedItem()); } else { e.getAffectedItem().removePropertyChangeListener (this); updateModel(); fireTableDataChanged(); } } /** * Update the internal model and inform any listeners according to the received event. * * <p>This method is public as an implementation detail and must not be called directly.</p> * * @override Never */ public void propertyChange (PropertyChangeEvent e) { if (e.getSource() instanceof CatalogItem) { checkUpdate ((CatalogItem) e.getSource()); } } /** * Internal helper method. Check where, if at all, the given CatalogItem has been added with respect to the * internal model. * * @param ci the added CatalogItem * * @override Never */ protected synchronized void checkAdd (CatalogItem ci) { updateModel(); int nIdx = m_lKeys.indexOf (ci.getName()); if (nIdx > -1) { fireTableRowsInserted (nIdx, nIdx); } } /** * Internal helper method. Check from where, if at all, the given CatalogItem has been removed with respect * to the internal model. * * @param ci the removed CatalogItem * * @override Never */ protected synchronized void checkRemove (CatalogItem ci) { int nIdx = m_lKeys.indexOf (ci.getName()); updateModel(); if (nIdx > -1) { fireTableRowsDeleted (nIdx, nIdx); } } /** * Internal helper method. Check for updates in the given CatalogItem. * * @param ci the updated CatalogItem * * @override Never */ protected synchronized void checkUpdate (CatalogItem ci) { int nIdx1 = m_lKeys.indexOf (ci.getName()); updateModel(); int nIdx2 = m_lKeys.indexOf (ci.getName()); if (nIdx1 == -1) { if (nIdx2 > -1) { fireTableRowsInserted (nIdx2, nIdx2); } else { return; } } else { if (nIdx2 == -1) { fireTableRowsDeleted (nIdx1, nIdx1); } else { if (nIdx1 > nIdx2) { int nTemp = nIdx2; nIdx2 = nIdx1; nIdx1 = nIdx2; } fireTableRowsUpdated (nIdx1, nIdx2); } } } }