001    /*
002     * $Id: DefaultTableColumnModelExt.java 3397 2009-07-22 08:20:13Z kleopatra $
003     *
004     * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
005     * Santa Clara, California 95054, U.S.A. All rights reserved.
006     *
007     * This library is free software; you can redistribute it and/or
008     * modify it under the terms of the GNU Lesser General Public
009     * License as published by the Free Software Foundation; either
010     * version 2.1 of the License, or (at your option) any later version.
011     * 
012     * This library is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015     * Lesser General Public License for more details.
016     * 
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this library; if not, write to the Free Software
019     * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
020     */
021    
022    package org.jdesktop.swingx.table;
023    
024    import java.beans.PropertyChangeEvent;
025    import java.beans.PropertyChangeListener;
026    import java.io.Serializable;
027    import java.util.ArrayList;
028    import java.util.Collections;
029    import java.util.Iterator;
030    import java.util.List;
031    
032    import javax.swing.event.EventListenerList;
033    import javax.swing.event.TableColumnModelListener;
034    import javax.swing.table.DefaultTableColumnModel;
035    import javax.swing.table.TableColumn;
036    
037    import org.jdesktop.swingx.event.TableColumnModelExtListener;
038    
039    
040    /**
041     * A default implementation of <code>TableColumnModelExt</code>.
042     * <p>
043     * 
044     * TODO: explain sub-optimal notification on showing/hiding columns.
045     * (hot fixed issues #156, #157. To really do it
046     * need enhanced TableColumnModelEvent and -Listeners that are
047     * aware of the event.)
048     * 
049     *  
050     * @author Richard Bair
051     * @author Jeanette Winzenburg
052     */
053    public class DefaultTableColumnModelExt extends DefaultTableColumnModel 
054        implements TableColumnModelExt {
055        /** flag to distinguish a shown/hidden column from really added/removed
056         *  columns during notification. This is brittle! 
057         */ 
058    //    private static final String IGNORE_EVENT = "TableColumnModelExt.ignoreEvent";
059        private boolean isVisibilityChange;
060        /**
061         * contains a list of all columns, in the order in which were
062         * added to the model.
063         */
064        private List<TableColumn> initialColumns = new ArrayList<TableColumn>();
065        
066        /**
067         * contains a list of all column, in the order they would appear if
068         * all were visible.
069         */
070        private List<TableColumn> currentColumns = new ArrayList<TableColumn>();
071    
072        /**
073         * Listener attached to TableColumnExt instances to listen for changes
074         * to their visibility status, and to hide/show the column as oppropriate
075         */
076        private VisibilityListener visibilityListener = new VisibilityListener();
077        
078        /** 
079         * Creates a an empty DefaultTableColumnModelExt. 
080         */
081        public DefaultTableColumnModelExt() {
082            super();
083        }
084    
085    //----------------------- implement TableColumnModelExt
086        
087        /**
088         * {@inheritDoc}
089         */
090        public List<TableColumn> getColumns(boolean includeHidden) {
091            if (includeHidden) {
092                return new ArrayList<TableColumn>(initialColumns);
093            } 
094            return Collections.list(getColumns());
095        }
096    
097        /**
098         * {@inheritDoc}
099         */
100        public int getColumnCount(boolean includeHidden) {
101            if (includeHidden) {
102                return initialColumns.size();
103            }
104            return getColumnCount();
105        }
106        
107        /**
108         * {@inheritDoc}
109         */
110        public TableColumnExt getColumnExt(Object identifier) {
111            for (Iterator<TableColumn> iter = initialColumns.iterator(); iter.hasNext();) {
112                TableColumn column = iter.next();
113                if ((column instanceof TableColumnExt) && (identifier.equals(column.getIdentifier()))) {
114                    return (TableColumnExt) column;
115                }
116            }
117            return null;
118        }
119        
120        /**
121         * {@inheritDoc}
122         */
123        public TableColumnExt getColumnExt(int columnIndex) {
124            TableColumn column = getColumn(columnIndex);
125            if (column instanceof TableColumnExt) {
126                return (TableColumnExt) column;
127            }
128            return null;
129        }
130        
131        /**
132         * hot fix for #157: listeners that are aware of
133         * the possible existence of invisible columns
134         * should check if the received columnRemoved originated
135         * from moving a column from visible to invisible.
136         * 
137         * @param oldIndex the fromIndex of the columnEvent
138         * @return true if the column was moved to invisible
139         */
140        public boolean isRemovedToInvisibleEvent(int oldIndex) {
141            return isVisibilityChange;
142        }
143    
144        /**
145         * hot fix for #157: listeners that are aware of
146         * the possible existence of invisible columns
147         * should check if the received columnAdded originated
148         * from moving a column from invisible to visible.
149         * 
150         * @param newIndex the toIndex of the columnEvent
151         * @return true if the column was moved to visible
152         */
153        public boolean isAddedFromInvisibleEvent(int newIndex) {
154            return isVisibilityChange;
155        }
156    
157    //------------------------ TableColumnModel
158        
159        /**
160         * {@inheritDoc} <p>
161         * 
162         * Overridden to update internals related to column visibility.
163         */
164        @Override
165        public void removeColumn(TableColumn column) {
166            //remove the visibility listener if appropriate
167            if (column instanceof TableColumnExt) {
168                ((TableColumnExt)column).removePropertyChangeListener(visibilityListener);
169            }
170            currentColumns.remove(column);
171            initialColumns.remove(column);
172            //let the superclass handle notification etc
173            super.removeColumn(column);
174        }
175    
176        /**
177         * {@inheritDoc} <p>
178         * 
179         * Overridden to update internals related to column visibility.
180         */
181        @Override
182        public void addColumn(TableColumn aColumn) {
183            // hacking to guarantee correct events
184            // two step: add as visible, setVisible
185            boolean oldVisible = true;
186            //add the visibility listener if appropriate
187            if (aColumn instanceof TableColumnExt) {
188                TableColumnExt xColumn = (TableColumnExt) aColumn;
189                oldVisible = xColumn.isVisible();
190                xColumn.setVisible(true);
191                xColumn.addPropertyChangeListener(visibilityListener);
192            }
193            // append the column to the end of both initial- and currentColumns. 
194            currentColumns.add(aColumn);
195            initialColumns.add(aColumn);
196            // let super handle the event notification, super.book-keeping
197            super.addColumn(aColumn);
198            if (aColumn instanceof TableColumnExt) {
199                // reset original visibility
200                ((TableColumnExt) aColumn).setVisible(oldVisible);
201            }
202            
203        }
204    
205        /**
206         * {@inheritDoc} <p>
207         * 
208         * Overridden to update internals related to column visibility.
209         */
210        @Override
211        public void moveColumn(int columnIndex, int newIndex) {
212            if (columnIndex != newIndex) {
213                updateCurrentColumns(columnIndex, newIndex);
214            }
215            super.moveColumn(columnIndex, newIndex);
216        }
217    
218        /**
219         * Adjusts the current column sequence when a visible column is moved.
220         *  
221         * @param oldIndex the old visible position.
222         * @param newIndex the new visible position.
223         */
224        private void updateCurrentColumns(int oldIndex, int newIndex) {
225            TableColumn movedColumn = tableColumns.elementAt(oldIndex);
226            int oldPosition = currentColumns.indexOf(movedColumn);
227            TableColumn targetColumn = tableColumns.elementAt(newIndex);
228            int newPosition = currentColumns.indexOf(targetColumn);
229            currentColumns.remove(oldPosition);
230            currentColumns.add(newPosition, movedColumn);
231            
232        }
233    
234        /**
235         * Update internal state after the visibility of the column
236         * was changed to invisible. The given column is assumed to
237         * be contained in this model.
238         * 
239         * @param col the column which was hidden.
240         */    
241        protected void moveToInvisible(TableColumnExt col) {
242            isVisibilityChange = true;
243            super.removeColumn(col);
244            isVisibilityChange = false;
245        }
246    
247    
248        /**
249         * Update internal state after the visibility of the column
250         * was changed to visible. The given column is assumed to
251         * be contained in this model.
252         *  
253         * @param col the column which was made visible.
254         */    
255        protected void moveToVisible(TableColumnExt col) {
256            isVisibilityChange = true;
257            // two step process: first add at end of columns
258            // then move to "best" position relative to where it
259            // was before hiding.
260            super.addColumn(col);
261            // this is analogous to the proposed fix in #253-swingx
262            // but uses the currentColumns as reference.
263            Integer addIndex = currentColumns.indexOf(col);
264            for (int i = 0; i < (getColumnCount() - 1); i++) {
265                TableColumn tableCol = getColumn(i);
266                int actualPosition = currentColumns.indexOf(tableCol);
267                if (actualPosition > addIndex) {
268                    super.moveColumn(getColumnCount() - 1, i);
269                    break;
270                }
271            }
272            isVisibilityChange = false;
273        }
274    
275    
276        /**
277         * TODO JW: move into propertyChanged! No need for a dedicated listener.
278         * Changed evaluation JW: may still be required as super removes itself as
279         * propertyChangeListener if column is hidden 
280         */
281        private class VisibilityListener implements PropertyChangeListener, Serializable {        
282            public void propertyChange(PropertyChangeEvent evt) {
283                if ("visible".equals(evt.getPropertyName())) {
284                    TableColumnExt columnExt = (TableColumnExt)evt.getSource();
285    
286                    if (columnExt.isVisible()) {
287                        moveToVisible(columnExt);
288                        fireColumnPropertyChange(evt);
289                    } else  {
290                        moveToInvisible(columnExt);
291                    }
292                }  else if (!((TableColumnExt) evt.getSource()).isVisible()) {
293                    fireColumnPropertyChange(evt);
294                }
295            }
296        }
297     
298        // enhanced listener notification
299        
300        
301        /**
302         * Exposed for testing only - don't use! Will be removed again!
303         * @return super's listener list
304         */
305        protected EventListenerList getEventListenerList() {
306            return listenerList;
307        }
308    
309        
310        
311        /**
312         * {@inheritDoc}
313         */
314        @Override
315        public void propertyChange(PropertyChangeEvent evt) {
316            super.propertyChange(evt);
317            fireColumnPropertyChange(evt);
318        }
319    
320        /**
321         * Notifies <code>TableColumnModelExtListener</code>s about property
322         * changes of contained columns. The event instance
323         * is the original as fired by the <code>TableColumn</code>.
324         * @param  evt the event received
325         * @see EventListenerList
326         */
327        protected void fireColumnPropertyChange(PropertyChangeEvent evt) {
328    //        if (IGNORE_EVENT.equals(evt.getPropertyName())) return;
329            // Guaranteed to return a non-null array
330            Object[] listeners = listenerList.getListenerList();
331            // Process the listeners last to first, notifying
332            // those that are interested in this event
333            for (int i = listeners.length-2; i>=0; i-=2) {
334                if (listeners[i]==TableColumnModelExtListener.class) {
335                    ((TableColumnModelExtListener)listeners[i+1]).
336                        columnPropertyChange(evt);
337                }
338            }
339        }
340    
341        
342        /**
343         * {@inheritDoc} <p>
344         * 
345         * 
346         * Overridden to install enhanced notification of listeners of type.
347         * TableColumnModelListenerExt about property changes of contained columns.
348         *  
349         */
350        @Override
351        public void addColumnModelListener(TableColumnModelListener x) {
352            super.addColumnModelListener(x);
353            if (x instanceof TableColumnModelExtListener) {
354                listenerList.add(TableColumnModelExtListener.class, (TableColumnModelExtListener) x);
355            }
356        }
357    
358        /**
359         * {@inheritDoc} <p>
360         * 
361         * Overridden to uninstall enhanced notification of listeners of type.
362         * TableColumnModelListenerExt about property changes of contained columns.
363         */
364        @Override
365        public void removeColumnModelListener(TableColumnModelListener x) {
366            super.removeColumnModelListener(x);
367            if (x instanceof TableColumnModelExtListener) {
368                listenerList.remove(TableColumnModelExtListener.class, (TableColumnModelExtListener) x);
369            }
370        }
371    
372        /**
373         * @return array of all registered listeners 
374         */
375        public TableColumnModelExtListener[] getTableColumnModelExtListeners() {
376            return listenerList.getListeners(TableColumnModelExtListener.class);
377        }
378    }