001/*
002 * $Id: ComponentAdapter.java 4158 2012-02-03 18:29:40Z kschaefe $
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
022package org.jdesktop.swingx.decorator;
023
024import java.awt.Rectangle;
025
026import javax.swing.JComponent;
027
028import org.jdesktop.swingx.renderer.StringValues;
029
030/**
031 * Abstract base class for all component data adapter classes. A
032 * <code>ComponentAdapter</code> allows the decoration collaborators like f.i.
033 * {@link Highlighter} to interact with a {@link #target} component through a
034 * common API. <p>
035 * 
036 * It has two aspects:
037 * <ul>
038 * <li> interact with the view state for the "current" cell. The row/column
039 * fields and the parameterless methods service this aspect. The coordinates are
040 * in view coordinate system. 
041 * <li> interact with the data of the component. The methods for this are those
042 * taking row/column indices as parameters. The coordinates are in model
043 * coordinate system. 
044 * </ul>
045 * 
046 * Typically, application code is interested in the first aspect. An example is
047 * highlighting the background of a row in a JXTable based on the value of a
048 * cell in a specific column. The solution is to implement a custom
049 * HighlightPredicate which decides if a given cell should be highlighted and
050 * configure a ColorHighlighter with the predicate and an appropriate background
051 * color.
052 * 
053 * <pre><code>
054 * HighlightPredicate feverWarning = new HighlightPredicate() {
055 *     int temperatureColumn = 10;
056 * 
057 *     public boolean isHighlighted(Component component, ComponentAdapter adapter) {
058 *         return hasFever(adapter.getValue(temperatureColumn));
059 *     }
060 * 
061 *     private boolean hasFever(Object value) {
062 *         if (!value instanceof Number)
063 *             return false;
064 *         return ((Number) value).intValue() &gt; 37;
065 *     }
066 * };
067 * 
068 * Highlighter hl = new ColorHighlighter(feverWarning, Color.RED, null);
069 * </code></pre>
070 * 
071 * The adapter is responsible for mapping column and row coordinates.
072 * 
073 * All input column indices are in model coordinates with exactly two
074 * exceptions:
075 * <ul>
076 * <li> {@link #column} in column view coordinates
077 * <li> the mapping method {@link #convertColumnIndexToModel(int)} in view coordinates
078 * </ul>
079 * 
080 * All input row indices are in model coordinates with exactly four exceptions:
081 * <ul>
082 * <li> {@link #row} in row view coordinates
083 * <li> the mapping method {@link #convertRowIndexToModel(int)} in view coordinates
084 * <li> the getter for the filtered value {@link #getFilteredValueAt(int, int)}
085 * takes the row in view coordinates.
086  * <li> the getter for the filtered string representation {@link #getFilteredStringAt(int, int)}
087 * takes the row in view coordinates.
088* </ul>
089 * 
090 * 
091 * PENDING JW: anything to gain by generics here?<p>
092 * PENDING JW: formally document that row/column coordinates must be valid in all methods taking
093 *  model coordinates, that is 0<= row < getRowCount().
094 * 
095 * @author Ramesh Gupta
096 * @author Karl Schaefer
097 * @author Jeanette Winzenburg
098 * 
099 * @see org.jdesktop.swingx.decorator.HighlightPredicate
100 * @see org.jdesktop.swingx.decorator.Highlighter
101 */
102public abstract class ComponentAdapter {
103    public static final Object DEFAULT_COLUMN_IDENTIFIER = "Column0";
104    /** current row in view coordinates. */
105    public int row = 0;
106    /** current column in view coordinates. */
107    public int column = 0;
108    protected final JComponent    target;
109
110    /**
111     * Constructs a ComponentAdapter, setting the specified component as the
112     * target component.
113     *
114     * @param component target component for this adapter
115     */
116    public ComponentAdapter(JComponent component) {
117        target = component;
118    }
119
120    /**
121     * Returns the component which is this adapter's target.
122     * 
123     * @return the component which is this adapter's target.
124     */
125    public JComponent getComponent() {
126        return target;
127    }
128
129//---------------------------- accessing the target's model: column meta data
130    
131    /**
132     * Returns the column's display name (= headerValue) of the column
133     * at columnIndex in model coordinates.
134     * 
135     * Used f.i. in SearchPanel to fill the field with the 
136     * column name.<p>
137     * 
138     * Note: it's up to the implementation to decide for which
139     * columns it returns a name - most will do so for the
140     * subset with isTestable = true.
141     * 
142     * This implementation delegates to getColumnIdentifierAt and returns it's
143     * toString or null.
144     * 
145     * @param columnIndex in model coordinates
146     * @return column name or null if not found
147     */
148    public String getColumnName(int columnIndex) {
149        Object identifier = getColumnIdentifierAt(columnIndex);
150        return identifier != null ? identifier.toString() : null;
151    }
152
153    
154    /**
155     * Returns logical identifier of the column at 
156     * columnIndex in model coordinates.
157     * 
158     * Note: it's up to the implementation to decide for which
159     * columns it returns an identifier - most will do so for the
160     * subset with isTestable = true.<p>
161     * 
162     * This implementation returns DEFAULT_COLUMN_IDENTIFIER.
163     * 
164     * PENDING JW: This method replaces the old getColumnIdentifier(int)
165     * which returned a String which is overly restrictive.
166     * The only way to gently replace this method was
167     * to add this with a different name - which makes this name suboptimal.
168     * Probably should rename again once the old has died out ;-)
169     * 
170     * @param columnIndex in model coordinates, must be valid.
171     * @return the identifier of the column at columnIndex or null if it has none.
172     * @throws ArrayIndexOutOfBoundsException if columnIndex < 0 or columnIndex >= getColumnCount().
173     *  
174     *  
175     * @see #getColumnIndex(Object)  
176     */
177    public Object getColumnIdentifierAt(int columnIndex) {
178        if ((columnIndex < 0) || (columnIndex >= getColumnCount())) {
179            throw new ArrayIndexOutOfBoundsException("invalid column index: " + columnIndex);
180        }
181        return DEFAULT_COLUMN_IDENTIFIER;
182    }
183
184    /**
185     * Returns the column index in model coordinates for the logical identifier.
186     * <p>
187     * 
188     * This implementation returns 0 if the identifier is the same as the one
189     * known identifier returned from getColumnIdentifierAt(0), or -1 otherwise.
190     * So subclasses with one column and a customizable identifier need not
191     * override. Subclasses which support multiple columns must override this as
192     * well to keep the contract as in (assuming that the lookup succeeded):
193     * 
194     * <pre><code>
195     *  Object id = getColumnIdentifierAt(index);
196     *  assertEquals(index, getColumnIndex(index);
197     *  // and the reverse 
198     *  int column = getColumnIndex(identifier);
199     *  assertEquals(identifier, getColumnIdentifierAt(column));
200     * </code></pre>
201     * 
202     * 
203     * @param identifier the column's identifier, must not be null
204     * @return the index of the column identified by identifier in model
205     *         coordinates or -1 if no column with the given identifier is
206     *         found.
207     * @throws NullPointerException if identifier is null.
208     * @see #getColumnIdentifierAt(int)
209     */
210    public int getColumnIndex(Object identifier) {
211        if (identifier.equals(getColumnIdentifierAt(0))) {
212            return 0;
213        }
214        return -1;
215    }
216
217    /**
218     * Returns true if the column should be included in testing.<p>
219     * 
220     * Here: returns true if visible (that is modelToView gives a valid
221     * view column coordinate). 
222     * 
223     * @param column the column index in model coordinates
224     * @return true if the column should be included in testing
225     */
226    public boolean isTestable(int column) {
227        return convertColumnIndexToView(column) >= 0;
228    }
229    
230    /**
231     * Returns the common class of all data column identified by the given
232     * column index in model coordinates.<p>
233     * 
234     * This implementation returns <code>Object.class</code>. Subclasses should
235     * implement as appropriate.
236     * 
237     * @return the common class of all data given column in model coordinates.
238     * 
239     * @see #getColumnClass()
240     */
241    public Class<?> getColumnClass(int column) {
242        return Object.class;
243    }
244    
245    
246    /**
247     * Returns the common class of all data in the current column.<p>
248     * 
249     * This implementation delegates to getColumnClass(int) with the current
250     * column converted to model coordinates.
251     * 
252     * @return the common class of all data in the current column.
253     * @see #getColumnClass(int)
254     */
255    public Class<?> getColumnClass() {
256        return getColumnClass(convertColumnIndexToModel(column));
257    }
258    
259    
260
261//---------------------------- accessing the target's model: meta data
262    /**
263     * Returns the number of columns in the target's data model.
264     *
265     * @return the number of columns in the target's data model.
266     */
267    public int getColumnCount() {
268        return 1;    // default for combo-boxes, lists, and trees
269    }
270
271    /**
272     * Returns the number of rows in the target's data model.
273     *
274     * @return the number of rows in the target's data model.
275     */
276    public int getRowCount() {
277        return 0;
278    }
279
280//---------------------------- accessing the target's model: data
281    /**
282     * Returns the value of the target component's cell identified by the
283     * specified row and column in model coordinates.
284     *
285     * @param row in model coordinates
286     * @param column in model coordinates
287     * @return the value of the target component's cell identified by the
288     *          specified row and column
289     */
290    public abstract Object getValueAt(int row, int column);
291
292    /**
293     * Determines whether this cell is editable.
294     * 
295     * @param row the row to query in model coordinates
296     * @param column the column to query in model coordinates
297     * @return <code>true</code> if the cell is editable, <code>false</code>
298     *         otherwise
299     */
300    public abstract boolean isCellEditable(int row, int column);
301
302    
303    /**
304     * Returns the String representation of the value of the cell identified by this adapter. That is,
305     * for the at position (adapter.row, adapter.column) in view coordinates.<p>
306     * 
307     * NOTE: this implementation assumes that view coordinates == model 
308     * coordinates, that is simply calls getValueAt(this.row, this.column). It is
309     * up to subclasses to override appropriately is they support model/view
310     * coordinate transformation. <p>
311     * 
312     * This implementation messages the StringValue.TO_STRING with the getValue,
313     * subclasses should re-implement and use the API appropriate for the target component type.
314     * 
315     * @return the String representation of value of the cell identified by this adapter
316     * @see #getValueAt(int, int)
317     * @see #getFilteredValueAt(int, int)
318     * @see #getValue(int)
319     */
320    public String getString() {
321        return getString(convertColumnIndexToModel(column));
322    }
323
324    /**
325     * Returns the String representation of the value of the cell identified by the current 
326     * adapter row and the given column index in model coordinates.<p>
327     * 
328     * @param modelColumnIndex the column index in model coordinates 
329     * @return the String representation of the value of the cell identified by this adapter
330     * 
331     * @see #getFilteredStringAt(int, int)
332     * @see #getString()
333     */
334    public String getString(int modelColumnIndex) {
335        return getFilteredStringAt(row, modelColumnIndex);
336    }
337
338    /**
339     * Returns the String representation of the filtered value of the cell identified by the row
340     * in view coordinate and the column in model coordinates.<p>
341     * 
342     * Note: the asymetry of the coordinates is intentional - clients like
343     * Highlighters are interested in view values but might need to access
344     * non-visible columns for testing. While it is possible to access 
345     * row coordinates different from the current (that is this.row) it is not
346     * safe to do so for row > this.row because the adapter doesn't allow to
347     * query the count of visible rows.<p>
348     * 
349     * This implementation messages the StringValue.TO_STRING with the filteredValue,
350     * subclasses should re-implement and use the API appropriate for the target component type.<p>
351     * 
352     * PENDING JW: what about null cell values? StringValue has a contract to return a 
353     * empty string then, would that be okay here as well?
354     * 
355     * @param row the row of the cell in view coordinates
356     * @param column the column of the cell in model coordinates.
357     * @return the String representation of the filtered value of the cell identified by the row
358     * in view coordinate and the column in model coordinates
359     */
360    public String getFilteredStringAt(int row, int column) {
361        return getStringAt(convertRowIndexToModel(row), column);
362    }
363    
364    /**
365     * Returns the String representation of the value of the cell identified by the row
366     * specified row and column in model coordinates.<p>
367     *
368     * This implementation messages the StringValue.TO_STRING with the valueAt,
369     * subclasses should re-implement and use the api appropriate for the target component type.<p>
370     * 
371     * @param row in model coordinates
372     * @param column in model coordinates
373     * @return the value of the target component's cell identified by the
374     *          specified row and column
375     */
376    public String getStringAt(int row, int column) {
377        return StringValues.TO_STRING.getString(getValueAt(row, column));
378    }
379    
380    /**
381     * Returns the value of the cell identified by this adapter. That is,
382     * for the at position (adapter.row, adapter.column) in view coordinates.<p>
383     * 
384     * NOTE: this implementation assumes that view coordinates == model 
385     * coordinates, that is simply calls getValueAt(this.row, this.column). It is
386     * up to subclasses to override appropriately is they support model/view
387     * coordinate transformation.
388     * 
389     * @return the value of the cell identified by this adapter
390     * @see #getValueAt(int, int)
391     * @see #getFilteredValueAt(int, int)
392     * @see #getValue(int)
393     */
394    public Object getValue() {
395        return getValue(convertColumnIndexToModel(column));
396    }
397
398    
399    /**
400     * Returns the value of the cell identified by the current 
401     * adapter row and the given column index in model coordinates.<p>
402     * 
403     * @param modelColumnIndex the column index in model coordinates 
404     * @return the value of the cell identified by this adapter
405     * @see #getValueAt(int, int)
406     * @see #getFilteredValueAt(int, int)
407     * @see #getValue(int)
408     */
409    public Object getValue(int modelColumnIndex) {
410        return getFilteredValueAt(row, modelColumnIndex);
411    }
412    
413    /**
414     * Returns the filtered value of the cell identified by the row
415     * in view coordinate and the column in model coordinates.
416     * 
417     * Note: the asymmetry of the coordinates is intentional - clients like
418     * Highlighters are interested in view values but might need to access
419     * non-visible columns for testing. While it is possible to access 
420     * row coordinates different from the current (that is this.row) it is not
421     * safe to do so for row > this.row because the adapter doesn't allow to
422     * query the count of visible rows.
423     * 
424     * @param row the row of the cell in view coordinates
425     * @param column the column of the cell in model coordinates.
426     * @return the filtered value of the cell identified by the row
427     * in view coordinate and the column in model coordinates
428     */
429    public Object getFilteredValueAt(int row, int column) {
430        return getValueAt(convertRowIndexToModel(row), column);
431    }
432
433    //----------------------- accessing the target's view state
434
435    /**
436     * Returns the bounds of the cell identified by this adapter.<p>
437     * 
438     * @return the bounds of the cell identified by this adapter
439     */
440    public Rectangle getCellBounds() {
441        return target.getBounds();
442    }
443
444    /**
445     * Returns true if the cell identified by this adapter currently has focus.
446     * Otherwise, it returns false.
447     *
448     * @return true if the cell identified by this adapter currently has focus;
449     *  Otherwise, return false
450     */
451    public abstract boolean hasFocus();
452
453    /**
454     * Returns true if the cell identified by this adapter is currently selected.
455     * Otherwise, it returns false.
456     *
457     * @return true if the cell identified by this adapter is currently selected;
458     *  Otherwise, return false
459     */
460    public abstract boolean isSelected();
461
462    /**
463     * Returns {@code true} if the cell identified by this adapter is editable,
464     * {@code false} otherwise.
465     * 
466     * @return {@code true} if the cell is editable, {@code false} otherwise
467     */
468    public abstract boolean isEditable();
469    
470    /**
471     * Returns true if the cell identified by this adapter is currently expanded.
472     * Otherwise, it returns false. For components that do not support
473     * hierarchical data, this method always returns true because the cells in
474     * such components can never be collapsed.
475     *
476     * @return true if the cell identified by this adapter is currently expanded;
477     *     Otherwise, return false
478     */
479    public boolean isExpanded() {
480        return true; // sensible default for JList and JTable
481    }
482
483    /**
484     * Returns true if the cell identified by this adapter is a leaf node.
485     * Otherwise, it returns false. For components that do not support
486     * hierarchical data, this method always returns true because the cells in
487     * such components can never have children.
488     *
489     * @return true if the cell identified by this adapter is a leaf node;
490     *     Otherwise, return false
491     */
492    public boolean isLeaf() {
493        return true; // sensible default for JList and JTable
494    }
495
496    /**
497     * Returns true if the cell identified by this adapter displays the hierarchical node.
498     * Otherwise, it returns false. For components that do not support
499     * hierarchical data, this method always returns false because the cells in
500     * such components can never have children.
501     *
502     * @return true if the cell identified by this adapter displays the hierarchical node;
503     *  Otherwise, return false
504     */
505    public boolean isHierarchical() {
506        return false; // sensible default for JList and JTable
507    }
508
509    /**
510     * Returns the depth of this row in the hierarchy where the root is 0. For
511     * components that do not contain hierarchical data, this method returns 1.
512     * 
513     * @return the depth for this adapter
514     */
515    public int getDepth() {
516        return 1; // sensible default for JList and JTable
517    }
518 
519//-------------------- cell coordinate transformations
520    
521    /**
522     * For target components that support multiple columns in their model,
523     * along with column reordering in the view, this method transforms the
524     * specified columnIndex from model coordinates to view coordinates. For all
525     * other types of target components, this method returns the columnIndex
526     * unchanged.
527     *
528     * @param columnModelIndex index of a column in model coordinates
529     * @return index of the specified column in view coordinates
530     */
531    public int convertColumnIndexToView(int columnModelIndex) {
532        return columnModelIndex; // sensible default for JList and JTree
533    }
534    
535    /**
536     * For target components that support multiple columns in their model, along
537     * with column reordering in the view, this method transforms the specified
538     * columnIndex from view coordinates to model coordinates. For all other
539     * types of target components, this method returns the columnIndex
540     * unchanged.
541     * 
542     * @param columnViewIndex index of a column in view coordinates
543     * @return index of the specified column in model coordinates
544     */
545    public int convertColumnIndexToModel(int columnViewIndex) {
546        return columnViewIndex; // sensible default for JList and JTree
547    }
548    
549    /**
550     * Converts a row index in model coordinates to an index in view coordinates.
551     *
552     * @param rowModelIndex index of a row in model coordinates
553     * @return index of the specified row in view coordinates
554     */
555    public int convertRowIndexToView(int rowModelIndex) {
556        return rowModelIndex; // sensible default for JTree
557    }
558    
559    /**
560     * Converts a row index in view coordinates to an index in model coordinates.
561     * 
562     * @param rowViewIndex index of a row in view coordinates
563     * @return index of the specified row in model coordinates
564     */
565    public int convertRowIndexToModel(int rowViewIndex) {
566        return rowViewIndex; // sensible default for JTree
567    }
568}