| XJTable |

    1   /*
    2    *  Copyright (c) 1998-2001, The University of Sheffield.
    3    *
    4    *  This file is part of GATE (see http://gate.ac.uk/), and is free
    5    *  software, licenced under the GNU Library General Public License,
    6    *  Version 2, June 1991 (in the distribution as file licence.html,
    7    *  and also available at http://gate.ac.uk/gate/licence.html).
    8    *
    9    *  Valentin Tablan 23/01/2001
    10   *
    11   *  $Id: XJTable.java,v 1.11 2001/11/16 17:32:22 valyt Exp $
    12   *
    13   */
    14
    15  package gate.swing;
    16
    17  import java.awt.*;
    18  import java.awt.event.*;
    19  import java.util.*;
    20  import javax.swing.*;
    21  import javax.swing.table.*;
    22  import javax.swing.event.*;
    23
    24  import gate.util.*;
    25
    26  /**
    27   * A &quot;smarter&quot; JTable. Feaures include:
    28   * <ul>
    29   * <li>sorting the table using the values from a column as keys</li>
    30   * <li>updating the widths of the columns so they accommodate the contents to
    31   * their preferred sizes.
    32   * </ul>
    33   * It uses a custom made model that stands between the table model set by the
    34   * user and the gui component. This middle model is responsible for sorting the
    35   * rows.
    36   */
    37  public class XJTable extends JTable {
    38
    39    /**Default constructor*/
    40    public XJTable() {
    41      init();
    42    }
    43
    44    /**Constructor from model*/
    45    public XJTable(TableModel model) {
    46      init();
    47      setModel(model);
    48    }
    49
    50    public void setModel(TableModel model){
    51      if(sorter != null) sorter.setModel(model);
    52      else{
    53        sorter = new TableSorter(model);
    54        super.setModel(sorter);
    55      }
    56    }// void setModel(TableModel model)
    57
    58    /**
    59     * Returns the actual table model. Note that gateModel() will return the
    60     * middle model used for sorting. This cannot be avoided because JTable
    61     * expects to find the model used for the component when calling getModel().
    62     */
    63    public TableModel getActualModel(){
    64      if(sorter != null)return sorter.getModel();
    65      else return super.getModel();
    66    }// public TableModel getActualModel()
    67
    68    /**
    69     * Get the row in the table for a row in the model.
    70     */
    71    public int getTableRow(int modelRow){
    72      for(int i = 0; i < sorter.indexes.length; i ++){
    73        if(sorter.indexes[i] == modelRow) return i;
    74      }
    75      return -1;
    76    }
    77
    78    public void tableChanged(TableModelEvent e){
    79      super.tableChanged(e);
    80      adjustSizes();
    81    }
    82
    83    /**Should the soring facility be enabled*/
    84    public void setSortable(boolean isSortable){
    85      this.sortable = isSortable;
    86    }
    87
    88    protected void init(){
    89      //make sure we have a model
    90      if(sorter == null){
    91        sorter = new TableSorter(super.getModel());
    92        super.setModel(sorter);
    93      }
    94      //read the arrows icons
    95      upIcon = new ImageIcon(getClass().getResource(Files.getResourcePath() +
    96                                                    "/img/up.gif"));
    97      downIcon = new ImageIcon(getClass().getResource(Files.getResourcePath() +
    98                                                      "/img/down.gif"));
    99
    100     setColumnSelectionAllowed(false);
    101     headerMouseListener = new MouseAdapter() {
    102       public void mouseClicked(MouseEvent e) {
    103         if(!sortable) return;
    104         TableColumnModel columnModel = getColumnModel();
    105         int viewColumn = columnModel.getColumnIndexAtX(e.getX());
    106         int column = convertColumnIndexToModel(viewColumn);
    107         if (column != -1) {
    108           if(column != sortedColumn) ascending = true;
    109           else ascending = !ascending;
    110           sorter.sortByColumn(column);
    111           sortedColumn = column;
    112         }
    113         adjustSizes();
    114       }
    115     };
    116     if(sortable) getTableHeader().addMouseListener(headerMouseListener);
    117     setAutoResizeMode(AUTO_RESIZE_OFF);
    118     headerRenderer =
    119       new CustomHeaderRenderer(getTableHeader().getDefaultRenderer());
    120
    121     getTableHeader().setDefaultRenderer(headerRenderer);
    122   }//init()
    123
    124
    125   protected void configureEnclosingScrollPane(){
    126     super.configureEnclosingScrollPane();
    127     //if we're into a scroll pane resize with it
    128     Container p = getParent();
    129     if (p instanceof JViewport) {
    130       Container gp = p.getParent();
    131       if (gp instanceof JScrollPane) {
    132         JScrollPane scrollPane = (JScrollPane)gp;
    133         // Make certain we are the viewPort's view and not, for
    134         // example, the rowHeaderView of the scrollPane -
    135         // an implementor of fixed columns might do this.
    136         JViewport viewport = scrollPane.getViewport();
    137         if (viewport != null && viewport.getView() == this) {
    138           scrollPane.addComponentListener(new ComponentAdapter() {
    139             public void componentResized(ComponentEvent e) {
    140               adjustSizes();
    141             }
    142
    143             public void componentShown(ComponentEvent e) {
    144               adjustSizes();
    145             }
    146           });
    147         }//if
    148       }//if
    149     }//if
    150   }// void configureEnclosingScrollPane()
    151
    152
    153   /**Resizes all the cells so they accommodate the components at their
    154    * preferred sizes.
    155    */
    156   protected void adjustSizes(){
    157     int totalWidth = 0;
    158     TableColumn tCol = null;
    159     Dimension dim;
    160     int cellWidth;
    161     int cellHeight;
    162     int rowMargin = getRowMargin();
    163
    164     //delete the current rowModel in order to get a new updated one
    165     //this way we fix a bug in JTable
    166     setRowHeight(Math.max(getRowHeight(0), 1));
    167     for(int column = 0; column < getColumnCount(); column ++){
    168       int width;
    169       tCol = getColumnModel().getColumn(column);
    170       //compute the sizes
    171       width = headerRenderer.getTableCellRendererComponent(
    172                   this, tCol.getHeaderValue(), false, false,0,column
    173               ).getPreferredSize().width;
    174       for(int row = 0; row < getRowCount(); row ++){
    175         TableCellRenderer renderer = getCellRenderer(row,column);
    176         if(renderer == null){
    177           renderer = getDefaultRenderer(getModel().getColumnClass(column));
    178         }
    179         if(renderer != null){
    180           dim = renderer.
    181                       getTableCellRendererComponent(
    182                         this, getValueAt(row, column), false, false, row, column
    183                       ).getPreferredSize();
    184           cellWidth = dim.width;
    185           cellHeight = dim.height;
    186           width = Math.max(width, cellWidth);
    187           //width = Math.max(width, tCol.getPreferredWidth());
    188           if((cellHeight + rowMargin) > getRowHeight(row)){
    189            setRowHeight(row, cellHeight + rowMargin);
    190           }
    191         }//if(renderer != null)
    192       }//for
    193
    194       width += getColumnModel().getColumnMargin();
    195       tCol.setPreferredWidth(width);
    196       tCol.setWidth(width);
    197       totalWidth += width;
    198     }
    199     int totalHeight = 0;
    200     for (int row = 0; row < getRowCount(); row++)
    201       totalHeight += getRowHeight(row);
    202     dim = new Dimension(totalWidth, totalHeight);
    203     setPreferredScrollableViewportSize(dim);
    204
    205     //extend the last column
    206     Container p = getParent();
    207     if (p instanceof JViewport) {
    208       Container gp = p.getParent();
    209       if (gp instanceof JScrollPane) {
    210         JScrollPane scrollPane = (JScrollPane)gp;
    211         // Make certain we are the viewPort's view and not, for
    212         // example, the rowHeaderView of the scrollPane -
    213         // an implementor of fixed columns might do this.
    214         JViewport viewport = scrollPane.getViewport();
    215         if (viewport == null || viewport.getView() != this) {
    216             return;
    217         }
    218         int portWidth = scrollPane.getSize().width -
    219                         scrollPane.getInsets().left -
    220                         scrollPane.getInsets().right;
    221         if(scrollPane.getVerticalScrollBar().isVisible())
    222           portWidth -= scrollPane.getVerticalScrollBar().getWidth();
    223         if(totalWidth < portWidth){
    224           int width = tCol.getWidth() + portWidth - totalWidth - 2;
    225           tCol.setPreferredWidth(width);
    226           tCol.setWidth(width);
    227         }//if(totalWidth < portWidth)
    228       }//if (gp instanceof JScrollPane)
    229     }//if (p instanceof JViewport)
    230   }//protected void adjustSizes()
    231
    232   /**
    233    * Sets the column to be used as key for sorting. This column changes
    234    * automatically when the user click a column header.
    235    */
    236   public void setSortedColumn(int column){
    237     sortedColumn = column;
    238     sorter.sortByColumn(sortedColumn);
    239   }
    240
    241   /**Should the sorting be ascending or descending*/
    242   public void setAscending(boolean ascending){
    243     this.ascending = ascending;
    244   }
    245
    246   public void setAutoResizeMode(int resizeMode){
    247     /*
    248     throw new UnsupportedOperationException(
    249         "Auto resize mode not supported for " + getClass().getName() + ".\n"
    250         "The default mode is AUTO_RESIZE_LAST_COLUMN");
    251     */
    252   }
    253
    254   protected TableSorter sorter;
    255
    256   protected Icon upIcon;
    257   protected Icon downIcon;
    258   int sortedColumn = -1;
    259 //  int oldSortedColumn = -1;
    260   boolean ascending = true;
    261   protected TableCellRenderer headerRenderer;
    262   protected boolean sortable = true;
    263   MouseListener headerMouseListener;
    264 //  protected TableCellRenderer savedHeaderRenderer;
    265
    266 //classes
    267
    268   /**
    269    * A sorter for TableModels. The sorter has a model (conforming to TableModel)
    270    * and itself implements TableModel. TableSorter does not store or copy
    271    * the data in the TableModel, instead it maintains an array of
    272    * integers which it keeps the same size as the number of rows in its
    273    * model. When the model changes it notifies the sorter that something
    274    * has changed eg. "rowsAdded" so that its internal array of integers
    275    * can be reallocated. As requests are made of the sorter (like
    276    * getValueAt(row, col) it redirects them to its model via the mapping
    277    * array. That way the TableSorter appears to hold another copy of the table
    278    * with the rows in a different order. The sorting algorthm used is stable
    279    * which means that it does not move around rows when its comparison
    280    * function returns 0 to denote that they are equivalent.
    281    *
    282    * @version 1.5 12/17/97
    283    * @author Philip Milne
    284    */
    285
    286   class TableSorter extends TableMap {
    287     int             indexes[];
    288     Vector          sortingColumns = new Vector();
    289
    290     public TableSorter() {
    291       indexes = new int[0]; // for consistency
    292     }
    293
    294     public TableSorter(TableModel model) {
    295       setModel(model);
    296     }
    297
    298     public void setModel(TableModel model) {
    299       super.setModel(model);
    300       reallocateIndexes();
    301     }
    302
    303     public int compareRowsByColumn(int row1, int row2, int column) {
    304       Class type = model.getColumnClass(column);
    305       TableModel data = model;
    306
    307       // Check for nulls.
    308
    309       Object o1 = data.getValueAt(row1, column);
    310       Object o2 = data.getValueAt(row2, column);
    311
    312       // If both values are null, return 0.
    313       if (o1 == null && o2 == null) {
    314         return 0;
    315       } else if (o1 == null) { // Define null less than everything.
    316         return -1;
    317       } else if (o2 == null) {
    318         return 1;
    319       }
    320
    321       /*
    322        * We copy all returned values from the getValue call in case
    323        * an optimised model is reusing one object to return many
    324        * values.  The Number subclasses in the JDK are immutable and
    325        * so will not be used in this way but other subclasses of
    326        * Number might want to do this to save space and avoid
    327        * unnecessary heap allocation.
    328        */
    329
    330       if (type.getSuperclass() == java.lang.Number.class) {
    331         Number n1 = (Number)data.getValueAt(row1, column);
    332         double d1 = n1.doubleValue();
    333         Number n2 = (Number)data.getValueAt(row2, column);
    334         double d2 = n2.doubleValue();
    335
    336         if (d1 < d2) {
    337           return -1;
    338         } else if (d1 > d2) {
    339           return 1;
    340         } else {
    341           return 0;
    342         }
    343       } else if (type == java.util.Date.class) {
    344         Date d1 = (Date)data.getValueAt(row1, column);
    345         long n1 = d1.getTime();
    346         Date d2 = (Date)data.getValueAt(row2, column);
    347         long n2 = d2.getTime();
    348
    349         if (n1 < n2) {
    350           return -1;
    351         } else if (n1 > n2) {
    352           return 1;
    353         } else {
    354           return 0;
    355         }
    356       } else if (type == String.class) {
    357         String s1 = (String)data.getValueAt(row1, column);
    358         String s2    = (String)data.getValueAt(row2, column);
    359         int result = s1.compareTo(s2);
    360
    361         if (result < 0) {
    362           return -1;
    363         } else if (result > 0) {
    364           return 1;
    365         } else {
    366           return 0;
    367         }
    368       } else if (type == Boolean.class) {
    369         Boolean bool1 = (Boolean)data.getValueAt(row1, column);
    370         boolean b1 = bool1.booleanValue();
    371         Boolean bool2 = (Boolean)data.getValueAt(row2, column);
    372         boolean b2 = bool2.booleanValue();
    373
    374         if (b1 == b2) {
    375           return 0;
    376         } else if (b1) { // Define false < true
    377           return 1;
    378         } else {
    379           return -1;
    380         }
    381       } else {
    382         Object v1 = data.getValueAt(row1, column);
    383         Object v2 = data.getValueAt(row2, column);
    384         int result;
    385         if(v1 instanceof Comparable){
    386           try {
    387             result = ((Comparable)v1).compareTo(v2);
    388           } catch(ClassCastException cce) {
    389             String s1 = v1.toString();
    390             String s2 = v2.toString();
    391             result = s1.compareTo(s2);
    392           }
    393         } else {
    394           String s1 = v1.toString();
    395           String s2 = v2.toString();
    396           result = s1.compareTo(s2);
    397         }
    398
    399         if (result < 0) {
    400           return -1;
    401         } else if (result > 0) {
    402           return 1;
    403         } else {
    404           return 0;
    405         }
    406       }
    407     }
    408
    409     public int compare(int row1, int row2) {
    410      // compares++;
    411       for (int level = 0; level < sortingColumns.size(); level++) {
    412         Integer column = (Integer)sortingColumns.elementAt(level);
    413         int result = compareRowsByColumn(row1, row2, column.intValue());
    414         if (result != 0) {
    415           return ascending ? result : -result;
    416         }
    417       }
    418       return 0;
    419     }
    420
    421     public void reallocateIndexes() {
    422       int rowCount = model.getRowCount();
    423
    424       // Set up a new array of indexes with the right number of elements
    425       // for the new data model.
    426       indexes = new int[rowCount];
    427
    428       // Initialise with the identity mapping.
    429       for (int row = 0; row < rowCount; row++) {
    430         indexes[row] = row;
    431       }
    432     }
    433
    434     public void tableChanged(TableModelEvent e) {
    435       reallocateIndexes();
    436       sort(sorter);
    437       super.tableChanged(e);
    438     }
    439
    440     public void checkModel() {
    441       if (indexes.length != model.getRowCount()) {
    442         tableChanged(null);
    443         //System.err.println("Sorter not informed of a change in model.");
    444       }
    445     }
    446
    447     public void sort(Object sender) {
    448       checkModel();
    449       shuttlesort((int[])indexes.clone(), indexes, 0, indexes.length);
    450     }
    451
    452     // This is a home-grown implementation which we have not had time
    453     // to research - it may perform poorly in some circumstances. It
    454     // requires twice the space of an in-place algorithm and makes
    455     // NlogN assigments shuttling the values between the two
    456     // arrays. The number of compares appears to vary between N-1 and
    457     // NlogN depending on the initial order but the main reason for
    458     // using it here is that, unlike qsort, it is stable.
    459     public void shuttlesort(int from[], int to[], int low, int high) {
    460       if (high - low < 2) {
    461           return;
    462       }
    463       int middle = (low + high)/2;
    464       shuttlesort(to, from, low, middle);
    465       shuttlesort(to, from, middle, high);
    466
    467       int p = low;
    468       int q = middle;
    469
    470       /* This is an optional short-cut; at each recursive call,
    471       check to see if the elements in this subset are already
    472       ordered.  If so, no further comparisons are needed; the
    473       sub-array can just be copied.  The array must be copied rather
    474       than assigned otherwise sister calls in the recursion might
    475       get out of sinc.  When the number of elements is three they
    476       are partitioned so that the first set, [low, mid), has one
    477       element and and the second, [mid, high), has two. We skip the
    478       optimisation when the number of elements is three or less as
    479       the first compare in the normal merge will produce the same
    480       sequence of steps. This optimisation seems to be worthwhile
    481       for partially ordered lists but some analysis is needed to
    482       find out how the performance drops to Nlog(N) as the initial
    483       order diminishes - it may drop very quickly.  */
    484
    485       if (high - low >= 4 && compare(from[middle-1], from[middle]) <= 0) {
    486         for (int i = low; i < high; i++) {
    487           to[i] = from[i];
    488         }
    489         return;
    490       }
    491
    492       // A normal merge.
    493
    494       for (int i = low; i < high; i++) {
    495         if (q >= high || (p < middle && compare(from[p], from[q]) <= 0)) {
    496           to[i] = from[p++];
    497         }
    498         else {
    499           to[i] = from[q++];
    500         }
    501       }
    502     }
    503
    504     public void swap(int i, int j) {
    505       int tmp = indexes[i];
    506       indexes[i] = indexes[j];
    507       indexes[j] = tmp;
    508     }
    509
    510     // The mapping only affects the contents of the data rows.
    511     // Pass all requests to these rows through the mapping array: "indexes".
    512
    513     public Object getValueAt(int aRow, int aColumn) {
    514       checkModel();
    515       return model.getValueAt(indexes[aRow], aColumn);
    516     }
    517
    518     public void setValueAt(Object aValue, int aRow, int aColumn) {
    519       checkModel();
    520       model.setValueAt(aValue, indexes[aRow], aColumn);
    521     }
    522
    523     public boolean isCellEditable(int aRow, int aColumn) {
    524       checkModel();
    525       return model.isCellEditable(indexes[aRow], aColumn);
    526     }
    527
    528     public void sortByColumn(int column) {
    529       sortingColumns.removeAllElements();
    530       sortingColumns.addElement(new Integer(column));
    531       sort(this);
    532       super.tableChanged(new TableModelEvent(this));
    533       getTableHeader().repaint();
    534     }
    535   }//class TableSorter extends TableMap
    536
    537   class CustomHeaderRenderer extends DefaultTableCellRenderer{
    538     public CustomHeaderRenderer(TableCellRenderer oldRenderer){
    539       this.oldRenderer = oldRenderer;
    540     }
    541
    542     public Component getTableCellRendererComponent(JTable table,
    543                                              Object value,
    544                                              boolean isSelected,
    545                                              boolean hasFocus,
    546                                              int row,
    547                                              int column){
    548
    549       Component res = oldRenderer.getTableCellRendererComponent(
    550                             table, value, isSelected, hasFocus, row, column);
    551       if(res instanceof JLabel){
    552         if(convertColumnIndexToModel(column) == sortedColumn){
    553           ((JLabel)res).setIcon(ascending?upIcon:downIcon);
    554         } else {
    555           ((JLabel)res).setIcon(null);
    556         }
    557         ((JLabel)res).setHorizontalTextPosition(JLabel.LEFT);
    558       }
    559       return res;
    560     }// Component getTableCellRendererComponent
    561     protected TableCellRenderer oldRenderer;
    562
    563   }// class CustomHeaderRenderer extends DefaultTableCellRenderer
    564 }

| XJTable |