001    /*
002     * $Id: DefaultTableColumnModelExt.java,v 1.10 2005/10/24 15:01:34 kleopatra Exp $
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.util.ArrayList;
027    import java.util.Collections;
028    import java.util.HashMap;
029    import java.util.HashSet;
030    import java.util.Iterator;
031    import java.util.List;
032    import java.util.Map;
033    import java.util.Set;
034    
035    import javax.swing.table.DefaultTableColumnModel;
036    import javax.swing.table.TableColumn;
037    
038    
039    /**
040     * A default implementation supporting invisible columns.
041     *
042     * Note (JW): hot fixed issues #156, #157. To really do it
043     * need enhanced TableColumnModelEvent and -Listeners that are
044     * aware of the event.
045     * 
046     *  
047     * @author Richard Bair
048     */
049    public class DefaultTableColumnModelExt extends DefaultTableColumnModel implements TableColumnModelExt {
050        private static final String IGNORE_EVENT = "TableColumnModelExt.ignoreEvent";
051        /**
052         * contains a list of all of the columns, in the order in which they would
053         * appear if all of the columns were visible.
054         */
055        private List allColumns = new ArrayList();
056        
057        /**
058         * Set of invisible columns. These must be of type TableColumnExt.
059         */
060        private Set invisibleColumns = new HashSet();
061        
062        private Map oldIndexes = new HashMap();
063        
064        /**
065         * Listener attached to TableColumnExt instances to listen for changes
066         * to their visibility status, and to hide/show the column as oppropriate
067         */
068        private VisibilityListener visibilityListener = new VisibilityListener();
069        
070        /** 
071         * Creates a new instance of DefaultTableColumnModelExt 
072         */
073        public DefaultTableColumnModelExt() {
074            super();
075        }
076    
077    
078        /**
079         * 
080         */
081        public List getColumns(boolean includeHidden) {
082            if (includeHidden) {
083                return new ArrayList(allColumns);
084            } 
085            return Collections.list(getColumns());
086        }
087    
088        public int getColumnCount(boolean includeHidden) {
089            if (includeHidden) {
090                return allColumns.size();
091            }
092            return getColumnCount();
093        }
094        /**
095         * {@inheritDoc}
096         */
097        public TableColumnExt getColumnExt(Object identifier) {
098            for (Iterator iter = allColumns.iterator(); iter.hasNext();) {
099                TableColumn column = (TableColumn) iter.next();
100                if ((column instanceof TableColumnExt) && (identifier.equals(column.getIdentifier()))) {
101                    return (TableColumnExt) column;
102                }
103            }
104            return null;
105        }
106        public Set getInvisibleColumns() {
107            return new HashSet(invisibleColumns);
108        }
109    
110        /**
111         * hot fix for #157: listeners that are aware of
112         * the possible existence of invisible columns
113         * should check if the received columnRemoved originated
114         * from moving a column from visible to invisible.
115         * 
116         * @param oldIndex the fromIndex of the columnEvent
117         * @return true if the column was moved to invisible
118         */
119        public boolean isRemovedToInvisibleEvent(int oldIndex) {
120            Integer index = new Integer(oldIndex);
121            return oldIndexes.containsValue(index);
122        }
123    
124        /**
125         * hot fix for #157: listeners that are aware of
126         * the possible existence of invisible columns
127         * should check if the received columnAdded originated
128         * from moving a column from invisible to visible.
129         * 
130         * @param newIndex the toIndex of the columnEvent
131         * @return true if the column was moved to visible
132         */
133        public boolean isAddedFromInvisibleEvent(int newIndex) {
134            if (!(getColumn(newIndex) instanceof TableColumnExt)) return false;
135            return Boolean.TRUE.equals(((TableColumnExt) getColumn(newIndex)).getClientProperty(IGNORE_EVENT));
136        }
137    
138        
139        public void removeColumn(TableColumn column) {
140            //remove the visibility listener if appropriate
141            if (column instanceof TableColumnExt) {
142                ((TableColumnExt)column).removePropertyChangeListener(visibilityListener);
143            }
144            //remove from the invisible columns set and the allColumns list first
145            invisibleColumns.remove(column);
146            allColumns.remove(column);
147            //let the superclass handle notification etc
148            super.removeColumn(column);
149        }
150    
151        public void addColumn(TableColumn aColumn) {
152            // hacking to guarantee correct events
153            // two step: add as visible, setVisible
154            boolean oldVisible = true;
155            //add the visibility listener if appropriate
156            if (aColumn instanceof TableColumnExt) {
157                TableColumnExt xColumn = (TableColumnExt) aColumn;
158                oldVisible = xColumn.isVisible();
159                xColumn.setVisible(true);
160                xColumn.addPropertyChangeListener(visibilityListener);
161            }
162            //append the column to the end of "allColumns". If the column is
163            //visible, let super add it to the list of columns. If not, then
164            //add it to the set of invisible columns and return.
165            //In the case of an invisible column being added to the model,
166            //I still do event notification for the model having
167            //been changed so that the ColumnControlButton or other similar
168            //code interacting with invisible columns knows that a new invisible
169            //column has been added
170            allColumns.add(aColumn);
171            super.addColumn(aColumn);
172            if (aColumn instanceof TableColumnExt) {
173                ((TableColumnExt) aColumn).setVisible(oldVisible);
174            }
175            
176        }
177    
178            
179        protected void moveToInvisible(TableColumnExt col) {
180            //make invisible
181            int oldIndex = tableColumns.indexOf(col);
182            invisibleColumns.add(col);
183            oldIndexes.put(col, new Integer(oldIndex));
184            super.removeColumn(col);
185        }
186    
187        protected void moveToVisible(TableColumnExt col) {
188            //make visible
189            invisibleColumns.remove(col);
190            Integer oldIndexInteger = (Integer)oldIndexes.get(col);
191            int oldIndex = oldIndexInteger == null ? getColumnCount() : oldIndexInteger.intValue();
192            oldIndexes.remove(col);
193            col.putClientProperty(IGNORE_EVENT, Boolean.TRUE);
194            super.addColumn(col);
195            moveColumn(getColumnCount() - 1, Math.min(getColumnCount() - 1, oldIndex));
196            col.putClientProperty(IGNORE_EVENT, null);
197        }
198    
199        private final class VisibilityListener implements PropertyChangeListener {        
200            public void propertyChange(PropertyChangeEvent evt) {
201                if (evt.getPropertyName().equals("visible")) {
202                    boolean oldValue = ((Boolean)evt.getOldValue()).booleanValue();
203                    boolean newValue = ((Boolean)evt.getNewValue()).booleanValue();
204                    TableColumnExt col = (TableColumnExt)evt.getSource();
205    
206                    if (oldValue && !newValue) {
207                        moveToInvisible(col);
208                    } else if (!oldValue && newValue) {
209                        moveToVisible(col);
210                    }
211                }
212            }
213        }
214        
215    }