KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > jdesktop > swing > JXTable


1 /*
2  * $Id: JXTable.java,v 1.16 2005/02/22 17:14:59 kleopatra Exp $
3  *
4  * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
5  * Santa Clara, California 95054, U.S.A. All rights reserved.
6  */

7
8 package org.jdesktop.swing;
9
10 import java.text.MessageFormat JavaDoc;
11 import java.util.ArrayList JavaDoc;
12 import java.util.Date JavaDoc;
13 import java.util.Enumeration JavaDoc;
14 import java.util.Iterator JavaDoc;
15 import java.util.List JavaDoc;
16 import java.util.Hashtable JavaDoc;
17 import java.util.Timer JavaDoc;
18 import java.util.TimerTask JavaDoc;
19 import java.util.Vector JavaDoc;
20 import java.util.regex.Pattern JavaDoc;
21 import javax.print.attribute.HashPrintRequestAttributeSet JavaDoc;
22 import javax.print.attribute.PrintRequestAttributeSet JavaDoc;
23
24 import java.awt.Color JavaDoc;
25 import java.awt.Component JavaDoc;
26 import java.awt.Dimension JavaDoc;
27 import java.awt.EventQueue JavaDoc;
28 import java.awt.FontMetrics JavaDoc;
29 import java.awt.Graphics JavaDoc;
30 import java.awt.Point JavaDoc;
31 import java.awt.Rectangle JavaDoc;
32 import java.awt.event.*;
33 import java.awt.print.Printable JavaDoc;
34 import java.awt.print.PrinterException JavaDoc;
35 import java.awt.print.PrinterJob JavaDoc;
36
37 import java.text.DateFormat JavaDoc;
38 import java.text.NumberFormat JavaDoc;
39
40 import javax.swing.ActionMap JavaDoc;
41 import javax.swing.Icon JavaDoc;
42 import javax.swing.ImageIcon JavaDoc;
43 import javax.swing.JCheckBox JavaDoc;
44 import javax.swing.JLabel JavaDoc;
45 import javax.swing.JTable JavaDoc;
46 import javax.swing.JViewport JavaDoc;
47 import javax.swing.ListSelectionModel JavaDoc;
48 import javax.swing.UIDefaults JavaDoc;
49 import javax.swing.event.TableModelEvent JavaDoc;
50 import javax.swing.table.DefaultTableCellRenderer JavaDoc;
51 import javax.swing.table.JTableHeader JavaDoc;
52 import javax.swing.table.TableCellRenderer JavaDoc;
53 import javax.swing.table.TableColumn JavaDoc;
54 import javax.swing.table.TableColumnModel JavaDoc;
55 import javax.swing.table.TableModel JavaDoc;
56
57 import org.jdesktop.swing.data.Link;
58 import org.jdesktop.swing.data.MetaData;
59 import org.jdesktop.swing.data.MetaDataProvider;
60 import org.jdesktop.swing.data.DefaultTableModelExt;
61
62 import org.jdesktop.swing.table.ColumnHeaderRenderer;
63 import org.jdesktop.swing.table.TableColumnExt;
64 import org.jdesktop.swing.table.TableColumnModelExt;
65
66 import org.jdesktop.swing.decorator.ComponentAdapter;
67 import org.jdesktop.swing.decorator.FilterPipeline;
68 import org.jdesktop.swing.decorator.HighlighterPipeline;
69 import org.jdesktop.swing.decorator.PipelineEvent;
70 import org.jdesktop.swing.decorator.PipelineListener;
71 import org.jdesktop.swing.decorator.Sorter;
72 import org.jdesktop.swing.table.DefaultTableColumnModelExt;
73
74 /**
75  * JXTable
76  *
77  * @author Ramesh Gupta
78  * @author Amy Fowler
79  * @author Mark Davidson
80  */

81 public class JXTable extends JTable JavaDoc implements PipelineListener, Searchable {
82
83 public static boolean TRACE = false;
84     /**
85      * Printing mode that prints the table at its current size,
86      * spreading both columns and rows across multiple pages if necessary.
87      */

88     public static final int PRINT_MODE_NORMAL = 0;
89
90     /**
91      * Printing mode that scales the output smaller, if necessary,
92      * to fit the table's entire width (and thereby all columns) on each page;
93      * Rows are spread across multiple pages as necessary
94      */

95     public static final int PRINT_MODE_FIT_WIDTH = 1;
96
97     protected Sorter sorter = null;
98     protected FilterPipeline filters = null;
99     protected HighlighterPipeline highlighters = null;
100
101     // MUST ALWAYS ACCESS dataAdapter through accessor method!!!
102
private final ComponentAdapter dataAdapter = new TableAdapter(this);
103
104     // No need to define a separate JTableHeader subclass!
105
private final static MouseAdapter headerListener = new MouseAdapter() {
106         // MouseAdapter must be stateless
107
public void mouseClicked(MouseEvent e) {
108             JTableHeader JavaDoc header = (JTableHeader JavaDoc) e.getSource();
109             JXTable table = (JXTable) header.getTable();
110             if (!table.isSortable()) return;
111             if ((e.getModifiersEx() & e.SHIFT_DOWN_MASK) == e.SHIFT_DOWN_MASK) {
112                 table.resetSorter();
113             }
114             else {
115                 
116                 int column = header.getColumnModel().getColumnIndexAtX(e.getX());
117                 if (column >= 0) {
118                     table.setSorter(column);
119                 }
120             }
121             header.repaint();
122         }
123     };
124
125     /**
126      * A flag to indicate whether or not the table is currently being printed.
127      * Used by print() and prepareRenderer() to disable indication of the
128      * selection and focused cell while printing.
129      */

130     private transient boolean isPrinting = false;
131
132     private boolean sortable = false;
133     private int visibleRowCount = 18;
134
135     public JXTable() {
136         init();
137     }
138
139     public JXTable(TableModel JavaDoc dm) {
140         super(dm);
141         init();
142     }
143
144     public JXTable(TableModel JavaDoc dm, TableColumnModel JavaDoc cm) {
145         super(dm, cm);
146         init();
147     }
148
149     public JXTable(TableModel JavaDoc dm, TableColumnModel JavaDoc cm, ListSelectionModel JavaDoc sm) {
150         super(dm, cm, sm);
151         init();
152     }
153
154     public JXTable(int numRows, int numColumns) {
155         super(numRows, numColumns);
156         init();
157     }
158
159     public JXTable(Vector JavaDoc rowData, Vector JavaDoc columnNames) {
160         super(rowData, columnNames);
161         init();
162     }
163
164     public JXTable(Object JavaDoc[][] rowData, Object JavaDoc[] columnNames) {
165         super(rowData, columnNames);
166         init();
167     }
168
169     protected void init() {
170         setSortable(true);
171         // Register the actions that this class can handle.
172
ActionMap JavaDoc map = getActionMap();
173         map.put("print", new Actions("print"));
174         map.put("find", new Actions("find"));
175
176         // Add a link handler to to the table.
177
// XXX note: this listener represents overhead if no columns are links.
178
// Beter to detect if the table has a link column and add the handler.
179
LinkHandler handler = new LinkHandler();
180         addMouseListener(handler);
181         addMouseMotionListener(handler);
182     }
183
184
185     /**
186      * Returns a new instance of the default renderer for the specified class.
187      * This differs from <code>getDefaultRenderer()</code> in that it returns a <b>new</b>
188      * instance each time so that the renderer may be set and customized on
189      * a particular column.
190      *
191      * @param columnClass Class of value being rendered
192      * @return TableCellRenderer instance which renders values of the specified type
193      */

194     public TableCellRenderer JavaDoc getNewDefaultRenderer(Class JavaDoc columnClass) {
195         TableCellRenderer JavaDoc renderer = getDefaultRenderer(columnClass);
196         if (renderer != null) {
197             try {
198                 return (TableCellRenderer JavaDoc) renderer.getClass().newInstance();
199             } catch (Exception JavaDoc e) {
200                 e.printStackTrace();
201             }
202         }
203         return null;
204     }
205
206     private void setLazyValue(Hashtable JavaDoc h, Class JavaDoc c, String JavaDoc s) {
207         h.put(c, new UIDefaults.ProxyLazyValue JavaDoc(s));
208     }
209
210     private void setLazyRenderer(Class JavaDoc c, String JavaDoc s) {
211         setLazyValue(defaultRenderersByColumnClass, c, s);
212     }
213
214     /**
215      * Creates default cell renderers for objects, numbers, doubles, dates,
216      * booleans, icons, and links.
217      *
218      */

219     protected void createDefaultRenderers() {
220         // super.createDefaultRenderers();
221
// This duplicates JTable's functionality in order to make the renderers
222
// available in getNewDefaultRenderer(); If JTable's renderers either
223
// were public, or it provided a factory for *new* renderers, this would
224
// not be needed
225

226         defaultRenderersByColumnClass = new UIDefaults JavaDoc();
227
228         // Objects
229
setLazyRenderer(Object JavaDoc.class,
230                         "javax.swing.table.DefaultTableCellRenderer");
231
232         // Numbers
233
setLazyRenderer(Number JavaDoc.class, "org.jdesktop.swing.JXTable$NumberRenderer");
234
235         // Doubles and Floats
236
setLazyRenderer(Float JavaDoc.class, "org.jdesktop.swing.JXTable$DoubleRenderer");
237         setLazyRenderer(Double JavaDoc.class, "org.jdesktop.swing.JXTable$DoubleRenderer");
238
239         // Dates
240
setLazyRenderer(Date JavaDoc.class, "org.jdesktop.swing.JXTable$DateRenderer");
241
242         // Icons and ImageIcons
243
setLazyRenderer(Icon JavaDoc.class, "org.jdesktop.swing.JXTable$IconRenderer");
244         setLazyRenderer(ImageIcon JavaDoc.class, "org.jdesktop.swing.JXTable$IconRenderer");
245
246         // Booleans
247
setLazyRenderer(Boolean JavaDoc.class, "org.jdesktop.swing.JXTable$BooleanRenderer");
248
249         // Other
250
setLazyRenderer(Link.class, "org.jdesktop.swing.JXTable$LinkRenderer");
251     }
252
253
254     /**
255      * A small class which dispatches actions.
256      * TODO: Is there a way that we can make this static?
257      */

258     private class Actions extends UIAction {
259         Actions(String JavaDoc name) {
260             super(name);
261         }
262
263         public void actionPerformed(ActionEvent evt) {
264             if ("print".equals(getName())) {
265                 try {
266                     print();
267                 }
268                 catch (PrinterException JavaDoc ex) {
269                     //REMIND(aim): should invoke pluggable application error handler
270
ex.printStackTrace();
271                 }
272             }
273             else if ("find".equals(getName())) {
274                 find();
275             }
276         }
277     }
278
279     private JXFindDialog dialog = null;
280     private boolean automaticSortDisabled;
281
282     private void find() {
283         if (dialog == null) {
284             dialog = new JXFindDialog(this);
285         }
286         dialog.setVisible(true);
287     }
288
289     /**
290      * Sets &quot;sortable&quot; property indicating whether or not this table
291          * supports sortable columns. If <code>sortable</code> is <code>true</code>
292      * then sorting will be enabled on all columns whose <code>sortable</code>
293          * property is <code>true</code>. If <code>sortable</code> is <code>false</code>
294          * then sorting will be disabled for all columns, regardless of each column's
295          * individual <code>sorting</code> property. The default is <code>true</code>.
296      * @see TableColumnExt#isSortable
297      * @see TableColumnExt#setSortable
298      * @param sortable boolean indicating whether or not this table supports
299      * sortable columns
300      */

301     public void setSortable(boolean sortable) {
302         if (sortable == isSortable()) return;
303         this.sortable = sortable;
304         firePropertyChange("sortable", !sortable, sortable);
305         //JW @todo: this is a hack!
306
if (sorter != null) {
307            contentsChanged(null);
308         }
309         
310     }
311     
312     public boolean isSortable() {
313         return sortable;
314     }
315
316     
317 // public void setAutomaticSort(boolean automaticEnabled) {
318
// this.automaticSortDisabled = !automaticEnabled;
319
//
320
// }
321
//
322
// public boolean isAutomaticSort() {
323
// return !automaticSortDisabled;
324
// }
325

326     public void tableChanged(TableModelEvent JavaDoc e) {
327         Selection selection = new Selection(this);
328         if (filters != null) {
329             filters.flush(); // will call contentsChanged()
330
}
331         else if (sorter != null) {
332             sorter.refresh();
333 // if (isAutomaticSort()) {
334
// sorter.refresh();
335
// }
336
}
337         
338         super.tableChanged(e);
339         restoreSelection(selection);
340     }
341
342     public void contentsChanged(PipelineEvent e) {
343         removeSorter();
344         clearSelection();
345
346         // Force private rowModel in JTable to null;
347
setRowHeight(getRowHeight()); // Ugly!
348

349         revalidate();
350         repaint();
351     }
352
353     public int getRowCount() {
354         int count;
355         if (filters == null) {
356             count = getModel().getRowCount();
357         }
358         else {
359             count = filters.getOutputSize();
360         }
361         return count;
362     }
363
364     /**
365      * Convert row index from view coordinates to model coordinates
366      * accounting for the presence of sorters and filters.
367      *
368      * @param row row index in view coordinates
369      * @return row index in model coordinates
370      */

371     public int convertRowIndexToModel(int row) {
372         if (sorter == null) {
373             if (filters == null) {
374                 return row;
375             }
376             else {
377                 // delegate conversion to the filters pipeline
378
return filters.convertRowIndexToModel(row);
379             }
380         }
381         else {
382             // after performing its own conversion, the sorter
383
// delegates the conversion to the filters pipeline, if any
384
return sorter.convertRowIndexToModel(row);
385         }
386     }
387
388     /**
389      * Convert row index from model coordinates to view coordinates
390      * accounting for the presence of sorters and filters.
391      *
392      * @param row row index in model coordinates
393      * @return row index in view coordinates
394      */

395     public int convertRowIndexToView(int row) {
396         if (sorter == null) {
397             if (filters == null) {
398                 return row;
399             }
400             else {
401                 // delegate conversion to the filters pipeline
402
return filters.convertRowIndexToView(row);
403             }
404         }
405         else {
406             // before performing its own conversion, the sorter
407
// delegates the conversion to the filters pipeline, if any
408
return sorter.convertRowIndexToView(row);
409         }
410     }
411
412     /**
413      * {@inheritDoc}
414      */

415     public Object JavaDoc getValueAt(int row, int column) {
416         if (sorter == null) { // have interactive sorter?
417
if (filters == null) { // have filter pipeline?
418
return super.getValueAt(row, column); // unsorted, unfiltered
419
}
420             else { // filtered
421
return filters.getValueAt(row, convertColumnIndexToModel(column));
422             }
423         }
424         else { // interactively sorted, and potentially filtered
425
return sorter.getValueAt(row, convertColumnIndexToModel(column));
426         }
427     }
428
429     public void setValueAt(Object JavaDoc aValue, int row, int column) {
430         if (sorter == null) {
431             if (filters == null) {
432                 super.setValueAt(aValue, row, column);
433             }
434             else {
435                 filters.setValueAt(aValue, row, convertColumnIndexToModel(column));
436             }
437         }
438         else {
439             sorter.setValueAt(aValue, row, convertColumnIndexToModel(column));
440         }
441     }
442
443     public boolean isCellEditable(int row, int column) {
444         if (sorter == null) {
445             if (filters == null) {
446                 return super.isCellEditable(row, column);
447             }
448             else {
449                 return filters.isCellEditable(row, convertColumnIndexToModel(column));
450             }
451         }
452         else {
453             return sorter.isCellEditable(row, convertColumnIndexToModel(column));
454         }
455     }
456
457     public void setModel(TableModel JavaDoc newModel) {
458         //JW: need to clear here because super.setModel
459
// calls tableChanged...
460
// fixing #173
461
clearSelection();
462         super.setModel(newModel);
463         use(filters);
464     }
465
466
467     /**
468      * Adds private mouse listener to the table header (for sorting support)
469      * before handing it off to the super class for processing.
470      *
471      * @param tableHeader
472      */

473     public void setTableHeader(JTableHeader JavaDoc tableHeader) {
474         // This method is also called during construction of JTable
475
if (tableHeader != null) {
476             tableHeader.addMouseListener(headerListener);
477             tableHeader.setDefaultRenderer(new ColumnHeaderRenderer());
478         }
479         super.setTableHeader(tableHeader);
480     }
481
482 /*
483     protected JTableHeader createDefaultTableHeader() {
484         return new JTableHeaderExt(columnModel);
485     }
486 */

487
488     protected TableColumnModel JavaDoc createDefaultColumnModel() {
489         return new DefaultTableColumnModelExt();
490     }
491
492     private void restoreSelection(Selection selection) {
493         clearSelection(); // call overridden version
494

495         for (int i = 0; i < selection.selected.length; i++) {
496             // JW: make sure we convert valid row indices (in model coordinates) only
497
// fix #16
498
if ((selection.selected[i] != selection.lead) && (selection.selected[i] < getModel().getRowCount())) {
499                 int index = convertRowIndexToView(selection.selected[i]);
500                 selectionModel.addSelectionInterval(index, index);
501             }
502         }
503
504         // JW: make sure we convert valid row indices (in model coordinates) only
505
// fix #16
506
if ((selection.lead >= 0) && (selection.lead < getModel().getRowCount())) {
507             selection.lead = convertRowIndexToView(selection.lead);
508             selectionModel.addSelectionInterval(selection.lead, selection.lead);
509         }
510     }
511
512     public FilterPipeline getFilters() {
513         return filters;
514     }
515
516     /**
517      * setModel() and setFilters() may be called in either order.
518      *
519      * @param pipeline
520      */

521     private void use(FilterPipeline pipeline) {
522         if (pipeline != null) {
523             // check JW: adding listener multiple times (after setModel)?
524
if (initialUse(pipeline)) {
525                 pipeline.addPipelineListener(this);
526                 pipeline.assign(getComponentAdapter());
527             }
528             pipeline.flush();
529         }
530     }
531     
532     /**
533      *
534      * @param pipeline must be != null
535      * @return true is not yet used in this JXTable, false otherwise
536      */

537     private boolean initialUse(FilterPipeline pipeline) {
538         PipelineListener[] l = pipeline.getPipelineListeners();
539         for (int i = 0; i < l.length; i++) {
540             if (this.equals(l[i])) return false;
541         }
542         return true;
543     }
544
545     public void setFilters(FilterPipeline pipeline) {
546         unsetFilters();
547         doSetFilters(pipeline);
548     }
549
550     private void unsetFilters() {
551         if (filters == null) return;
552         // fix#125: cleanup old filters
553
filters.removePipelineListener(this);
554         // hacking around -
555
//brute force update of sorter by removing
556
contentsChanged(null);
557     }
558
559     private void doSetFilters(FilterPipeline pipeline) {
560         filters = pipeline;
561         use(filters);
562     }
563
564     public HighlighterPipeline getHighlighters() {
565         return highlighters;
566     }
567
568     public void setHighlighters(HighlighterPipeline pipeline) {
569         highlighters = pipeline;
570     }
571
572     private void removeSorter() {
573 // // fixing #167: remove from pipeline
574
// // moved to refreshSorter
575
// if (sorter != null) {
576
// sorter.interpose(null, getComponentAdapter(), null);
577
// }
578
sorter = null;
579         getTableHeader().repaint();
580     }
581     
582     
583     /*
584      * Used by headerListener
585      */

586     protected void resetSorter() {
587         if (sorter != null) {
588             Selection selection = new Selection(this);
589             removeSorter();
590             restoreSelection(selection);
591         }
592     }
593
594     private Sorter refreshSorter(int columnIndex) {
595         TableColumn JavaDoc col = getColumnModel().getColumn(columnIndex);
596         if (col instanceof TableColumnExt) {
597             TableColumnExt column = (TableColumnExt) col;
598             Sorter newSorter = column.getSorter();
599             if (newSorter != null) {
600                 // JW: hacking around #167: un-assign from filters
601
// this should be done somewhere else!
602
newSorter.interpose(null, getComponentAdapter(), null);
603                 // filter pipeline may be null!
604
newSorter.interpose(filters, getComponentAdapter(), sorter); // refresh
605
return newSorter;
606             }
607         }
608         return sorter;
609     }
610
611     /*
612      * Used by headerListener
613      */

614     protected void setSorter(int columnIndex) {
615         Selection selection = new Selection(this);
616         if (sorter == null) {
617             sorter = refreshSorter(columnIndex); // create and refresh
618
}
619         else {
620             int modelColumnIndex = convertColumnIndexToModel(columnIndex);
621             if (sorter.getColumnIndex() == modelColumnIndex) {
622                 sorter.toggle();
623             }
624             else {
625                 sorter = refreshSorter(columnIndex); // create and refresh
626
}
627         }
628         restoreSelection(selection);
629     }
630
631     /*
632      * Used by ColumnHeaderRenderer.getTableCellRendererComponent()
633      */

634     public Sorter getSorter(int columnIndex) {
635         return sorter == null ? null :
636             sorter.getColumnIndex() == convertColumnIndexToModel(columnIndex) ?
637             sorter : null;
638     }
639
640     /**
641      * Remove all columns
642      */

643     protected void removeColumns() {
644         /** @todo promote this method to superclass, and
645          * change createDefaultColumnsFromModel() to call this method */

646         TableColumnModel JavaDoc cm = getColumnModel();
647         while (cm.getColumnCount() > 0) {
648             cm.removeColumn(cm.getColumn(0));
649         }
650     }
651
652     public List JavaDoc getColumns() {
653         return null; /** @todo Implement this */
654     }
655
656     /**
657      * Returns the <code>TableColumn</code> object for the column in the table
658      * whose identifier is equal to <code>identifier</code>, when compared using
659      * <code>equals</code>.
660      *
661      * @return the <code>TableColumn</code> object that matches the identifier
662      * @exception IllegalArgumentException
663      * if <code>identifier</code> is <code>null</code>
664      * or no <code>TableColumn</code> has this identifier
665      *
666      * @param identifier the identifier object
667      */

668
669     public TableColumnExt getColumnExt(Object JavaDoc identifier) {
670         return (TableColumnExt)super.getColumn(identifier);
671     }
672
673     public TableColumnExt getColumnExt(int viewColumnIndex) {
674         return (TableColumnExt) getColumnModel().getColumn(viewColumnIndex);
675     }
676
677     /**
678      * Sets &quot;sortable&quot; property indicating whether or not this table
679          * supports sortable columns. If <code>sortable</code> is <code>true</code>
680      * then sorting will be enabled on all columns whose <code>sortable</code>
681          * property is <code>true</code>. If <code>sortable</code> is <code>false</code>
682          * then sorting will be disabled for all columns, regardless of each column's
683          * individual <code>sorting</code> property. The default is <code>true</code>.
684      * @see TableColumnExt#isSortable
685      * @see TableColumnExt#setSortable
686      * @param sortable boolean indicating whether or not this table supports
687      * sortable columns
688      */

689     /* public void setSortable(boolean sortable) {
690             boolean old = this.sortable;
691             this.sortable = sortable;
692             if (!old && sortable) {
693                 TableModel model = getModel();
694                 if (model != null) {
695                     // wrap model
696                     super.setModel(new TableSorter(getModel()));
697                 }
698                 setTableHeader(new SortableTableHeader(getColumnModel()));
699             } else if (old && !sortable) {
700                 TableModel model = getModel();
701                 if (model instanceof TableSorter) {
702                     // unwrap model
703                     super.setModel(((TableSorter)model).getModel());
704                 }
705                 setTableHeader(createDefaultTableHeader());
706             }
707             firePropertyChange("sortable", old, sortable);
708         }
709         public boolean isSortable() {
710             return sortable;
711         }
712      */

713
714     /**
715      * Returns the default table model object, which is
716      * a <code>DefaultTableModel</code>. A subclass can override this
717      * method to return a different table model object.
718      *
719      * @return the default table model object
720      * @see org.jdesktop.swing.table.DefaultTableModelExt
721      */

722     protected TableModel JavaDoc createDefaultDataModel() {
723         return new DefaultTableModelExt();
724     }
725
726     public void createDefaultColumnsFromModel() {
727         TableModel JavaDoc model = getModel();
728         if (model != null) {
729             // Create new columns from the data model info
730
// Note: it's critical to create the new columns before
731
// deleting the old ones. Why?
732
int modelColumnCount = model.getColumnCount();
733             TableColumn JavaDoc newColumns[] = new TableColumn JavaDoc[modelColumnCount];
734             for (int i = 0; i < newColumns.length; i++) {
735                 newColumns[i] = createColumn(i);
736             }
737
738             if (model instanceof MetaDataProvider) {
739                 MetaData metaData[] = ((MetaDataProvider)model).getMetaData();
740                 for(int i = 0; i < metaData.length; i++) {
741                     newColumns[i].setIdentifier(metaData[i].getName());
742                     newColumns[i].setHeaderValue(metaData[i].getLabel());
743                 }
744             }
745
746             // Remove any current columns
747
TableColumnModel JavaDoc columnModel = getColumnModel();
748             while (columnModel.getColumnCount() > 0) {
749                 columnModel.removeColumn(columnModel.getColumn(0));
750             }
751
752             // Now add the new columns to the column model
753
for (int i = 0; i < newColumns.length; i++) {
754                 addColumn(newColumns[i]);
755             }
756         }
757     }
758
759     protected TableColumn JavaDoc createColumn(int modelIndex) {
760         return new TableColumnExt(modelIndex);
761     }
762
763
764     /**
765      * Returns the margin between columns.
766      *
767      * @return the margin between columns
768      */

769     public int getColumnMargin() {
770         return getColumnModel().getColumnMargin();
771     }
772
773     /**
774      * Sets the margin between columns.
775      *
776          * @param value margin between columns; must be greater than or equal to zero.
777      */

778     public void setColumnMargin(int value) {
779         getColumnModel().setColumnMargin(value);
780     }
781
782     /**
783      * Returns the selection mode used by this table's selection model.
784      *
785      * @return the selection mode used by this table's selection model
786      */

787     public int getSelectionMode() {
788         return getSelectionModel().getSelectionMode();
789     }
790
791     /**
792      * Returns the decorated <code>Component</code> used as a stamp to render
793      * the specified cell. Overrides superclass version to provide support for
794      * cell decorators.
795      *
796      * @param renderer the <code>TableCellRenderer</code> to prepare
797      * @param row the row of the cell to render, where 0 is the first row
798          * @param column the column of the cell to render, where 0 is the first column
799      * @return the decorated <code>Component</code> used as a stamp to render the specified cell
800      * @see org.jdesktop.swing.decorator.Highlighter
801      */

802     public Component JavaDoc prepareRenderer(TableCellRenderer JavaDoc renderer, int row,
803                                      int column) {
804         Component JavaDoc stamp = super.prepareRenderer(renderer, row, column);
805         if (highlighters == null) {
806             return stamp; // no need to decorate renderer with highlighters
807
}
808         else {
809             // MUST ALWAYS ACCESS dataAdapter through accessor method!!!
810
ComponentAdapter adapter = getComponentAdapter();
811             adapter.row = row;
812             adapter.column = column;
813             return highlighters.apply(stamp, adapter);
814         }
815     }
816
817     protected ComponentAdapter getComponentAdapter() {
818         // MUST ALWAYS ACCESS dataAdapter through accessor method!!!
819
return dataAdapter;
820     }
821
822     public int search(String JavaDoc searchString) {
823         return search(searchString, -1);
824     }
825
826     public int search(String JavaDoc searchString, int columnIndex) {
827         Pattern JavaDoc pattern = null;
828         if (searchString != null) {
829             return search(Pattern.compile(searchString, 0), columnIndex);
830         }
831         return -1;
832     }
833
834     public int search(Pattern JavaDoc pattern) {
835         return search(pattern, -1);
836     }
837
838     public int search(Pattern JavaDoc pattern, int startIndex) {
839         return search(pattern, startIndex, false);
840     }
841
842     // Save the last column with the match.
843
private int lastCol = 0;
844
845     /**
846      * @param startIndex row to start search
847      * @return row with a match.
848      */

849     public int search(Pattern JavaDoc pattern, int startIndex, boolean backwards) {
850         if (pattern == null) {
851             lastCol = 0;
852             return -1;
853         }
854         int rows = getRowCount();
855         int endCol = getColumnCount();
856
857         int startRow = startIndex + 1;
858         int matc