001/*
002 * $Id: DefaultTableColumnModelExt.java 3975 2011-03-28 14:05:29Z 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
022package org.jdesktop.swingx.table;
023
024import java.beans.PropertyChangeEvent;
025import java.beans.PropertyChangeListener;
026import java.io.Serializable;
027import java.util.ArrayList;
028import java.util.Collections;
029import java.util.Iterator;
030import java.util.List;
031
032import javax.swing.event.EventListenerList;
033import javax.swing.event.TableColumnModelListener;
034import javax.swing.table.DefaultTableColumnModel;
035import javax.swing.table.TableColumn;
036
037import 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 */
053public 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    @Override
091    public List<TableColumn> getColumns(boolean includeHidden) {
092        if (includeHidden) {
093            return new ArrayList<TableColumn>(initialColumns);
094        } 
095        return Collections.list(getColumns());
096    }
097
098    /**
099     * {@inheritDoc}
100     */
101    @Override
102    public int getColumnCount(boolean includeHidden) {
103        if (includeHidden) {
104            return initialColumns.size();
105        }
106        return getColumnCount();
107    }
108    
109    /**
110     * {@inheritDoc}
111     */
112    @Override
113    public TableColumnExt getColumnExt(Object identifier) {
114        for (Iterator<TableColumn> iter = initialColumns.iterator(); iter.hasNext();) {
115            TableColumn column = iter.next();
116            if ((column instanceof TableColumnExt) && (identifier.equals(column.getIdentifier()))) {
117                return (TableColumnExt) column;
118            }
119        }
120        return null;
121    }
122    
123    /**
124     * {@inheritDoc}
125     */
126    @Override
127    public TableColumnExt getColumnExt(int columnIndex) {
128        TableColumn column = getColumn(columnIndex);
129        if (column instanceof TableColumnExt) {
130            return (TableColumnExt) column;
131        }
132        return null;
133    }
134    
135    /**
136     * hot fix for #157: listeners that are aware of
137     * the possible existence of invisible columns
138     * should check if the received columnRemoved originated
139     * from moving a column from visible to invisible.
140     * 
141     * @param oldIndex the fromIndex of the columnEvent
142     * @return true if the column was moved to invisible
143     */
144    public boolean isRemovedToInvisibleEvent(int oldIndex) {
145        return isVisibilityChange;
146    }
147
148    /**
149     * hot fix for #157: listeners that are aware of
150     * the possible existence of invisible columns
151     * should check if the received columnAdded originated
152     * from moving a column from invisible to visible.
153     * 
154     * @param newIndex the toIndex of the columnEvent
155     * @return true if the column was moved to visible
156     */
157    public boolean isAddedFromInvisibleEvent(int newIndex) {
158        return isVisibilityChange;
159    }
160
161//------------------------ TableColumnModel
162    
163    /**
164     * {@inheritDoc} <p>
165     * 
166     * Overridden to update internals related to column visibility.
167     */
168    @Override
169    public void removeColumn(TableColumn column) {
170        boolean oldVisible = true;
171        //remove the visibility listener if appropriate
172        if (column instanceof TableColumnExt) {   
173            oldVisible = ((TableColumnExt) column).isVisible();
174            ((TableColumnExt) column).setVisible(true);
175            ((TableColumnExt)column).removePropertyChangeListener(visibilityListener);
176        }
177        currentColumns.remove(column);
178        initialColumns.remove(column);
179        //let the superclass handle notification etc
180        super.removeColumn(column);
181        if (column instanceof TableColumnExt) {
182            ((TableColumnExt) column).setVisible(oldVisible);
183        }
184    }
185
186    /**
187     * {@inheritDoc} <p>
188     * 
189     * Overridden to update internals related to column visibility.
190     */
191    @Override
192    public void addColumn(TableColumn aColumn) {
193        // hacking to guarantee correct events
194        // two step: add as visible, setVisible
195        boolean oldVisible = true;
196        //add the visibility listener if appropriate
197        if (aColumn instanceof TableColumnExt) {
198            TableColumnExt xColumn = (TableColumnExt) aColumn;
199            oldVisible = xColumn.isVisible();
200            xColumn.setVisible(true);
201            xColumn.addPropertyChangeListener(visibilityListener);
202        }
203        // append the column to the end of both initial- and currentColumns. 
204        currentColumns.add(aColumn);
205        initialColumns.add(aColumn);
206        // let super handle the event notification, super.book-keeping
207        super.addColumn(aColumn);
208        if (aColumn instanceof TableColumnExt) {
209            // reset original visibility
210            ((TableColumnExt) aColumn).setVisible(oldVisible);
211        }
212        
213    }
214
215    /**
216     * {@inheritDoc} <p>
217     * 
218     * Overridden to update internals related to column visibility.
219     */
220    @Override
221    public void moveColumn(int columnIndex, int newIndex) {
222        if (columnIndex != newIndex) {
223            updateCurrentColumns(columnIndex, newIndex);
224        }
225        super.moveColumn(columnIndex, newIndex);
226    }
227
228    /**
229     * Adjusts the current column sequence when a visible column is moved.
230     *  
231     * @param oldIndex the old visible position.
232     * @param newIndex the new visible position.
233     */
234    private void updateCurrentColumns(int oldIndex, int newIndex) {
235        TableColumn movedColumn = tableColumns.elementAt(oldIndex);
236        int oldPosition = currentColumns.indexOf(movedColumn);
237        TableColumn targetColumn = tableColumns.elementAt(newIndex);
238        int newPosition = currentColumns.indexOf(targetColumn);
239        currentColumns.remove(oldPosition);
240        currentColumns.add(newPosition, movedColumn);
241        
242    }
243
244    /**
245     * Update internal state after the visibility of the column
246     * was changed to invisible. The given column is assumed to
247     * be contained in this model.
248     * 
249     * @param col the column which was hidden.
250     */    
251    protected void moveToInvisible(TableColumnExt col) {
252        isVisibilityChange = true;
253        super.removeColumn(col);
254        isVisibilityChange = false;
255    }
256
257
258    /**
259     * Update internal state after the visibility of the column
260     * was changed to visible. The given column is assumed to
261     * be contained in this model.
262     *  
263     * @param col the column which was made visible.
264     */    
265    protected void moveToVisible(TableColumnExt col) {
266        isVisibilityChange = true;
267        // two step process: first add at end of columns
268        // then move to "best" position relative to where it
269        // was before hiding.
270        super.addColumn(col);
271        // this is analogous to the proposed fix in #253-swingx
272        // but uses the currentColumns as reference.
273        Integer addIndex = currentColumns.indexOf(col);
274        for (int i = 0; i < (getColumnCount() - 1); i++) {
275            TableColumn tableCol = getColumn(i);
276            int actualPosition = currentColumns.indexOf(tableCol);
277            if (actualPosition > addIndex) {
278                super.moveColumn(getColumnCount() - 1, i);
279                break;
280            }
281        }
282        isVisibilityChange = false;
283    }
284
285
286    /**
287     * TODO JW: move into propertyChanged! No need for a dedicated listener.
288     * Changed evaluation JW: may still be required as super removes itself as
289     * propertyChangeListener if column is hidden 
290     */
291    private class VisibilityListener implements PropertyChangeListener, Serializable {        
292        @Override
293        public void propertyChange(PropertyChangeEvent evt) {
294            if ("visible".equals(evt.getPropertyName())) {
295                TableColumnExt columnExt = (TableColumnExt)evt.getSource();
296
297                if (columnExt.isVisible()) {
298                    moveToVisible(columnExt);
299                    fireColumnPropertyChange(evt);
300                } else  {
301                    moveToInvisible(columnExt);
302                }
303            }  else if (!((TableColumnExt) evt.getSource()).isVisible()) {
304                fireColumnPropertyChange(evt);
305            }
306        }
307    }
308 
309    // enhanced listener notification
310    
311    
312    /**
313     * Exposed for testing only - don't use! Will be removed again!
314     * @return super's listener list
315     */
316    protected EventListenerList getEventListenerList() {
317        return listenerList;
318    }
319
320    
321    
322    /**
323     * {@inheritDoc}
324     */
325    @Override
326    public void propertyChange(PropertyChangeEvent evt) {
327        super.propertyChange(evt);
328        fireColumnPropertyChange(evt);
329    }
330
331    /**
332     * Notifies <code>TableColumnModelExtListener</code>s about property
333     * changes of contained columns. The event instance
334     * is the original as fired by the <code>TableColumn</code>.
335     * @param  evt the event received
336     * @see EventListenerList
337     */
338    protected void fireColumnPropertyChange(PropertyChangeEvent evt) {
339//        if (IGNORE_EVENT.equals(evt.getPropertyName())) return;
340        // Guaranteed to return a non-null array
341        Object[] listeners = listenerList.getListenerList();
342        // Process the listeners last to first, notifying
343        // those that are interested in this event
344        for (int i = listeners.length-2; i>=0; i-=2) {
345            if (listeners[i]==TableColumnModelExtListener.class) {
346                ((TableColumnModelExtListener)listeners[i+1]).
347                    columnPropertyChange(evt);
348            }
349        }
350    }
351
352    
353    /**
354     * {@inheritDoc} <p>
355     * 
356     * 
357     * Overridden to install enhanced notification of listeners of type.
358     * TableColumnModelListenerExt about property changes of contained columns.
359     *  
360     */
361    @Override
362    public void addColumnModelListener(TableColumnModelListener x) {
363        super.addColumnModelListener(x);
364        if (x instanceof TableColumnModelExtListener) {
365            listenerList.add(TableColumnModelExtListener.class, (TableColumnModelExtListener) x);
366        }
367    }
368
369    /**
370     * {@inheritDoc} <p>
371     * 
372     * Overridden to uninstall enhanced notification of listeners of type.
373     * TableColumnModelListenerExt about property changes of contained columns.
374     */
375    @Override
376    public void removeColumnModelListener(TableColumnModelListener x) {
377        super.removeColumnModelListener(x);
378        if (x instanceof TableColumnModelExtListener) {
379            listenerList.remove(TableColumnModelExtListener.class, (TableColumnModelExtListener) x);
380        }
381    }
382
383    /**
384     * @return array of all registered listeners 
385     */
386    public TableColumnModelExtListener[] getTableColumnModelExtListeners() {
387        return listenerList.getListeners(TableColumnModelExtListener.class);
388    }
389}