XJTable.java
0001 /*
0002  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
0003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0004  *
0005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0006  *  software, licenced under the GNU Library General Public License,
0007  *  Version 2, June 1991 (in the distribution as file licence.html,
0008  *  and also available at http://gate.ac.uk/gate/licence.html).
0009  *
0010  *  XJTable.java
0011  *
0012  *  Valentin Tablan, 25-Jun-2004
0013  *
0014  *  $Id: XJTable.java 13462 2011-02-18 08:55:17Z markagreenwood $
0015  */
0016 
0017 package gate.swing;
0018 
0019 import java.awt.Component;
0020 import java.awt.Container;
0021 import java.awt.Dimension;
0022 import java.awt.Point;
0023 import java.awt.Rectangle;
0024 import java.awt.event.*;
0025 import java.util.*;
0026 import javax.swing.*;
0027 import javax.swing.event.ChangeEvent;
0028 import javax.swing.event.TableColumnModelEvent;
0029 import javax.swing.event.TableModelEvent;
0030 import javax.swing.event.TableModelListener;
0031 import javax.swing.table.*;
0032 
0033 import gate.util.ObjectComparator;
0034 
0035 /**
0036  * A "smarter" JTable. Features include:
0037  <ul>
0038  <li>sorting the table using the values from a column as keys</li>
0039  <li>updating the widths of the columns so they accommodate the contents to
0040  * their preferred sizes.</li>
0041  <li>filling the whole viewport by default, unless told not to auto resize 
0042  * (see {@link JTable#setAutoResizeMode(int)}.
0043  <li>sizing the rows according to the preferred sizes of the renderers</li>
0044  <li>ability to hide/show columns</li>
0045  <li>ability for tab key to skip uneditable cells
0046  <li>ability to get in editing mode as soon as a cell gets the focus
0047  </ul>
0048  * It uses a custom made model that stands between the table model set by the
0049  * user and the GUI component. This middle model is responsible for sorting the
0050  * rows.
0051  */
0052 public class XJTable extends JTable{
0053 
0054   public XJTable(){
0055     this(null);
0056   }
0057   
0058   public XJTable(TableModel model){
0059     super();
0060     if(model != nullsetModel(model);
0061     // this is a partial fix for 
0062     // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4330950:
0063     // causes the current edit to be committed when focus moves to another
0064     // component. The other half of this bug (edits lost when the table 
0065     // header is clicked) is being addressed by overriding 
0066     // columnMarginChanged(ChangeEvent) and columnMoved(TableColumnModelEvent)
0067     putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
0068   }
0069   
0070   public void setModel(TableModel dataModel) {
0071     sortingModel = new SortingModel(dataModel);
0072     componentSizedProperly = false;
0073     super.setModel(sortingModel);
0074     newColumns();
0075   }
0076 
0077   /**
0078    * Called when the columns have changed.
0079    */
0080   protected void newColumns(){
0081     columnData = new ArrayList<ColumnData>(dataModel.getColumnCount());
0082     hiddenColumns = new ArrayList<TableColumn>();
0083     for(int i = 0; i < dataModel.getColumnCount(); i++) {
0084       columnData.add(new ColumnData(i));
0085     }
0086   }
0087   
0088   
0089   public void setTableHeader(JTableHeader newTableHeader) {
0090     //first remove the old listener from the old header
0091     JTableHeader oldHeader = getTableHeader();
0092     if(oldHeader != null && headerMouseListener != null)
0093       oldHeader.removeMouseListener(headerMouseListener);
0094     // set the new header
0095     super.setTableHeader(newTableHeader);
0096     //add the mouse listener to the new header
0097     if(headerMouseListener == nullheaderMouseListener = 
0098       new HeaderMouseListener();
0099     if(newTableHeader != null
0100       newTableHeader.addMouseListener(headerMouseListener);
0101   }
0102   
0103   public Dimension getPreferredScrollableViewportSize(){
0104     return getPreferredSize();
0105   }
0106   
0107   protected void calculatePreferredSize(){
0108     try{
0109       if(sizingInProgress){
0110         return;
0111       }else{
0112         sizingInProgress = true;
0113       }
0114       
0115       int colCount = getColumnModel().getColumnCount();
0116       if (colCount == 0) { return}
0117       //recalculate the preferred sizes if anything changed 
0118       if(!componentSizedProperly){
0119         Dimension spacing = getIntercellSpacing();
0120         //start with all columns at header size
0121         for(int col = 0; col < colCount; col++){
0122           TableColumn tColumn = getColumnModel().getColumn(col);
0123           TableCellRenderer headerRenderer = tColumn.getHeaderRenderer();
0124           boolean needToResetRenderer = false;
0125           if(headerRenderer == null){
0126             needToResetRenderer = true;
0127             //no header renderer provided -> use default implementation
0128             JTableHeader tHeader = getTableHeader();
0129             if(tHeader == null){
0130               tHeader = new JTableHeader();
0131             }
0132             headerRenderer = tHeader.getDefaultRenderer();
0133             tColumn.setHeaderRenderer(headerRenderer);
0134           }
0135           //initialise sizes for columns:
0136           // - min size = header size
0137           // - pref size = header size
0138           if(headerRenderer != null){
0139             Component c = headerRenderer.getTableCellRendererComponent(
0140                     XJTable.this, tColumn.getHeaderValue(), false, false, 00);
0141             int width = c.getMinimumSize().width  + spacing.width;
0142             if(tColumn.getMinWidth() != widthtColumn.setMinWidth(width);
0143             tColumn.setPreferredWidth(width);
0144           }else{
0145             tColumn.setMinWidth(1);
0146             tColumn.setPreferredWidth(1);
0147           }
0148           
0149           if (needToResetRenderertColumn.setHeaderRenderer(null);
0150         }
0151         
0152         //now fix the row height and column min/max widths
0153         for(int row = 0; row < getRowCount(); row++){
0154           //start with all rows of size 1      
0155           int newRowHeight = 1;
0156           // update the preferred size of the column ( to make it larger if any 
0157           // components are larger than then header.
0158           for(int column = 0; column < getColumnCount(); column ++){
0159             Component cellComponent = prepareRenderer(getCellRenderer(row, column)
0160                     row, column);
0161             TableColumn tColumn = getColumnModel().getColumn(column);
0162             int minWidth = cellComponent.getMinimumSize().width + spacing.width;
0163             //minimum width can only grow
0164             //if needed, increase the max width
0165 //            if(tColumn.getMaxWidth() < minWidth) tColumn.setMaxWidth(minWidth);
0166             //we prefer not to have any extra space.
0167             if(tColumn.getPreferredWidth() < minWidthtColumn.setPreferredWidth(minWidth);
0168 
0169             //now fix the row height
0170             int height = cellComponent.getPreferredSize().height;
0171             if(newRowHeight < (height + spacing.height)) 
0172               newRowHeight = height + spacing.height;
0173           }
0174           setRowHeight(row, newRowHeight);
0175         }        
0176       }//if(!componentSizedProperly){
0177 
0178       //now adjust the column widths, if we need to fill all the space
0179       if(getAutoResizeMode() != AUTO_RESIZE_OFF){
0180         //the column being resized (if any)
0181         TableColumn resizingCol = null;
0182         if(getTableHeader() != nullresizingCol = 
0183             getTableHeader().getResizingColumn();
0184         
0185         int prefWidth = 0;
0186         for(int i = 0; i < colCount; i++){
0187           int colWidth = getColumnModel().getColumn(i).getPreferredWidth();
0188           if(colWidth > 0prefWidth += colWidth;
0189         }
0190         
0191         int requestedWidth = getWidth();
0192         Container parent = this.getParent();
0193         if(parent != null && parent instanceof JViewport) {
0194           // only track the viewport width if it is big enough.
0195           int viewportWidth = ((JViewport)parent).getExtentSize().width;
0196           if(viewportWidth > requestedWidth){
0197             requestedWidth = viewportWidth;
0198           }
0199         }
0200         int extra = 0;
0201         
0202         if(requestedWidth > prefWidth){
0203           //we have extra space to fill
0204           extra = requestedWidth - prefWidth;
0205           if(getAutoResizeMode() == AUTO_RESIZE_ALL_COLUMNS){
0206             int extraCol = extra / colCount;
0207             //distribute the extra space to all columns
0208             for(int col = 0; col < colCount - 1; col++){
0209               TableColumn tColumn = getColumnModel().getColumn(col);
0210               //we leave the resizing column alone
0211               if(tColumn != resizingCol){
0212                 tColumn.setPreferredWidth(tColumn.getPreferredWidth() + extraCol);
0213                 extra -= extraCol;
0214               }
0215             }
0216           }
0217           //all the remainder goes to the last col
0218           TableColumn tColumn = getColumnModel().getColumn(colCount - 1);
0219           tColumn.setPreferredWidth(tColumn.getPreferredWidth() + extra);        
0220         }//if(requestedWidth > prefWidth){        
0221       }//if(getAutoResizeMode() != AUTO_RESIZE_OFF){
0222     }finally{
0223       componentSizedProperly = true;
0224       sizingInProgress = false;
0225     }
0226   }
0227   
0228   
0229   
0230   @Override
0231   public void doLayout() {
0232     calculatePreferredSize();
0233     super.doLayout();
0234   }
0235 
0236   @Override
0237   /**
0238    * Overridden so that the preferred size can be calculated properly
0239    */
0240   public Dimension getPreferredSize() {
0241     //if hard-coded, we return the given value.
0242     if(isPreferredSizeSet()) return super.getPreferredSize();
0243     
0244     //the first time the component is sized, calculate the actual preferred size
0245     if(!componentSizedProperly){
0246       calculatePreferredSize();
0247     }
0248     return super.getPreferredSize();
0249   }
0250 
0251   /**
0252    * Overridden to ignore requests for this table to track the width of its
0253    * containing viewport in cases where the viewport is narrower than the
0254    * minimum size of the table.  Where the viewport is at least as wide as
0255    * the minimum size of the table, we will allow the table to resize with
0256    * the viewport, but when it gets too small we stop tracking, which allows
0257    * the horizontal scrollbar to appear.
0258    */
0259   @Override
0260   public boolean getScrollableTracksViewportWidth() {
0261     if(super.getScrollableTracksViewportWidth()) {
0262       Container parent = this.getParent();
0263       if(parent != null && parent instanceof JViewport) {
0264         // only track the viewport width if it is big enough.
0265         return ((JViewport)parent).getExtentSize().width
0266                     this.getPreferredSize().width;
0267       else {
0268         return true;
0269       }
0270     }
0271     else // super.getScrollableTracksViewportWidth() == false
0272       return false;
0273     }
0274   }
0275 
0276   /**
0277    * Track parent viewport's size if it's larger than the current preferred 
0278    * height of the table (causes empty tables to fill in the whole area of
0279    * a JScrollPane). 
0280    @return true if the preferred height of the table is smaller than the
0281    *   viewport.
0282    */
0283   public boolean getScrollableTracksViewportHeight() {
0284     Container parent = getParent();
0285     Dimension dim = getPreferredSize();
0286     return  (parent != null && dim!= null &&
0287              parent instanceof JViewport && 
0288              parent.getHeight() > getPreferredSize().height);
0289   }
0290 
0291   private volatile boolean componentSizedProperly = false;
0292 
0293   private volatile boolean sizingInProgress = false;
0294   
0295   
0296   /**
0297    * Converts a row number from the model co-ordinates system to the view's. 
0298    @param modelRow the row number in the model
0299    @return the corresponding row number in the view. 
0300    @see #rowViewToModel(int) 
0301    */
0302   public int rowModelToView(int modelRow){
0303     return sortingModel.sourceToTarget(modelRow);
0304   }
0305 
0306   /**
0307    @return Returns the ascending.
0308    */
0309   public boolean isAscending() {
0310     return ascending;
0311   }
0312   
0313   /**
0314    * Gets the hidden state for a column
0315    @param columnIndex index of the column in the table model
0316    @return hidden state for a column
0317    */
0318   public boolean isColumnHidden(int columnIndex){
0319     for (TableColumn hiddenColumn : hiddenColumns) {
0320       if (hiddenColumn.getModelIndex() == columnIndex) {
0321         return true;
0322       }
0323     }
0324     return false;
0325   }
0326 
0327   /**
0328    * Hide a column. Do nothing if already hidden.
0329    @param columnIndex index of the column in the table model
0330    */
0331   public void hideColumn(int columnIndex) {
0332     int viewColumn = convertColumnIndexToView(columnIndex);
0333     TableColumn columnToHide = columnModel.getColumn(viewColumn);
0334     columnModel.removeColumn(columnToHide);
0335     hiddenColumns.add(columnToHide);
0336   }
0337 
0338   /**
0339    * Show a column. Do nothing if already shown.
0340    @param columnIndex index of the column in the table model
0341    @param insertionIndex index of the view where the colum will be inserted
0342    */
0343   public void showColumn(int columnIndex, int insertionIndex) {
0344     for (TableColumn hiddenColumn : hiddenColumns) {
0345       if (hiddenColumn.getModelIndex() == columnIndex) {
0346         columnModel.addColumn(hiddenColumn);
0347         columnModel.moveColumn(columnModel.getColumnCount()-1, insertionIndex);
0348         hiddenColumns.remove(hiddenColumn);
0349         break;
0350       }
0351     }
0352   }
0353 
0354   /**
0355    @param ascending The ascending to set. True by default.
0356    */
0357   public void setAscending(boolean ascending) {
0358     this.ascending = ascending;
0359   }
0360   /**
0361    * Converts a row number from the view co-ordinates system to the model's. 
0362    @param viewRow the row number in the view.
0363    @return the corresponding row number in the model.
0364    @see #rowModelToView(int)
0365    */
0366   public int rowViewToModel(int viewRow){
0367     return sortingModel.targetToSource(viewRow);
0368   }
0369 
0370   /**
0371    * Set the possibility for the user to hide/show a column by right-clicking
0372    * on a column header. False by default.
0373    @param enableHidingColumns true if and only if the columns can be hidden.
0374    */
0375   public void setEnableHidingColumns(boolean enableHidingColumns) {
0376     this.enableHidingColumns = enableHidingColumns;
0377   }
0378 
0379   /**
0380    * Returns the state for hiding a column.
0381    @return true if and only if the columns can be hidden.
0382    */
0383   public boolean isEnableHidingColumns() {
0384     return this.enableHidingColumns;
0385   }
0386 
0387   /**
0388    * Returns the state for editing a cell as soon as it gets the focus.
0389    @return true if and only if a cell get in editing mode when
0390    * it receives the focus.
0391    */
0392   public boolean isEditCellAsSoonAsFocus() {
0393     return editCellAsSoonAsFocus;
0394   }
0395 
0396   /**
0397    * Set the possibility for a cell to be in editing mode as soon as
0398    * it gets the focus. False by default.
0399    @param editCellAsSoonAsFocus true if and only if a cell get in editing
0400    * mode when it receives the focus.
0401    */
0402   public void setEditCellAsSoonAsFocus(boolean editCellAsSoonAsFocus) {
0403     this.editCellAsSoonAsFocus = editCellAsSoonAsFocus;
0404   }
0405 
0406   /**
0407    * Returns the state for enabling tab key to skip uneditable cells.
0408    @return true if and only if the tab key skip uneditable cells.
0409    */
0410   public boolean isTabSkipUneditableCell() {
0411     return tabSkipUneditableCell;
0412   }
0413 
0414   /**
0415    * Set the possibility for the tab key to skip uneditable cells.
0416    * False by default.
0417    @param tabSkipUneditableCell true if and only if the tab key skip
0418    * uneditable cells.
0419    */
0420   public void setTabSkipUneditableCell(boolean tabSkipUneditableCell) {
0421 
0422     this.tabSkipUneditableCell = tabSkipUneditableCell;
0423 
0424     InputMap im = getInputMap(JTable.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
0425     KeyStroke tab = KeyStroke.getKeyStroke("TAB");
0426     KeyStroke shiftTab = KeyStroke.getKeyStroke("shift TAB");
0427     if (oldTabAction == null) {
0428       oldTabAction = getActionMap().get(im.get(tab));
0429       oldShiftTabAction = getActionMap().get(im.get(shiftTab));
0430     }
0431 
0432     if (tabSkipUneditableCell) {
0433 
0434       // skip non editable cells when tabbing
0435       Action tabAction = new AbstractAction() {
0436         public void actionPerformed(ActionEvent e) {
0437           oldTabAction.actionPerformed(e);
0438           JTable table = (JTablee.getSource();
0439           int row = table.getSelectedRow();
0440           int originalRow = row;
0441           int column = table.getSelectedColumn();
0442           int originalColumn = column;
0443           while(!table.isCellEditable(row, column)) {
0444             oldTabAction.actionPerformed(e);
0445             row = table.getSelectedRow();
0446             column = table.getSelectedColumn();
0447             //  back to where we started, get out
0448             if(row == originalRow
0449               && column == originalColumn) {
0450               break;
0451             }
0452           }
0453         }
0454       };
0455       getActionMap().put(im.get(tab), tabAction);
0456 
0457       // skip non editable cells when shift tabbing
0458       Action shiftTabAction = new AbstractAction() {
0459         public void actionPerformed(ActionEvent e) {
0460           oldShiftTabAction.actionPerformed(e);
0461           JTable table = (JTablee.getSource();
0462           int row = table.getSelectedRow();
0463           int originalRow = row;
0464           int column = table.getSelectedColumn();
0465           int originalColumn = column;
0466           while(!table.isCellEditable(row, column)) {
0467             oldShiftTabAction.actionPerformed(e);
0468             row = table.getSelectedRow();
0469             column = table.getSelectedColumn();
0470             //  back to where we started, get out
0471             if(row == originalRow
0472               && column == originalColumn) {
0473               break;
0474             }
0475           }
0476         }
0477       };
0478       getActionMap().put(im.get(shiftTab), shiftTabAction);
0479 
0480     else {
0481       getActionMap().put(im.get(tab), oldTabAction);
0482       getActionMap().put(im.get(shiftTab), oldShiftTabAction);
0483     }
0484   }
0485 
0486 
0487   /**
0488    * Sets the custom comparator to be used for a particular column. Columns that
0489    * don't have a custom comparator will be sorted using the natural order.
0490    @param column the column index.
0491    @param comparator the comparator to be used.
0492    */
0493   public void setComparator(int column, Comparator comparator){
0494     columnData.get(column).comparator = comparator;
0495   }
0496     
0497   /**
0498    @return Returns the sortable.
0499    */
0500   public boolean isSortable(){
0501     return sortable;
0502   }
0503   /**
0504    @param sortable The sortable to set. True by default.
0505    */
0506   public void setSortable(boolean sortable){
0507     this.sortable = sortable;
0508   }
0509   /**
0510    @return Returns the sortColumn.
0511    */
0512   public int getSortedColumn(){
0513     return sortedColumn;
0514   }
0515   /**
0516    @param sortColumn The sortColumn to set. None (-1) by default.
0517    */
0518   public void setSortedColumn(int sortColumn){
0519     this.sortedColumn = sortColumn;
0520   }
0521   
0522   /**
0523    * Get the row in the table for a row in the model.
0524    @param modelRow row in the model
0525    @return row in the table view
0526    */
0527   public int getTableRow(int modelRow){
0528     return sortingModel.sourceToTarget(modelRow);
0529   }
0530 
0531   /**
0532    * Handles translations between an indexed data source and a permutation of 
0533    * itself (like the translations between the rows in sorted table and the
0534    * rows in the actual unsorted model).  
0535    */
0536   protected class SortingModel extends AbstractTableModel 
0537       implements TableModelListener{
0538     
0539     public SortingModel(TableModel sourceModel){
0540       compWrapper = new ValueHolderComparator();
0541       init(sourceModel);
0542     }
0543     
0544     protected class ValueHolder{
0545       private Object value;
0546       private int index;
0547       public ValueHolder(Object value, int index) {
0548         super();
0549         this.value = value;
0550         this.index = index;
0551       }
0552     }
0553     
0554     protected class ValueHolderComparator implements Comparator<ValueHolder>{
0555       private Comparator comparator;
0556       
0557       protected Comparator getComparator() {
0558         return comparator;
0559       }
0560 
0561       protected void setComparator(Comparator comparator) {
0562         this.comparator = comparator;
0563       }
0564 
0565       public int compare(ValueHolder o1, ValueHolder o2) {
0566         return ascending ? comparator.compare(o1.value, o2.value:
0567           comparator.compare(o1.value, o2.value* -1;
0568       }
0569     }
0570     
0571     protected void init(TableModel sourceModel){
0572       if(this.sourceModel != null
0573         this.sourceModel.removeTableModelListener(this);
0574       this.sourceModel = sourceModel;
0575       //start with the identity order
0576       int size = sourceModel.getRowCount();
0577       sourceToTarget = new int[size];
0578       targetToSource = new int[size];
0579       for(int i = 0; i < size; i++) {
0580         sourceToTarget[i= i;
0581         targetToSource[i= i;
0582       }
0583       sourceModel.addTableModelListener(this);
0584       if(isSortable() && sortedColumn == -1setSortedColumn(0);
0585       componentSizedProperly = false;
0586     }
0587     
0588     /**
0589      * This gets events from the source model and forwards them to the UI
0590      */
0591     public void tableChanged(TableModelEvent e){
0592       int type = e.getType();
0593       int firstRow = e.getFirstRow();
0594       int lastRow = e.getLastRow();
0595       int column = e.getColumn();
0596       
0597       //deal with the changes in the data
0598       //we have no way to "repair" the sorting on data updates so we will need
0599       //to rebuild the order every time
0600       
0601       switch(type){
0602         case TableModelEvent.UPDATE:
0603           if(firstRow == TableModelEvent.HEADER_ROW){
0604             //complete structure change -> reallocate the data
0605             init(sourceModel);
0606             newColumns();
0607             fireTableStructureChanged();
0608             if(isSortable()) sort();
0609           }else if(lastRow == Integer.MAX_VALUE){
0610             //all data changed (including the number of rows)
0611             init(sourceModel);
0612             if(isSortable()){
0613               sort();
0614             else {
0615               //this will re-create all rows (which deletes the rowModel in JTable)
0616               componentSizedProperly = false;
0617               fireTableDataChanged();
0618             }
0619           }else{
0620             //the rows should have normal values
0621             //if the sortedColumn is not affected we don't care
0622             if(isSortable() &&
0623                (column == sortedColumn || 
0624                 column == TableModelEvent.ALL_COLUMNS)){
0625                 //re-sorting will also fire the event upwards
0626                 sort();
0627             }else{
0628               componentSizedProperly = false;
0629               fireTableChanged(new TableModelEvent(this,  
0630                       sourceToTarget(firstRow)
0631                       sourceToTarget(lastRow), column, type));
0632               
0633             }
0634           }
0635           break;
0636         case TableModelEvent.INSERT:
0637           //rows were inserted -> we need to rebuild
0638           init(sourceModel);
0639           if(firstRow != TableModelEvent.HEADER_ROW && firstRow == lastRow){
0640             //single row insertion
0641             if(isSortable()) sort();
0642             else{
0643               componentSizedProperly = false;
0644               fireTableChanged(new TableModelEvent(this,  
0645                     sourceToTarget(firstRow)
0646                     sourceToTarget(lastRow), column, type));
0647             }
0648           }else{
0649             //the real rows are not in sequence
0650             if(isSortable()) sort();
0651             else {
0652               //this will re-create all rows (which deletes the rowModel in JTable)
0653               componentSizedProperly = false;
0654               fireTableDataChanged();
0655             }
0656           }
0657           break;
0658         case TableModelEvent.DELETE:
0659           //rows were deleted -> we need to rebuild
0660           init(sourceModel);
0661           if(isSortable()) sort();
0662           else {
0663             //this will re-create all rows (which deletes the rowModel in JTable)
0664             componentSizedProperly = false;
0665             fireTableDataChanged();
0666           }
0667       }
0668     }
0669     
0670     public int getRowCount(){
0671       return sourceToTarget.length;
0672 //      return sourceModel.getRowCount();
0673     }
0674     
0675     public int getColumnCount(){
0676       return sourceModel.getColumnCount();
0677     }
0678     
0679     public String getColumnName(int columnIndex){
0680       return sourceModel.getColumnName(columnIndex);
0681     }
0682     public Class getColumnClass(int columnIndex){
0683       return sourceModel.getColumnClass(columnIndex);
0684     }
0685     
0686     public boolean isCellEditable(int rowIndex, int columnIndex){
0687       return sourceModel.isCellEditable(targetToSource(rowIndex),
0688               columnIndex);
0689     }
0690     public void setValueAt(Object aValue, int rowIndex, int columnIndex){
0691       sourceModel.setValueAt(aValue, targetToSource(rowIndex)
0692               columnIndex);
0693     }
0694     public Object getValueAt(int row, int column){
0695       try{
0696         return sourceModel.getValueAt(targetToSource(row), column);
0697       }catch(IndexOutOfBoundsException iobe){
0698         //this can occur because of multithreaded access -> some threads empties 
0699         //the data while some other thread tries to update the display.
0700         //this error is safe to ignore as the GUI will get updated by another 
0701         //event once the change to the data has been effected
0702         return null;
0703       }
0704     }
0705     
0706     /**
0707      * Sorts the table using the values in the specified column and sorting order.
0708      * sortedColumn is the column used for sorting the data.
0709      * ascending is the sorting order.
0710      */
0711     public void sort(){
0712       try {
0713         if (sortedColumn >= columnData.size()) { return}
0714         //save the selection
0715         int[] rows = getSelectedRows();
0716         //convert to model co-ordinates
0717         for(int i = 0; i < rows.length; i++)  rows[i= rowViewToModel(rows[i]);
0718         //create a list of ValueHolder objects for the source data that needs
0719         //sorting
0720         List<ValueHolder> sourceData = 
0721             new ArrayList<ValueHolder>(sourceModel.getRowCount());
0722         //get the data in the source order
0723         for(int i = 0; i < sourceModel.getRowCount(); i++){
0724           Object value = sourceModel.getValueAt(i, sortedColumn);
0725           sourceData.add(new ValueHolder(value, i));
0726         }
0727         
0728         //get an appropriate comparator
0729         Comparator comparator = columnData.get(sortedColumn).comparator;
0730         if(comparator == null){
0731           //use the default comparator
0732           if(defaultComparator == nulldefaultComparator = new ObjectComparator();
0733           comparator = defaultComparator;
0734         }
0735         compWrapper.setComparator(comparator);
0736         //perform the actual sorting
0737         Collections.sort(sourceData, compWrapper);
0738         //extract the new order
0739         for(int i = 0; i < sourceData.size(); i++){
0740           int targetIndex = i;
0741           int sourceIndex = sourceData.get(i).index;
0742           sourceToTarget[sourceIndex= targetIndex;
0743           targetToSource[targetIndex= sourceIndex;
0744         }
0745         sourceData.clear();
0746         //the rows may have moved about so we need to recalculate the heights
0747         componentSizedProperly = false;
0748         //this deletes the JTable row model 
0749         fireTableDataChanged();
0750         //we need to recreate the row model, and only then restore selection
0751         resizeAndRepaint();
0752         //restore the selection
0753         clearSelection();
0754         for(int i = 0; i < rows.length; i++){
0755             rows[i= rowModelToView(rows[i]);
0756             if(rows[i&& rows[i< getRowCount())
0757               addRowSelectionInterval(rows[i], rows[i]);
0758         }
0759       }catch(ArrayIndexOutOfBoundsException aioob) {
0760         //this can happen when update events get behind
0761         //just ignore - we'll get another event later to cause the sorting
0762       }
0763     }
0764     /**
0765      * Converts an index from the source coordinates to the target ones.
0766      * Used to propagate events from the data source (table model) to the view. 
0767      @param index the index in the source coordinates.
0768      @return the corresponding index in the target coordinates or -1 if the 
0769      * provided row is outside the permitted values.
0770      */
0771     public int sourceToTarget(int index){
0772       return (index >= && index < sourceToTarget.length?
0773               sourceToTarget[index:
0774               -1;
0775     }
0776 
0777     /**
0778      * Converts an index from the target coordinates to the source ones. 
0779      @param index the index in the target coordinates.
0780      * Used to propagate events from the view (e.g. editing) to the source
0781      * data source (table model).
0782      @return the corresponding index in the source coordinates or -1 if the 
0783      * row provided is outside the allowed range.
0784      */
0785     public int targetToSource(int index){
0786       return (index >= && index < targetToSource.length?
0787              targetToSource[index:
0788              -1;
0789     }
0790     
0791     /**
0792      * Builds the reverse index based on the new sorting order.
0793      */
0794     protected void buildTargetToSourceIndex(){
0795       targetToSource = new int[sourceToTarget.length];
0796       for(int i = 0; i < sourceToTarget.length; i++)
0797         targetToSource[sourceToTarget[i]] = i;
0798     }
0799     
0800     /**
0801      * The direct index
0802      */
0803     protected int[] sourceToTarget;
0804     
0805     /**
0806      * The reverse index.
0807      */
0808     protected int[] targetToSource;
0809     
0810     protected TableModel sourceModel;
0811     
0812     protected ValueHolderComparator compWrapper;
0813   }
0814   
0815   protected class HeaderMouseListener extends MouseAdapter{
0816     public void mouseClicked(MouseEvent e){
0817       process(e);
0818     }
0819 
0820     public void mousePressed(MouseEvent e){
0821       process(e);
0822     }
0823 
0824     public void mouseReleased(MouseEvent e){
0825       process(e);
0826     }
0827 
0828     protected void process(MouseEvent e){
0829       final int viewColumn = columnModel.getColumnIndexAtX(e.getX());
0830       if(viewColumn != -1){
0831         final int column = convertColumnIndexToModel(viewColumn);
0832         if(e.isPopupTrigger()
0833         && enableHidingColumns){
0834           //show pop-up
0835           JPopupMenu popup = new JPopupMenu();
0836           if (columnModel.getColumnCount() 1) {
0837             popup.add(new AbstractAction("Hide column "
0838               + dataModel.getColumnName(column)){
0839               public void actionPerformed(ActionEvent e) {
0840                 hideColumn(column);
0841               }
0842             });
0843           }
0844           if (hiddenColumns.size() 0) {
0845             popup.addSeparator();
0846           }
0847           for (TableColumn hiddenColumn : hiddenColumns) {
0848             final TableColumn hiddenColumnF = hiddenColumn;
0849             popup.add(new AbstractAction("Show column "
0850               + dataModel.getColumnName(hiddenColumn.getModelIndex())){
0851               public void actionPerformed(ActionEvent e) {
0852                 showColumn(hiddenColumnF.getModelIndex(), viewColumn);
0853               }
0854             });
0855           }
0856           popup.show(e.getComponent(), e.getX(), e.getY());
0857         }
0858         else if(!e.isPopupTrigger()
0859               && e.getID() == MouseEvent.MOUSE_CLICKED
0860               && e.getClickCount() == 1){
0861           //normal click -> re-sort
0862           if(sortable && column != -1) {
0863             ascending = (column == sortedColumn? !ascending : true;
0864             sortedColumn = column;
0865             sortingModel.sort();
0866           }
0867         }
0868       }
0869     }
0870   }
0871   
0872   protected class ColumnData{
0873     public ColumnData(int column){
0874       this.column = column;
0875     }
0876     
0877     int column;
0878     int columnWidth;
0879     Comparator comparator;
0880   }
0881 
0882   public void changeSelection(int rowIndex, int columnIndex,
0883                               boolean toggle, boolean extend) {
0884     super.changeSelection(rowIndex, columnIndex, toggle, extend);
0885     if (!toggle && !extend && editCellAsSoonAsFocus) {
0886       // start cell editing as soon as a cell is selected
0887       if(!isEditing() && isCellEditable(rowIndex, columnIndex)) this.editCellAt(rowIndex, columnIndex);
0888     }
0889   }
0890 
0891   @Override
0892   public int rowAtPoint(Point point) {
0893     //The Swing implementation is very keen to drop the rowModel. We KNOW we
0894     //should always have a row model, so if null, create it!
0895     if(!componentSizedProperly){
0896       calculatePreferredSize();
0897     }
0898     return super.rowAtPoint(point);
0899   }
0900 
0901   /**
0902    * Overridden for efficiency reasons (provides a better calculation of the 
0903    * dirty region). See 
0904    * <a href="http://www.objectdefinitions.com/odblog/2009/jtable-setrowheight-causes-slow-repainting/">this page</a>
0905    * for a more complete discussion. 
0906    */
0907   public void tableChanged(TableModelEvent e) {
0908     //if just an update, and not a data or structure changed event or an insert or delete, use the fixed row update handling
0909     //otherwise call super.tableChanged to let the standard JTable update handling manage it
0910     if e != null &&
0911         e.getType() == TableModelEvent.UPDATE &&
0912         e.getFirstRow() != TableModelEvent.HEADER_ROW &&
0913         e.getLastRow() != Integer.MAX_VALUE) {
0914         handleRowUpdate(e);
0915     else {
0916         super.tableChanged(e);
0917     }
0918   }
0919 
0920   /**
0921    * This borrows most of the logic from the superclass handling of update 
0922    * events, but changes the calculation of the height for the dirty region to 
0923    * provide proper handling for repainting custom height rows.
0924    * Copied from <a href="http://www.objectdefinitions.com/odblog/2009/jtable-setrowheight-causes-slow-repainting/">this page</a>.
0925    */
0926   private void handleRowUpdate(TableModelEvent e) {
0927     int modelColumn = e.getColumn();
0928     int start = e.getFirstRow();
0929     int end = e.getLastRow();
0930 
0931     Rectangle dirtyRegion;
0932     if (modelColumn == TableModelEvent.ALL_COLUMNS) {
0933         // 1 or more rows changed
0934       int rowStart = 0;
0935       for int row=0; row < start; row++ rowStart += getRowHeight(row);
0936       dirtyRegion = new Rectangle(0, rowStart, 
0937               getColumnModel().getTotalColumnWidth()0);
0938       
0939     else {
0940       // A cell or column of cells has changed.
0941       // Unlike the rest of the methods in the JTable, the TableModelEvent
0942       // uses the coordinate system of the model instead of the view.
0943       // This is the only place in the JTable where this "reverse mapping"
0944       // is used.
0945       int column = convertColumnIndexToView(modelColumn);
0946       dirtyRegion = getCellRect(start, column, true);
0947     }
0948 
0949     // Now adjust the height of the dirty region
0950     dirtyRegion.height = 0;
0951     for (int row = start; row <= end; row ++ ) {
0952       //THIS IS CHANGED TO CALCULATE THE DIRTY REGION HEIGHT CORRECTLY
0953       dirtyRegion.height += getRowHeight(row);
0954     }
0955     repaint(dirtyRegion.x, dirtyRegion.y, dirtyRegion.width, dirtyRegion.height);
0956   }
0957   
0958   
0959   /**
0960    * Overridden to fix 
0961    * //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4330950:
0962    */ 
0963   public void columnMoved(TableColumnModelEvent e) {
0964     if (isEditing()) {
0965         cellEditor.stopCellEditing();
0966     }
0967     super.columnMoved(e);
0968   }
0969 
0970   /**
0971    * Overridden to fix 
0972    * //http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4330950:
0973    */   
0974   public void columnMarginChanged(ChangeEvent e) {
0975     //save the current edit (otherwise it gets lost)
0976     if (isEditing()) cellEditor.stopCellEditing();
0977     //allow resizing of columns even if JTable believes we should block it.
0978     TableColumn resizingCol = null;
0979     if(getTableHeader() != nullresizingCol = 
0980         getTableHeader().getResizingColumn();
0981     if (resizingCol != null) {
0982       resizingCol.setPreferredWidth(resizingCol.getWidth());
0983     }
0984     
0985     super.columnMarginChanged(e);
0986   }
0987 
0988   protected SortingModel sortingModel;
0989   protected ObjectComparator defaultComparator;
0990   
0991   /**
0992    * The column currently being sorted.
0993    */
0994   protected int sortedColumn = -1;
0995   
0996   /**
0997    * is the current sort order ascending (or descending)?
0998    */
0999   protected boolean ascending = true;
1000   /**
1001    * Should this table be sortable.
1002    */
1003   protected boolean sortable = true;
1004   
1005   /**
1006    * A list of {@link ColumnData} objects.
1007    */
1008   protected List<ColumnData> columnData;
1009   
1010   protected HeaderMouseListener headerMouseListener;
1011 
1012   /**
1013    * Contains the hidden columns in no particular order.
1014    */
1015   protected List<TableColumn> hiddenColumns;
1016 
1017   private boolean enableHidingColumns = false;
1018 
1019   private boolean tabSkipUneditableCell = false;
1020 
1021   private boolean editCellAsSoonAsFocus = false;
1022 
1023   private Action oldTabAction;
1024 
1025   private Action oldShiftTabAction;
1026 }