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