/*
* $Id: TableColumnExt.java 3927 2011-02-22 16:34:11Z kleopatra $
*
* Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
* Santa Clara, California 95054, U.S.A. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package org.jdesktop.swingx.table;
import java.awt.Component;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Comparator;
import java.util.Hashtable;
import javax.swing.DefaultCellEditor;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import org.jdesktop.swingx.decorator.CompoundHighlighter;
import org.jdesktop.swingx.decorator.Highlighter;
import org.jdesktop.swingx.plaf.UIDependent;
import org.jdesktop.swingx.renderer.AbstractRenderer;
/**
* TableColumn
extension for enhanced view column configuration.
* The general drift is to strengthen the TableColumn abstraction as the
* place to configure and dynamically update view column properties, covering a
* broad range of customization requirements. Using collaborators are expected
* to listen to property changes and update themselves accordingly.
*
*
* A functionality enhancement is the notion of column visibility:
* TableColumnModelExt
manages sets of visible/hidden
* TableColumnExt
s controlled by the columns'
* visible
property. Typically, users can toggle column
* visibility at runtime, f.i. through a dedicated control in the upper trailing
* corner of a JScrollPane
.
*
*
* A prominent group of properties allows fine-grained, per-column control of
* corresponding Table/-Header features.
*
*
* Sorting : sortable
controls whether this column
* should be sortable by user's sort gestures; Comparator
can
* hold a column specific type.
*
* Editing : editable
controls whether cells of this
* column should be accessible to in-table editing.
*
* Tooltip : toolTipText
holds the column tooltip
* which is shown when hovering over the column's header.
*
* Highlighter : highlighters
holds the column
* highlighters; these are applied to the renderer after the table highlighters.
* Any modification of the list of contained Highlighter
s
* (setting them, adding one or removing one) will result in a
* {@code PropertyChangeEvent} being fired for "highlighters". State changes on
* contained Highlighter
s will result in a PropertyChangeEvent
* for "highlighterStateChanged".
*
*
*
* Analogous to JComponent
, this class supports per-instance
* "client" properties. They are meant as a small-scale extension mechanism.
* They are similar to regular bean properties in that registered
* PropertyChangeListener
s are notified about changes. TODO:
* example?
*
*
* A TableColumnExt
implements UIDependent, that is it takes over
* responsibility to update LAF dependent properties of contained elements when
* messaged with updateUI. This implementation updates its Highlighter
s,
* Cell-/HeaderRenderer and CellEditor.
*
* TODO: explain prototype (sizing, collaborator-used-by ColumnFactory (?))
*
*
* @author Ramesh Gupta
* @author Amy Fowler
* @author Jeanette Winzenburg
* @author Karl Schaefer
*
* @see TableColumnModelExt
* @see ColumnFactory
* @see org.jdesktop.swingx.plaf.UIDependent
* @see javax.swing.JComponent#putClientProperty
*/
public class TableColumnExt extends TableColumn implements UIDependent {
/** visible property. Initialized to true
.*/
protected boolean visible = true;
/** hideable property. Initialized to true
.*/
protected boolean hideable = true;
/** prototype property. */
protected Object prototypeValue;
/** per-column comparator */
protected Comparator> comparator;
/** per-column sortable property. Initialized to true
. */
protected boolean sortable = true;
/** per-column editable property. Initialized to true
.*/
protected boolean editable = true;
/** per-column tool tip text. */
private String toolTipText;
/** storage for client properties. */
protected Hashtable clientProperties;
/**
* The compound highlighter for the column.
*/
protected CompoundHighlighter compoundHighlighter;
private ChangeListener highlighterChangeListener;
private boolean ignoreHighlighterStateChange;
/**
* Creates new table view column with a model index = 0.
*/
public TableColumnExt() {
this(0);
}
/**
* Creates new table view column with the specified model index.
* @param modelIndex index of table model column to which this view column
* is bound.
*/
public TableColumnExt(int modelIndex) {
this(modelIndex, 75); // default width taken from javax.swing.table.TableColumn
}
/**
* Creates new table view column with the specified model index and column width.
* @param modelIndex index of table model column to which this view column
* is bound.
* @param width pixel width of view column
*/
public TableColumnExt(int modelIndex, int width) {
this(modelIndex, width, null, null);
}
/**
* Creates new table view column with the specified model index, column
* width, cell renderer and cell editor.
* @param modelIndex index of table model column to which this view column
* is bound.
* @param width pixel width of view column
* @param cellRenderer the cell renderer which will render all cells in this
* view column
* @param cellEditor the cell editor which will edit cells in this view column
*/
public TableColumnExt(int modelIndex, int width,
TableCellRenderer cellRenderer, TableCellEditor cellEditor) {
super(modelIndex, width, cellRenderer, cellEditor);
}
/**
* Instantiates a new table view column with all properties copied from the
* given original.
*
* @param columnExt the column to copy properties from
* @see #copyFrom(TableColumnExt)
*/
public TableColumnExt(TableColumnExt columnExt) {
this(columnExt.getModelIndex(), columnExt.getWidth(), columnExt
.getCellRenderer(), columnExt.getCellEditor());
copyFrom(columnExt);
}
/**
* Sets the Highlighter
s to the table, replacing any old settings.
* None of the given Highlighters must be null.
*
* This is a bound property.
*
* Note: as of version #1.257 the null constraint is enforced strictly. To remove
* all highlighters use this method without param.
*
* @param highlighters zero or more not null highlighters to use for renderer decoration.
* @throws NullPointerException if array is null or array contains null values.
*
* @see #getHighlighters()
* @see #addHighlighter(Highlighter)
* @see #removeHighlighter(Highlighter)
*
*/
public void setHighlighters(Highlighter... highlighters) {
ignoreHighlighterStateChange = true;
Highlighter[] old = getHighlighters();
getCompoundHighlighter().setHighlighters(highlighters);
firePropertyChange("highlighters", old, getHighlighters());
ignoreHighlighterStateChange = false;
}
/**
* Returns the Highlighter
s used by this table.
* Maybe empty, but guarantees to be never null.
*
* @return the Highlighters used by this table, guaranteed to never null.
* @see #setHighlighters(Highlighter[])
*/
public Highlighter[] getHighlighters() {
return getCompoundHighlighter().getHighlighters();
}
/**
* Appends a Highlighter
to the end of the list of used
* Highlighter
s. The argument must not be null.
*
*
* @param highlighter the Highlighter
to add, must not be null.
* @throws NullPointerException if Highlighter
is null.
*
* @see #removeHighlighter(Highlighter)
* @see #setHighlighters(Highlighter[])
*/
public void addHighlighter(Highlighter highlighter) {
ignoreHighlighterStateChange = true;
Highlighter[] old = getHighlighters();
getCompoundHighlighter().addHighlighter(highlighter);
firePropertyChange("highlighters", old, getHighlighters());
ignoreHighlighterStateChange = false;
}
/**
* Removes the given Highlighter.
*
* Does nothing if the Highlighter is not contained.
*
* @param highlighter the Highlighter to remove.
* @see #addHighlighter(Highlighter)
* @see #setHighlighters(Highlighter...)
*/
public void removeHighlighter(Highlighter highlighter) {
ignoreHighlighterStateChange = true;
Highlighter[] old = getHighlighters();
getCompoundHighlighter().removeHighlighter(highlighter);
firePropertyChange("highlighters", old, getHighlighters());
ignoreHighlighterStateChange = false;
}
/**
* Returns the CompoundHighlighter assigned to the table, null if none.
* PENDING: open up for subclasses again?.
*
* @return the CompoundHighlighter assigned to the table.
*/
protected CompoundHighlighter getCompoundHighlighter() {
if (compoundHighlighter == null) {
compoundHighlighter = new CompoundHighlighter();
compoundHighlighter.addChangeListener(getHighlighterChangeListener());
}
return compoundHighlighter;
}
/**
* Returns the ChangeListener
to use with highlighters. Lazily
* creates the listener.
*
* @return the ChangeListener for observing changes of highlighters,
* guaranteed to be not-null
*/
protected ChangeListener getHighlighterChangeListener() {
if (highlighterChangeListener == null) {
highlighterChangeListener = createHighlighterChangeListener();
}
return highlighterChangeListener;
}
/**
* Creates and returns the ChangeListener observing Highlighters.
*
* Here: repaints the table on receiving a stateChanged.
*
* @return the ChangeListener defining the reaction to changes of
* highlighters.
*/
protected ChangeListener createHighlighterChangeListener() {
return new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
if (ignoreHighlighterStateChange) return;
firePropertyChange("highlighterStateChanged", false, true);
}
};
}
/**
* Returns true if the user can resize the TableColumn's width,
* false otherwise. This is a usability override: it takes into account
* the case where it's principally allowed to resize the column
* but not possible because the column has fixed size.
*
* @return a boolean indicating whether the user can resize this column.
*/
@Override
public boolean getResizable() {
// TODO JW: resizable is a bound property, so to be strict
// we'll need to override setMin/MaxWidth to fire resizable
// property change.
return super.getResizable() && (getMinWidth() < getMaxWidth());
}
/**
* Sets the editable property. This property allows to mark all cells in a
* column as read-only, independent of the per-cell editability as returned
* by the TableModel.isCellEditable
. If the cell is
* read-only in the model layer, this property will have no effect.
*
* @param editable boolean indicating whether or not the user may edit cell
* values in this view column
* @see #isEditable
* @see org.jdesktop.swingx.JXTable#isCellEditable(int, int)
* @see javax.swing.table.TableModel#isCellEditable
*/
public void setEditable(boolean editable) {
boolean oldEditable = this.editable;
this.editable = editable;
firePropertyChange("editable",
Boolean.valueOf(oldEditable),
Boolean.valueOf(editable));
}
/**
* Returns the per-column editable property.
* The default is true
.
*
* @return boolean indicating whether or not the user may edit cell
* values in this view column
* @see #setEditable
*/
public boolean isEditable() {
return editable;
}
/**
* Sets the prototypeValue property. The value should be of a type
* which corresponds to the column's class as defined by the table model.
* If non-null, the JXTable instance will use this property to calculate
* and set the initial preferredWidth of the column. Note that this
* initial preferredWidth will be overridden if the user resizes columns
* directly.
*
* @param value Object containing the value of the prototype to be used
* to calculate the initial preferred width of the column
* @see #getPrototypeValue
* @see org.jdesktop.swingx.JXTable#getPreferredScrollableViewportSize
*/
public void setPrototypeValue(Object value) {
Object oldPrototypeValue = this.prototypeValue;
this.prototypeValue = value;
firePropertyChange("prototypeValue",
oldPrototypeValue,
value);
}
/**
* Returns the prototypeValue property.
* The default is null
.
*
* @return Object containing the value of the prototype to be used
* to calculate the initial preferred width of the column
* @see #setPrototypeValue
*/
public Object getPrototypeValue() {
return prototypeValue;
}
/**
* Sets the comparator to use for this column.
* JXTable
sorting api respects this property by passing it on
* to the SortController
.
*
* @param comparator a custom comparator to use in interactive
* sorting.
* @see #getComparator
* @see org.jdesktop.swingx.sort.SortController
* @see org.jdesktop.swingx.decorator.SortKey
*/
public void setComparator(Comparator> comparator) {
Comparator> old = getComparator();
this.comparator = comparator;
firePropertyChange("comparator", old, getComparator());
}
/**
* Returns the comparator to use for the column.
* The default is null
.
*
* @return Comparator
to use for this column
* @see #setComparator
*/
public Comparator> getComparator() {
return comparator;
}
/**
* Sets the sortable property. JXTable
sorting api respects this
* property by disabling interactive sorting on this column if false.
*
* @param sortable boolean indicating whether or not this column can
* be sorted in the table
* @see #isSortable
*/
public void setSortable(boolean sortable) {
boolean old = isSortable();
this.sortable = sortable;
firePropertyChange("sortable", old, isSortable());
}
/**
* Returns the sortable property.
* The default value is true
.
*
* @return boolean indicating whether this view column is sortable
* @see #setSortable
*/
public boolean isSortable() {
return sortable;
}
/**
* Registers the text to display in the column's tool tip.
* Typically, this is used by JXTableHeader
to
* display when the mouse cursor lingers over the column's
* header cell.
*
* @param toolTipText text to show.
* @see #setToolTipText(String)
*/
public void setToolTipText(String toolTipText) {
String old = getToolTipText();
this.toolTipText = toolTipText;
firePropertyChange("toolTipText", old, getToolTipText());
}
/**
* Returns the text of to display in the column's tool tip.
* The default is null
.
*
* @return the text of the column ToolTip.
* @see #setToolTipText(String)
*/
public String getToolTipText() {
return toolTipText;
}
/**
* Sets the title of this view column. This is a convenience
* wrapper for setHeaderValue
.
* @param title String containing the title of this view column
*/
public void setTitle(String title) {
setHeaderValue(title); // simple wrapper
}
/**
* Convenience method which returns the headerValue property after
* converting it to a string.
* @return String containing the title of this view column or null if
* no headerValue is set.
*/
public String getTitle() {
Object header = getHeaderValue();
return header != null ? header.toString() : null; // simple wrapper
}
/**
* Sets the visible property. This property controls whether or not
* this view column is currently visible in the table.
*
* @param visible boolean indicating whether or not this view column is
* visible in the table
* @see #setVisible
*/
public void setVisible(boolean visible) {
boolean oldVisible = isVisible();
this.visible = visible;
firePropertyChange("visible", oldVisible, isVisible());
}
/**
* Returns a boolean indicating whether or not this column is visible.
* The bare property value is constrained by this column's hideable setting,
* that is a not hideable column is always visible, irrespective of the
* property setting.
*
* The default is true
.
*
* @return boolean indicating whether or not this view column is
* visible in the table
* @see #setVisible
*/
public boolean isVisible() {
if (!isHideable()) return true;
return visible;
}
/**
* Sets the hideable property. This property controls whether the column can
* be hidden. This is a bound property. If the column's visibilty is affected,
* listeners are notified about that change as well..
*
*
* The default value is true.
*
* @param hideable
*/
public void setHideable(boolean hideable) {
boolean old = isHideable();
boolean oldVisible = isVisible();
this.hideable = hideable;
firePropertyChange("visible", oldVisible, isVisible());
firePropertyChange("hideable", old, isHideable());
}
/**
* Returns the hideable property.
*
* @return the hideable property.
*
* @see #setHideable(boolean)
*/
public boolean isHideable() {
return hideable;
}
/**
* Sets the client property "key" to value
.
* If value
is null
this method will remove the property.
* Changes to
* client properties are reported with PropertyChange
events.
* The name of the property (for the sake of PropertyChange events) is
* key.toString()
.
*
* The get/putClientProperty
methods provide access to a
* per-instance hashtable, which is intended for small scale extensions of
* TableColumn.
*
*
* @param key Object which is used as key to retrieve value
* @param value Object containing value of client property
* @throws IllegalArgumentException if key is null
* @see #getClientProperty
* @see javax.swing.JComponent#putClientProperty
*/
public void putClientProperty(Object key, Object value) {
if (key == null)
throw new IllegalArgumentException("null key");
if ((value == null) && (getClientProperty(key) == null)) {
return;
}
Object old = getClientProperty(key);
if (value == null) {
getClientProperties().remove(key);
}
else {
getClientProperties().put(key, value);
}
firePropertyChange(key.toString(), old, value);
/* Make all fireXXX methods in TableColumn protected instead of private */
}
/**
* Returns the value of the property with the specified key. Only properties
* added with putClientProperty
will return a non-null
* value.
*
* @param key Object which is used as key to retrieve value
* @return Object containing value of client property or null
*
* @see #putClientProperty
*/
public Object getClientProperty(Object key) {
return ((key == null) || (clientProperties == null)) ?
null : clientProperties.get(key);
}
private Hashtable getClientProperties() {
if (clientProperties == null) {
clientProperties = new Hashtable();
}
return clientProperties;
}
/**
* Copies properties from original. Handles all properties except
* modelIndex, width, cellRenderer, cellEditor. Called from copy
* constructor.
*
* @param original the tableColumn to copy from
*
* @see #TableColumnExt(TableColumnExt)
*/
protected void copyFrom(TableColumnExt original) {
setEditable(original.isEditable());
setHeaderValue(original.getHeaderValue()); // no need to copy setTitle();
setToolTipText(original.getToolTipText());
setIdentifier(original.getIdentifier());
setMaxWidth(original.getMaxWidth());
setMinWidth(original.getMinWidth());
setPreferredWidth(original.getPreferredWidth());
setPrototypeValue(original.getPrototypeValue());
// JW: isResizable is overridden to return a calculated property!
setResizable(original.isResizable);
setVisible(original.isVisible());
setSortable(original.isSortable());
setComparator(original.getComparator());
copyClientPropertiesFrom(original);
if (original.compoundHighlighter != null) {
setHighlighters(original.getHighlighters());
}
}
/**
* Copies all clientProperties of this TableColumnExt
* to the target column.
*
* @param original the target column.
*/
protected void copyClientPropertiesFrom(TableColumnExt original) {
if (original.clientProperties == null) return;
for(Object key: original.clientProperties.keySet()) {
putClientProperty(key, original.getClientProperty(key));
}
}
/**
* Notifies registered PropertyChangeListener
s
* about property changes. This method must be invoked internally
* whe any of the enhanced properties changed.
*
* Implementation note: needed to replicate super
* functionality because super's field propertyChangeSupport
* and method fireXX
are both private.
*
* @param propertyName name of changed property
* @param oldValue old value of changed property
* @param newValue new value of changed property
*/
protected void firePropertyChange(String propertyName, Object oldValue, Object newValue) {
if ((oldValue != null && !oldValue.equals(newValue)) ||
oldValue == null && newValue != null) {
PropertyChangeListener pcl[] = getPropertyChangeListeners();
if (pcl != null && pcl.length != 0) {
PropertyChangeEvent pce = new PropertyChangeEvent(this,
propertyName,
oldValue, newValue);
for (int i = 0; i < pcl.length; i++) {
pcl[i].propertyChange(pce);
}
}
}
}
//---------------- implement UIDependent
/**
* Update ui of owned ui-dependent parts. This implementation
* updates the contained highlighters.
*
*/
@Override
public void updateUI() {
updateHighlighterUI();
updateRendererUI(getCellRenderer());
updateRendererUI(getHeaderRenderer());
updateEditorUI(getCellEditor());
}
/**
* @param editor
*
*/
private void updateEditorUI(TableCellEditor editor) {
if (editor == null) return;
// internal knowledge of core table - already updated
if ((editor instanceof JComponent)
|| (editor instanceof DefaultCellEditor))
return;
try {
Component comp = editor
.getTableCellEditorComponent(null, null, false, -1, -1);
if (comp != null) {
SwingUtilities.updateComponentTreeUI(comp);
}
} catch (Exception e) {
// can't do anything - renderer can't cope with off-range cells
}
}
/**
* @param tableCellRenderer
*
*/
private void updateRendererUI(TableCellRenderer renderer) {
if (renderer == null) return;
// internal knowledge of core table - already updated
if (renderer instanceof JComponent) {
return;
}
Component comp = null;
if (renderer instanceof AbstractRenderer) {
comp = ((AbstractRenderer) renderer).getComponentProvider().getRendererComponent(null);
} else {
try {
comp = renderer
.getTableCellRendererComponent(null, null, false, false,
-1, -1);
} catch (Exception e) {
// can't do anything - renderer can't cope with off-range cells
}
}
if (comp != null) {
SwingUtilities.updateComponentTreeUI(comp);
}
}
/**
*
*/
private void updateHighlighterUI() {
if (compoundHighlighter == null) return;
compoundHighlighter.updateUI();
}
}