001    /*
002     * $Id: TableColumnExt.java,v 1.8 2006/05/14 15:55:54 dmouse 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    import java.beans.PropertyChangeEvent;
024    import java.beans.PropertyChangeListener;
025    import java.lang.reflect.Constructor;
026    import java.util.Comparator;
027    import java.util.Hashtable;
028    
029    import javax.swing.table.TableCellEditor;
030    import javax.swing.table.TableCellRenderer;
031    
032    import org.jdesktop.swingx.decorator.Sorter;
033    
034    /**
035     * TableColumn extension which adds support for view column configuration features
036     * including column-visibility, sorting, and prototype values.
037     *
038     * @author Ramesh Gupta
039     * @author Amy Fowler
040     * @author Jeanette Winzenburg
041     */
042    public class TableColumnExt extends javax.swing.table.TableColumn
043        implements Cloneable {
044    
045        // removed - comparator now is a full-fledged bound property.
046    //    public static final String SORTER_COMPARATOR = "Sorter.COMPARATOR";
047        protected boolean editable = true;
048        protected boolean visible = true;
049        protected Object prototypeValue = null;
050    
051        private Hashtable clientProperties = null;
052    
053        protected Sorter sorter = null;
054        /** the comparator to use for this column */
055        protected Comparator comparator;
056        
057        private Constructor sorterConstructor = null;
058        private final static Constructor    defaultSorterConstructor;
059        private final static Class[]    sorterConstructorSignature =
060            new Class[]{int.class, boolean.class};
061    
062        static {
063            Constructor constructor = null;
064            try {
065                Class   sorterClass = Class.forName("org.jdesktop.swingx.decorator.ShuttleSorter", true,
066                                                  TableColumnExt.class.getClassLoader());
067                constructor = sorterClass.getConstructor(sorterConstructorSignature);
068            }
069            catch (Exception ex) {
070            }
071            defaultSorterConstructor = constructor;
072        }
073    
074        /**
075         * Creates new table view column with a model index = 0.
076         */
077        public TableColumnExt() {
078            this(0);
079        }
080    
081        /**
082         * Creates new table view column with the specified model index.
083         * @param modelIndex index of table model column to which this view column
084         *        is bound.
085         */
086        public TableColumnExt(int modelIndex) {
087            this(modelIndex, 75);   // default width taken from javax.swing.table.TableColumn
088        }
089    
090        /**
091         * Creates new table view column with the specified model index and column width.
092         * @param modelIndex index of table model column to which this view column
093         *        is bound.
094         * @param width pixel width of view column
095         */
096        public TableColumnExt(int modelIndex, int width) {
097            this(modelIndex, width, null, null);
098        }
099    
100        /**
101         * Creates new table view column with the specified model index, column
102         * width, cell renderer and cell editor.
103         * @param modelIndex index of table model column to which this view column
104         *        is bound.
105         * @param width pixel width of view column
106         * @param cellRenderer the cell renderer which will render all cells in this
107         *        view column
108         * @param cellEditor the cell editor which will edit cells in this view column
109         */
110        public TableColumnExt(int modelIndex, int width,
111                              TableCellRenderer cellRenderer, TableCellEditor cellEditor) {
112            super(modelIndex, width, cellRenderer, cellEditor);
113            this.sorterConstructor = defaultSorterConstructor;
114        }
115    
116        /** cosmetic override: don't fool users if resize is
117         * not possible due to fixed column width.
118         */
119        @Override
120        public boolean getResizable() {
121            return super.getResizable() && (getMinWidth() < getMaxWidth());
122        }
123    
124        /**
125         * Sets the editable property.  This property enables the table view to
126         * control whether or not the user is permitted to edit cell values in this
127         * view column, even if the model permits.  If the table model column corresponding to this view column
128         * returns <code>true</code> for <code>isCellEditable</code> and this
129         * property is <code>false</code>, then the user will not be permitted to
130         * edit values from this view column, dispite the model setting.
131         * If the model's <code>isCellEditable</code> returns <code>false</code>,
132         * then this property will be ignored and cell edits will not be permitted
133         * in this view column.
134         * @see #isEditable
135         * @see javax.swing.table.TableModel#isCellEditable
136         * @param editable boolean indicating whether or not the user may edit cell
137         *        values in this view column
138         */
139        public void setEditable(boolean editable) {
140            boolean oldEditable = this.editable;
141            this.editable = editable;
142            firePropertyChange("editable",
143                               Boolean.valueOf(oldEditable),
144                               Boolean.valueOf(editable));
145        }
146    
147        /**
148         * @see #setEditable
149         * @return boolean indicating whether or not the user may edit cell
150         *        values in this view column
151         */
152        public boolean isEditable() {
153            return editable;
154        }
155    
156        /**
157         * Sets the prototypeValue property.  The value should be of a type
158         * which corresponds to the column's class as defined by the table model.
159         * If non-null, the JXTable instance will use this property to calculate
160         * and set the initial preferredWidth of the column.  Note that this
161         * initial preferredWidth will be overridden if the user resizes columns
162         * directly.
163         * @see #getPrototypeValue
164         * @see org.jdesktop.swingx.JXTable#getPreferredScrollableViewportSize
165         * @param value Object containing the value of the prototype to be used
166         *         to calculate the initial preferred width of the column
167         */
168        public void setPrototypeValue(Object value) {
169            Object oldPrototypeValue = this.prototypeValue;
170            this.prototypeValue = value;
171            firePropertyChange("prototypeValue",
172                               oldPrototypeValue,
173                               value);
174    
175        }
176    
177        /**
178         * @see #setPrototypeValue
179         * @return Object containing the value of the prototype to be used
180         *         to calculate the initial preferred width of the column
181         */
182        public Object getPrototypeValue() {
183            return prototypeValue;
184        }
185    
186        /**
187         * Sets a user-defined sorter for this column
188         * @param sorterClassName String containing the name of the class which
189         *        performs sorting on this view column
190         */
191        public void setSorterClass(String sorterClassName) {
192            if ((sorterClassName == null) || (sorterClassName.length() == 0)){
193                sorterConstructor = null;
194            }
195            else {
196                try {
197                    Class   sorterClass = Class.forName(sorterClassName, true,
198                                                      getClass().getClassLoader());
199                    sorterConstructor = sorterClass.getConstructor(sorterConstructorSignature);
200                }
201                catch (Exception ex) {
202                    sorterConstructor = null;
203                }
204            }
205        }
206    
207        /**
208         *
209         * @return String containing the name of the class which
210         *         performs sorting on this view column
211         */
212        public String getSorterClass() {
213            return sorterConstructor == null ? null :
214                sorterConstructor.getDeclaringClass().getName();
215        }
216    
217        /**
218         *
219         * @return Sorter instance which performs sorting on this view column
220         */
221        public Sorter getSorter() {
222            if (sorter == null) {
223                if (sorterConstructor != null) {
224                    try {
225                        sorter = (Sorter) sorterConstructor.newInstance(
226                            new Object[] {
227                                new Integer(getModelIndex()),
228                                new Boolean(true)});
229                       sorter.setComparator(getComparator());
230                    }
231                    catch (Exception ex) {
232                    }
233                }
234            }
235            return sorter;
236        }
237    
238        /**
239         * returns the Comparator to use for this column.
240         * @return <code>Comparator</code> to use for this column
241         */
242        public Comparator getComparator() {
243            return comparator;
244        }
245    
246        /**
247         * sets the comparator to use for this column.
248         * Updates the column's sorter with the given comparator.
249         * NOTE: it's up to clients to not re-set the sorter's comparator
250         * somewhere else - the column cannot guarantee to keep both in synch!
251         *  
252         * 
253         * @param comparator
254         */
255        public void setComparator(Comparator comparator) {
256            Comparator old = getComparator();
257            this.comparator = comparator;
258            if (sorter != null) {
259                sorter.setComparator(comparator);
260            }
261            firePropertyChange("comparator", old, getComparator());
262        }
263        
264        /**
265         *
266         * @return boolean indicating whether this view column is sortable
267         */
268        public boolean isSortable() {
269            return sorterConstructor != null;
270        }
271    
272        /**
273         * Sets the title of this view column.  This is a convenience
274         * wrapper for <code>setHeaderValue</code>.
275         * @param title String containing the title of this view column
276         */
277        public void setTitle(String title) {
278            setHeaderValue(title);              // simple wrapper
279        }
280    
281        /**
282         * Convenience method which returns the headerValue property after
283         * converting it to a string. 
284         * @return String containing the title of this view column or null if
285         *   no headerValue is set.
286         */
287        public String getTitle() {
288            Object header = getHeaderValue();
289            return header != null ? header.toString() : null;   // simple wrapper
290        }
291    
292        /**
293         * Sets the visible property.  This property controls whether or not
294         * this view column is currently visible in the table.
295         * @see #setVisible
296         * @param visible boolean indicating whether or not this view column is
297         *        visible in the table
298         */
299        public void setVisible(boolean visible) {
300            boolean oldVisible = this.visible;
301            this.visible = visible;
302            firePropertyChange("visible",
303                               Boolean.valueOf(oldVisible),
304                               Boolean.valueOf(visible));
305    
306        }
307    
308        /**
309         * @see #setVisible
310         * @return boolean indicating whether or not this view column is
311         *        visible in the table
312         */
313        public boolean isVisible() {
314            return visible;
315        }
316    
317        /**
318         * Stores the object value using the specified key.
319         * @see #getClientProperty
320         * @param key Object which is used as key to retrieve value
321         * @param value Object containing value of client property
322         * @throws IllegalArgumentException if key == null
323         */
324        public void putClientProperty(Object key, Object value) {
325            if (key == null)
326                throw new IllegalArgumentException("null key");
327    
328            if ((value == null) && (getClientProperty(key) == null)) {
329                return;
330            }
331    
332            Object old = getClientProperty(key);
333            if (value == null) {
334                getClientProperties().remove(key);
335            }
336            else {
337                getClientProperties().put(key, value);
338            }
339    
340            firePropertyChange(key.toString(), old, value);
341            /* Make all fireXXX methods in TableColumn protected instead of private */
342        }
343    
344        /**
345         * Retrieves the object value using the specified key.
346         * @see #putClientProperty
347         * @param key Object which is used as key to retrieve value
348         * @return Object containing value of client property
349         */
350        public Object getClientProperty(Object key) {
351            return ((key == null) || (clientProperties == null)) ?
352                    null : clientProperties.get(key);
353        }
354    
355        private Hashtable getClientProperties() {
356            if (clientProperties == null) {
357                clientProperties = new Hashtable();
358            }
359            return clientProperties;
360        }
361    
362        /**
363          * Returns a clone of this TableColumn. Some implementations of TableColumn
364          * may assume that all TableColumnModels are unique, therefore it is
365          * recommended that the same TableColumn instance not be added more than
366          * once to a TableColumnModel. To show TableColumns with the same column of
367          * data from the model, create a new instance with the same modelIndex.
368          *
369          * @return a clone of this TableColumn
370          */
371         @Override
372         public Object clone() {
373             // TODO: JW: where are the client properties?
374             final TableColumnExt copy = new TableColumnExt(
375                 this.getModelIndex(), this.getWidth(),
376                 this.getCellRenderer(), this.getCellEditor());
377    
378             copy.setEditable(this.isEditable());
379             copy.setHeaderValue(this.getHeaderValue());    // no need to copy setTitle();
380             copy.setIdentifier(this.getIdentifier());
381             copy.setMaxWidth(this.getMaxWidth());
382             copy.setMinWidth(this.getMinWidth());
383             copy.setPreferredWidth(this.getPreferredWidth());
384             copy.setPrototypeValue(this.getPrototypeValue());
385             // JW: isResizable is overridden to return a calculated property!
386             copy.setResizable(super.getResizable());
387             copy.setVisible(this.isVisible());
388             copy.setSorterClass(this.getSorterClass());
389             copy.sorterConstructor = sorterConstructor;
390             copy.setComparator(getComparator());
391             return copy;
392         }
393    
394         protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
395             if ((oldValue != null && !oldValue.equals(newValue)) ||
396                  oldValue == null && newValue != null) {
397                 PropertyChangeListener pcl[] = getPropertyChangeListeners();
398                 if (pcl != null && pcl.length != 0) {
399                     PropertyChangeEvent pce = new PropertyChangeEvent(this,
400                         propertyName,
401                         oldValue, newValue);
402    
403                     for (int i = 0; i < pcl.length; i++) {
404                         pcl[i].propertyChange(pce);
405                     }
406                 }
407             }
408         }
409    }