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 }