/*
* $Id: ComponentAdapter.java 4158 2012-02-03 18:29:40Z kschaefe $
*
* 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.decorator;
import java.awt.Rectangle;
import javax.swing.JComponent;
import org.jdesktop.swingx.renderer.StringValues;
/**
* Abstract base class for all component data adapter classes. A
* <code>ComponentAdapter</code> allows the decoration collaborators like f.i.
* {@link Highlighter} to interact with a {@link #target} component through a
* common API. <p>
*
* It has two aspects:
* <ul>
* <li> interact with the view state for the "current" cell. The row/column
* fields and the parameterless methods service this aspect. The coordinates are
* in view coordinate system.
* <li> interact with the data of the component. The methods for this are those
* taking row/column indices as parameters. The coordinates are in model
* coordinate system.
* </ul>
*
* Typically, application code is interested in the first aspect. An example is
* highlighting the background of a row in a JXTable based on the value of a
* cell in a specific column. The solution is to implement a custom
* HighlightPredicate which decides if a given cell should be highlighted and
* configure a ColorHighlighter with the predicate and an appropriate background
* color.
*
* <pre><code>
* HighlightPredicate feverWarning = new HighlightPredicate() {
* int temperatureColumn = 10;
*
* public boolean isHighlighted(Component component, ComponentAdapter adapter) {
* return hasFever(adapter.getValue(temperatureColumn));
* }
*
* private boolean hasFever(Object value) {
* if (!value instanceof Number)
* return false;
* return ((Number) value).intValue() > 37;
* }
* };
*
* Highlighter hl = new ColorHighlighter(feverWarning, Color.RED, null);
* </code></pre>
*
* The adapter is responsible for mapping column and row coordinates.
*
* All input column indices are in model coordinates with exactly two
* exceptions:
* <ul>
* <li> {@link #column} in column view coordinates
* <li> the mapping method {@link #convertColumnIndexToModel(int)} in view coordinates
* </ul>
*
* All input row indices are in model coordinates with exactly four exceptions:
* <ul>
* <li> {@link #row} in row view coordinates
* <li> the mapping method {@link #convertRowIndexToModel(int)} in view coordinates
* <li> the getter for the filtered value {@link #getFilteredValueAt(int, int)}
* takes the row in view coordinates.
* <li> the getter for the filtered string representation {@link #getFilteredStringAt(int, int)}
* takes the row in view coordinates.
* </ul>
*
*
* PENDING JW: anything to gain by generics here?<p>
* PENDING JW: formally document that row/column coordinates must be valid in all methods taking
* model coordinates, that is 0<= row < getRowCount().
*
* @author Ramesh Gupta
* @author Karl Schaefer
* @author Jeanette Winzenburg
*
* @see org.jdesktop.swingx.decorator.HighlightPredicate
* @see org.jdesktop.swingx.decorator.Highlighter
*/
public abstract class ComponentAdapter {
public static final Object DEFAULT_COLUMN_IDENTIFIER = "Column0";
/** current row in view coordinates. */
public int row = 0;
/** current column in view coordinates. */
public int column = 0;
protected final JComponent target;
/**
* Constructs a ComponentAdapter, setting the specified component as the
* target component.
*
* @param component target component for this adapter
*/
public ComponentAdapter(JComponent component) {
target = component;
}
/**
* Returns the component which is this adapter's target.
*
* @return the component which is this adapter's target.
*/
public JComponent getComponent() {
return target;
}
//---------------------------- accessing the target's model: column meta data
/**
* Returns the column's display name (= headerValue) of the column
* at columnIndex in model coordinates.
*
* Used f.i. in SearchPanel to fill the field with the
* column name.<p>
*
* Note: it's up to the implementation to decide for which
* columns it returns a name - most will do so for the
* subset with isTestable = true.
*
* This implementation delegates to getColumnIdentifierAt and returns it's
* toString or null.
*
* @param columnIndex in model coordinates
* @return column name or null if not found
*/
public String getColumnName(int columnIndex) {
Object identifier = getColumnIdentifierAt(columnIndex);
return identifier != null ? identifier.toString() : null;
}
/**
* Returns logical identifier of the column at
* columnIndex in model coordinates.
*
* Note: it's up to the implementation to decide for which
* columns it returns an identifier - most will do so for the
* subset with isTestable = true.<p>
*
* This implementation returns DEFAULT_COLUMN_IDENTIFIER.
*
* PENDING JW: This method replaces the old getColumnIdentifier(int)
* which returned a String which is overly restrictive.
* The only way to gently replace this method was
* to add this with a different name - which makes this name suboptimal.
* Probably should rename again once the old has died out ;-)
*
* @param columnIndex in model coordinates, must be valid.
* @return the identifier of the column at columnIndex or null if it has none.
* @throws ArrayIndexOutOfBoundsException if columnIndex < 0 or columnIndex >= getColumnCount().
*
*
* @see #getColumnIndex(Object)
*/
public Object getColumnIdentifierAt(int columnIndex) {
if ((columnIndex < 0) || (columnIndex >= getColumnCount())) {
throw new ArrayIndexOutOfBoundsException("invalid column index: " + columnIndex);
}
return DEFAULT_COLUMN_IDENTIFIER;
}
/**
* Returns the column index in model coordinates for the logical identifier.
* <p>
*
* This implementation returns 0 if the identifier is the same as the one
* known identifier returned from getColumnIdentifierAt(0), or -1 otherwise.
* So subclasses with one column and a customizable identifier need not
* override. Subclasses which support multiple columns must override this as
* well to keep the contract as in (assuming that the lookup succeeded):
*
* <pre><code>
* Object id = getColumnIdentifierAt(index);
* assertEquals(index, getColumnIndex(index);
* // and the reverse
* int column = getColumnIndex(identifier);
* assertEquals(identifier, getColumnIdentifierAt(column));
* </code></pre>
*
*
* @param identifier the column's identifier, must not be null
* @return the index of the column identified by identifier in model
* coordinates or -1 if no column with the given identifier is
* found.
* @throws NullPointerException if identifier is null.
* @see #getColumnIdentifierAt(int)
*/
public int getColumnIndex(Object identifier) {
if (identifier.equals(getColumnIdentifierAt(0))) {
return 0;
}
return -1;
}
/**
* Returns true if the column should be included in testing.<p>
*
* Here: returns true if visible (that is modelToView gives a valid
* view column coordinate).
*
* @param column the column index in model coordinates
* @return true if the column should be included in testing
*/
public boolean isTestable(int column) {
return convertColumnIndexToView(column) >= 0;
}
/**
* Returns the common class of all data column identified by the given
* column index in model coordinates.<p>
*
* This implementation returns <code>Object.class</code>. Subclasses should
* implement as appropriate.
*
* @return the common class of all data given column in model coordinates.
*
* @see #getColumnClass()
*/
public Class<?> getColumnClass(int column) {
return Object.class;
}
/**
* Returns the common class of all data in the current column.<p>
*
* This implementation delegates to getColumnClass(int) with the current
* column converted to model coordinates.
*
* @return the common class of all data in the current column.
* @see #getColumnClass(int)
*/
public Class<?> getColumnClass() {
return getColumnClass(convertColumnIndexToModel(column));
}
//---------------------------- accessing the target's model: meta data
/**
* Returns the number of columns in the target's data model.
*
* @return the number of columns in the target's data model.
*/
public int getColumnCount() {
return 1; // default for combo-boxes, lists, and trees
}
/**
* Returns the number of rows in the target's data model.
*
* @return the number of rows in the target's data model.
*/
public int getRowCount() {
return 0;
}
//---------------------------- accessing the target's model: data
/**
* Returns the value of the target component's cell identified by the
* specified row and column in model coordinates.
*
* @param row in model coordinates
* @param column in model coordinates
* @return the value of the target component's cell identified by the
* specified row and column
*/
public abstract Object getValueAt(int row, int column);
/**
* Determines whether this cell is editable.
*
* @param row the row to query in model coordinates
* @param column the column to query in model coordinates
* @return <code>true</code> if the cell is editable, <code>false</code>
* otherwise
*/
public abstract boolean isCellEditable(int row, int column);
/**
* Returns the String representation of the value of the cell identified by this adapter. That is,
* for the at position (adapter.row, adapter.column) in view coordinates.<p>
*
* NOTE: this implementation assumes that view coordinates == model
* coordinates, that is simply calls getValueAt(this.row, this.column). It is
* up to subclasses to override appropriately is they support model/view
* coordinate transformation. <p>
*
* This implementation messages the StringValue.TO_STRING with the getValue,
* subclasses should re-implement and use the API appropriate for the target component type.
*
* @return the String representation of value of the cell identified by this adapter
* @see #getValueAt(int, int)
* @see #getFilteredValueAt(int, int)
* @see #getValue(int)
*/
public String getString() {
return getString(convertColumnIndexToModel(column));
}
/**
* Returns the String representation of the value of the cell identified by the current
* adapter row and the given column index in model coordinates.<p>
*
* @param modelColumnIndex the column index in model coordinates
* @return the String representation of the value of the cell identified by this adapter
*
* @see #getFilteredStringAt(int, int)
* @see #getString()
*/
public String getString(int modelColumnIndex) {
return getFilteredStringAt(row, modelColumnIndex);
}
/**
* Returns the String representation of the filtered value of the cell identified by the row
* in view coordinate and the column in model coordinates.<p>
*
* Note: the asymetry of the coordinates is intentional - clients like
* Highlighters are interested in view values but might need to access
* non-visible columns for testing. While it is possible to access
* row coordinates different from the current (that is this.row) it is not
* safe to do so for row > this.row because the adapter doesn't allow to
* query the count of visible rows.<p>
*
* This implementation messages the StringValue.TO_STRING with the filteredValue,
* subclasses should re-implement and use the API appropriate for the target component type.<p>
*
* PENDING JW: what about null cell values? StringValue has a contract to return a
* empty string then, would that be okay here as well?
*
* @param row the row of the cell in view coordinates
* @param column the column of the cell in model coordinates.
* @return the String representation of the filtered value of the cell identified by the row
* in view coordinate and the column in model coordinates
*/
public String getFilteredStringAt(int row, int column) {
return getStringAt(convertRowIndexToModel(row), column);
}
/**
* Returns the String representation of the value of the cell identified by the row
* specified row and column in model coordinates.<p>
*
* This implementation messages the StringValue.TO_STRING with the valueAt,
* subclasses should re-implement and use the api appropriate for the target component type.<p>
*
* @param row in model coordinates
* @param column in model coordinates
* @return the value of the target component's cell identified by the
* specified row and column
*/
public String getStringAt(int row, int column) {
return StringValues.TO_STRING.getString(getValueAt(row, column));
}
/**
* Returns the value of the cell identified by this adapter. That is,
* for the at position (adapter.row, adapter.column) in view coordinates.<p>
*
* NOTE: this implementation assumes that view coordinates == model
* coordinates, that is simply calls getValueAt(this.row, this.column). It is
* up to subclasses to override appropriately is they support model/view
* coordinate transformation.
*
* @return the value of the cell identified by this adapter
* @see #getValueAt(int, int)
* @see #getFilteredValueAt(int, int)
* @see #getValue(int)
*/
public Object getValue() {
return getValue(convertColumnIndexToModel(column));
}
/**
* Returns the value of the cell identified by the current
* adapter row and the given column index in model coordinates.<p>
*
* @param modelColumnIndex the column index in model coordinates
* @return the value of the cell identified by this adapter
* @see #getValueAt(int, int)
* @see #getFilteredValueAt(int, int)
* @see #getValue(int)
*/
public Object getValue(int modelColumnIndex) {
return getFilteredValueAt(row, modelColumnIndex);
}
/**
* Returns the filtered value of the cell identified by the row
* in view coordinate and the column in model coordinates.
*
* Note: the asymmetry of the coordinates is intentional - clients like
* Highlighters are interested in view values but might need to access
* non-visible columns for testing. While it is possible to access
* row coordinates different from the current (that is this.row) it is not
* safe to do so for row > this.row because the adapter doesn't allow to
* query the count of visible rows.
*
* @param row the row of the cell in view coordinates
* @param column the column of the cell in model coordinates.
* @return the filtered value of the cell identified by the row
* in view coordinate and the column in model coordinates
*/
public Object getFilteredValueAt(int row, int column) {
return getValueAt(convertRowIndexToModel(row), column);
}
//----------------------- accessing the target's view state
/**
* Returns the bounds of the cell identified by this adapter.<p>
*
* @return the bounds of the cell identified by this adapter
*/
public Rectangle getCellBounds() {
return target.getBounds();
}
/**
* Returns true if the cell identified by this adapter currently has focus.
* Otherwise, it returns false.
*
* @return true if the cell identified by this adapter currently has focus;
* Otherwise, return false
*/
public abstract boolean hasFocus();
/**
* Returns true if the cell identified by this adapter is currently selected.
* Otherwise, it returns false.
*
* @return true if the cell identified by this adapter is currently selected;
* Otherwise, return false
*/
public abstract boolean isSelected();
/**
* Returns {@code true} if the cell identified by this adapter is editable,
* {@code false} otherwise.
*
* @return {@code true} if the cell is editable, {@code false} otherwise
*/
public abstract boolean isEditable();
/**
* Returns true if the cell identified by this adapter is currently expanded.
* Otherwise, it returns false. For components that do not support
* hierarchical data, this method always returns true because the cells in
* such components can never be collapsed.
*
* @return true if the cell identified by this adapter is currently expanded;
* Otherwise, return false
*/
public boolean isExpanded() {
return true; // sensible default for JList and JTable
}
/**
* Returns true if the cell identified by this adapter is a leaf node.
* Otherwise, it returns false. For components that do not support
* hierarchical data, this method always returns true because the cells in
* such components can never have children.
*
* @return true if the cell identified by this adapter is a leaf node;
* Otherwise, return false
*/
public boolean isLeaf() {
return true; // sensible default for JList and JTable
}
/**
* Returns true if the cell identified by this adapter displays the hierarchical node.
* Otherwise, it returns false. For components that do not support
* hierarchical data, this method always returns false because the cells in
* such components can never have children.
*
* @return true if the cell identified by this adapter displays the hierarchical node;
* Otherwise, return false
*/
public boolean isHierarchical() {
return false; // sensible default for JList and JTable
}
/**
* Returns the depth of this row in the hierarchy where the root is 0. For
* components that do not contain hierarchical data, this method returns 1.
*
* @return the depth for this adapter
*/
public int getDepth() {
return 1; // sensible default for JList and JTable
}
//-------------------- cell coordinate transformations
/**
* For target components that support multiple columns in their model,
* along with column reordering in the view, this method transforms the
* specified columnIndex from model coordinates to view coordinates. For all
* other types of target components, this method returns the columnIndex
* unchanged.
*
* @param columnModelIndex index of a column in model coordinates
* @return index of the specified column in view coordinates
*/
public int convertColumnIndexToView(int columnModelIndex) {
return columnModelIndex; // sensible default for JList and JTree
}
/**
* For target components that support multiple columns in their model, along
* with column reordering in the view, this method transforms the specified
* columnIndex from view coordinates to model coordinates. For all other
* types of target components, this method returns the columnIndex
* unchanged.
*
* @param columnViewIndex index of a column in view coordinates
* @return index of the specified column in model coordinates
*/
public int convertColumnIndexToModel(int columnViewIndex) {
return columnViewIndex; // sensible default for JList and JTree
}
/**
* Converts a row index in model coordinates to an index in view coordinates.
*
* @param rowModelIndex index of a row in model coordinates
* @return index of the specified row in view coordinates
*/
public int convertRowIndexToView(int rowModelIndex) {
return rowModelIndex; // sensible default for JTree
}
/**
* Converts a row index in view coordinates to an index in model coordinates.
*
* @param rowViewIndex index of a row in view coordinates
* @return index of the specified row in model coordinates
*/
public int convertRowIndexToModel(int rowViewIndex) {
return rowViewIndex; // sensible default for JTree
}
}