001/* ----------------------------------------------------------------------------
002   The Kiwi Toolkit - A Java Class Library
003   Copyright (C) 1998-2004 Mark A. Lindner
004
005   This library is free software; you can redistribute it and/or
006   modify it under the terms of the GNU General Public License as
007   published by the Free Software Foundation; either version 2 of the
008   License, or (at your option) any later version.
009
010   This library is distributed in the hope that it will be useful,
011   but WITHOUT ANY WARRANTY; without even the implied warranty of
012   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013   General Public License for more details.
014
015   You should have received a copy of the GNU General Public License
016   along with this library; if not, write to the Free Software
017   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
018   02111-1307, USA.
019 
020   The author may be contacted at: mark_a_lindner@yahoo.com
021   ----------------------------------------------------------------------------
022   $Log: KTable.java,v $
023   Revision 1.9  2004/05/31 07:49:44  markl
024   Added isSelectionClearing() method.
025
026   Revision 1.8  2004/03/23 06:49:06  markl
027   New configureColumn() splitup.
028
029   Revision 1.7  2004/01/23 00:01:27  markl
030   added configureColumn() method.
031
032   Revision 1.6  2003/02/06 07:40:31  markl
033   Removed references to Metal Look & Feel.
034
035   Revision 1.5  2003/01/19 09:49:18  markl
036   Updated header cell renderer to display appropriate sort icon.
037
038   Revision 1.4  2001/06/26 06:18:16  markl
039   Bugfix to header construction code.
040
041   Revision 1.3  2001/03/12 09:27:57  markl
042   Source code and Javadoc cleanup.
043   ----------------------------------------------------------------------------
044*/
045
046package kiwi.ui;
047
048import java.awt.*;
049import javax.swing.*;
050import javax.swing.event.*;
051import javax.swing.table.*;
052
053import kiwi.ui.model.*;
054import kiwi.util.*;
055
056/** An extension of <code>JTable</code> that fixes a number of blatant bugs
057 * and limitations in that component.
058 *
059 * <p><center>
060 * <img src="snapshot/KTable.gif"><br>
061 * <i>An example sortable KTable.</i>
062 * </center>
063 *
064 * @author Mark Lindner
065 */
066
067public class KTable extends JTable
068  {
069  private TableSorter sorter;
070  private boolean sortable = false, editable = true;
071  private TableModel realModel = null;
072  private Icon i_sort, i_rsort;
073  private _HeaderRenderer headerRenderer;
074  private boolean columnReordering = false;
075  private boolean internalSelectionChange = false;
076  
077  /** Construct a new <code>KTable</code>.
078   */
079  
080  public KTable()
081    {
082    i_sort = KiwiUtils.getResourceManager().getIcon("sort-down.gif");
083    i_rsort = KiwiUtils.getResourceManager().getIcon("sort-up.gif");
084    
085    setAutoCreateColumnsFromModel(true);
086    
087    setShowHorizontalLines(false);
088    setShowVerticalLines(false);
089    setShowGrid(false);
090
091    setSelectionModel(new _SelectionModel());
092
093    setTableHeader(new _TableHeader(getColumnModel()));
094
095    setAutoResizeMode(AUTO_RESIZE_ALL_COLUMNS);
096    sizeColumnsToFit(AUTO_RESIZE_ALL_COLUMNS);
097    }
098
099  /**
100   * This method is overridden to intercept
101   * <code>TableModelEvents</code>.  These events cause the selection
102   * to be cleared as a side-effect; this situation is sometimes of
103   * interest to application code, and may be tested via a call to
104   * <code>isSelectionClearing()</code>.
105   */
106
107  public void tableChanged(TableModelEvent event)
108    {
109    internalSelectionChange = true;
110    super.tableChanged(event);
111    internalSelectionChange = false;
112    }
113
114  /**
115   * Determine if the selection on this table is clearing as a result of a
116   * <code>TableModelEvent</code>.
117   *
118   * @since Kiwi 2.0
119   */
120  
121  public boolean isSelectionClearing()
122    {
123    return(internalSelectionChange);
124    }
125
126  /**
127   */
128  
129  public void createDefaultColumnsFromModel()
130    {
131    super.createDefaultColumnsFromModel();
132
133    prepareHeader();
134    }
135
136  /** Get the row translation for a given row in the table. If sorting is
137   * turned on, the visual row may not be the same as the row in the underlying
138   * data model; this method obtains the model row corresponding to the
139   * specified visual row, whether sorting is turned on or not.
140   *
141   * @param row The visual row.
142   * @return The corresponding row in the data model.
143   */
144
145  public int getRowTranslation(int row)
146    {
147    return(sortable ? sorter.getRowTranslation(row) : row);
148    }
149  
150  /** Set the data model for this table.
151   *
152   * @param model The new model.
153   */
154  
155  public void setModel(TableModel model)
156    {
157    realModel = model;
158    if(sortable)
159      {
160      sorter = new TableSorter(realModel);
161      super.setModel(sorter);
162      sorter.registerTableHeaderListener(this);
163      }
164    else
165      super.setModel(realModel);
166    }
167
168  /** Prepare the header to use the custom header renderer.
169   */
170
171  private void prepareHeader()
172    {
173    TableColumnModel cmodel = getColumnModel();
174    headerRenderer = new _HeaderRenderer();
175    
176    int cols = cmodel.getColumnCount();
177
178    for(int i = 0; i < cols; i++)
179      {
180      TableColumn tc = cmodel.getColumn(i);
181      tc.setHeaderRenderer(headerRenderer);
182      }
183    }
184
185  /** Enable or disable this table. A disabled table cannot be edited or
186   * sorted, nor can the selection be changed, nor the columns reordered or
187   * resized.
188   *
189   * @param enabled A flag specifying whether this table should be enabled
190   * or disabled.
191   */
192
193  public void setEnabled(boolean enabled)
194    {
195    Color fg = (enabled
196                ? UIManager.getColor("Table.selectionForeground")
197                : UIManager.getColor("Label.disabledForeground"));
198    
199    setForeground(fg);
200    setSelectionForeground(fg);
201    super.setEnabled(enabled);
202    getTableHeader().setEnabled(enabled);
203    
204    }
205
206  /** Set the editable state of this table.
207   *
208   * @param editable A flag specifying whether this table is editable.
209   */
210  
211  public void setEditable(boolean editable)
212    {
213    this.editable = editable;
214    }
215
216  /** Determine if the table is editable.
217   *
218   * @return <code>true</code> if the table is editable and <code>false</code>
219   * otherwise.
220   */
221  
222  public boolean isEditable()
223    {
224    return(editable);
225    }
226
227  /** Set the sortable state of this table.
228   *
229   * @param sortable A flag specifying whether this table is sortable.
230   */
231  
232  public void setSortable(boolean sortable)
233    {
234    if(this.sortable == sortable)
235      return;
236    
237    this.sortable = sortable;
238
239    if(sortable)
240      {
241      if(realModel != null)
242        {
243        sorter = new TableSorter(realModel);
244        super.setModel(sorter);
245        sorter.registerTableHeaderListener(this);
246        }
247      }
248    else
249      {
250      if(realModel != null)
251        super.setModel(realModel);
252      sorter = null;
253      }
254
255    prepareHeader();    
256    }
257
258  /** Determine if the table is sortable.
259   *
260   * @return <code>true</code> if the table is sortable and <code>false</code>
261   * otherwise.
262   */
263  
264  public boolean isSortable()
265    {
266    return(sortable);
267    }  
268
269  /** Determine if a cell is editable. Editability of a given cell is
270   * ultimately determined by the table model, unless the table has been
271   * made non-editable via a call to <code>setEditable()</code>, or if it has
272   * been disabled via a call to <code>setEnabled()</code>.
273   *
274   * @param row The row of the cell.
275   * @param col The column of the cell.
276   * @return <code>true</code> if the cell at the specified coordinates is
277   * editable, or <code>false</code> if it is not editable or if the table has
278   * been made non-editable.
279   * @see #setEditable
280   */
281
282  public boolean isCellEditable(int row, int col)
283    {
284    if(!isEnabled())
285      return(false);
286    
287    return(editable ? getModel().isCellEditable(row, col) : false);
288    }
289
290  /** Scroll the table to ensure that a given row is visible.
291   *
292   * @param row The row that must be visible.
293   */
294  
295  public void ensureRowIsVisible(int row)
296    {
297    Rectangle r = getCellRect(row, 0, false);
298    r.y = ((rowHeight + rowMargin) * row) + (getSize().height / 2);
299    scrollRectToVisible(r);
300    }
301
302  /** Stop any cell edit that is in progress on the table. */
303
304  public void stopEditing()
305    {
306    int row = getEditingRow();
307    int col = getEditingColumn();
308
309    getCellEditor(row, col).stopCellEditing();
310    }
311
312  /** Configure a column in the table with width attributes, a cell renderer,
313   * and a cell editor. Columns should be configured <i>after</i> a model has
314   * been set for the table.
315   *
316   * @param column The column to configure.
317   * @param width The preferred width for the column.
318   * @param minWidth The minimum width for the column.
319   * @param maxWidth The maximum width for the column.
320   * @param renderer The cell renderer for the column.
321   * @param editor The cell editor for the column.
322   */
323
324  public void configureColumn(int column, int width, int minWidth,
325                              int maxWidth, TableCellRenderer renderer,
326                              TableCellEditor editor)
327    {
328    configureColumn(column, width, minWidth, maxWidth);
329    configureColumn(column, renderer, editor);
330    }
331
332  /** Configure a column in the table with a cell renderer and cell
333   * editor. Columns should be configured <i>after</i> a model has
334   * been set for the table.
335   *
336   * @param column The column to configure.
337   * @param renderer The cell renderer for the column.
338   * @param editor The cell editor for the column.
339   *
340   * @since Kiwi 2.0
341   */
342  
343  public void configureColumn(int column, TableCellRenderer renderer,
344                              TableCellEditor editor)
345    {
346    TableColumnModel cmodel = getColumnModel();
347    TableColumn tc = cmodel.getColumn(column);
348    if(tc != null)
349      {
350      tc.setCellRenderer(renderer);
351      tc.setCellEditor(editor);
352      }
353    }
354
355  /** Configure a column in the table with width attributes. Columns
356   * should be configured <i>after</i> a model has been set for the
357   * table.
358   *
359   * @param column The column to configure.
360   * @param width The preferred width for the column.
361   * @param minWidth The minimum width for the column.
362   * @param maxWidth The maximum width for the column.
363   *
364   * @since Kiwi 2.0
365   */
366  
367  public void configureColumn(int column, int width, int minWidth,
368                              int maxWidth)
369    {
370    TableColumnModel cmodel = getColumnModel();
371    TableColumn tc = cmodel.getColumn(column);
372    if(tc != null)
373      {
374      tc.setPreferredWidth(width);
375      tc.setMinWidth(minWidth);
376      tc.setMaxWidth(maxWidth);
377      }
378    }
379  
380  /* A custom list selection model that honors disabled state by disallowing
381   * changes to the current selection(s).
382   */
383
384  private class _SelectionModel extends DefaultListSelectionModel
385    {
386    public void clearSelection()
387      {
388      if(isEnabled())
389        super.clearSelection();
390      }
391    
392    public void addSelectionInterval(int a, int b)
393      {
394      if(isEnabled())
395        super.addSelectionInterval(a, b);
396      }
397    
398    public void insertIndexInterval(int a, int b, boolean before)
399      {
400      if(isEnabled())
401        super.insertIndexInterval(a, b, before);
402      }
403    
404    public void removeIndexInterval(int a, int b)
405      {
406      if(isEnabled())
407        super.removeIndexInterval(a, b);
408      }
409
410    public void setSelectionInterval(int a, int b)
411      {
412      if(isEnabled())
413        super.setSelectionInterval(a, b);
414      }
415
416    public void setAnchorSelectionIndex(int a)
417      {
418      if(isEnabled())
419        super.setAnchorSelectionIndex(a);
420      }
421
422    public void setLeadSelectionIndex(int a)
423      {
424      if(isEnabled())
425        super.setLeadSelectionIndex(a);
426      }
427    }
428
429  /* A custom header renderer that displays a sort icon in each column if the
430   * table is sortable.
431   */
432  
433  private class _HeaderRenderer extends HeaderCellRenderer
434    {
435    public Component getTableCellRendererComponent(JTable table,
436                                                   Object value,
437                                                   boolean isSelected,
438                                                   boolean hasFocus,
439                                                   int row,
440                                                   int column)
441      {
442      if(isSortable())
443        {
444        int sortCol = sorter.getSortedColumn();
445        if((sortCol < 0) || (sortCol != column))
446          setIcon(null);
447        else
448          setIcon(sorter.isSortedAscending() ? i_sort : i_rsort);
449        }
450      else
451        setIcon(null);
452      
453      return(super.getTableCellRendererComponent(table, value, isSelected,
454                                                 hasFocus, row, column));
455      } 
456    }
457
458  /* A custom table header that honors disabled state by disallowing column
459   * resizing and reordering.
460   */
461
462  private class _TableHeader extends JTableHeader
463    {
464
465    public _TableHeader(TableColumnModel model)
466      {
467      super(model);
468      }
469    
470    public boolean getResizingAllowed()
471      {
472      return(isEnabled() ? super.getResizingAllowed() : false);
473      }
474
475    public boolean getReorderingAllowed()
476      {
477      return(isEnabled() ? super.getReorderingAllowed() : false);
478      }    
479    }
480
481  }
482
483/* end of source file */