C:\Users\akluge\Documents\vizit\presentations\FixedJtable\code\src\com\vizitsolutions\identitytable\TabKeyActionHandler.java
  1 
  2 package com.vizitsolutions.identitytable;
  3 
  4 import javax.swing.AbstractAction;
  5 import java.awt.event.ActionEvent;
  6 import javax.swing.JTable;
  7 import javax.swing.ListSelectionModel;
  8 import java.awt.Rectangle;
  9 
 10 
 11 /**
 12  * <p>$Id: $</p>
 13  *
 14  * Make tab, shift-tab, and the left and right arrows behave reasonably in the
 15  * IdentityTable, which is a composit of two separate JTables. Shift the focus
 16  * to and from the composited table to make it appear that it is all a single
 17  * table.
 18  *
 19  * @author  Alex Kluge
 20  * @version $Revision: $, $Date: $
 21  */
 22 public class TabKeyActionHandler extends AbstractAction
 23 {
 24     /** 
 25      * The number of cells to shift. This is either +1 for a tab or right arrow,
 26      * or -1 for a shift tab or left arrow.
 27      */
 28     int                delta;
 29     
 30     /**
 31      * The column selection model for the main table. This is used to
 32      * set the appropriate column as selected when the keyboard is used to
 33      * navigate to another cell. The selected cell is the intersection of the
 34      * celected column and the selected row.
 35      * 
 36      * @see rowSelectionModel
 37      */
 38     ListSelectionModel mainColumnSelectionModel;
 39     
 40     /**
 41      * The main table, contains the table model columns other than the
 42      * row header columns.
 43      */
 44     JTable             mainTable;
 45     
 46     /**
 47      * The column selection model for the row header table. This is used to
 48      * set the appropriate column as selected when the keyboard is used to
 49      * navigate to another cell. The selected cell is the intersection of the
 50      * selected column and the selected row.
 51      * 
 52      * @see rowSelectionModel
 53      */
 54     ListSelectionModel rowHeaderColumnSelectionModel;
 55     
 56     /**
 57      * This table is the row header for the table.
 58      */
 59     JTable             rowHeaderTable;
 60     
 61     /**
 62      * The selected row. The row selection model, and hence the selected row
 63      * spans both of the compoent tables, so the same row is always selected
 64      * in both tables.
 65      * 
 66      * @see mainColumnSelectionModel, rowHeaderColumnSelectionModel
 67      */
 68     ListSelectionModel rowSelectionModel;
 69     
 70     /**
 71      * Describes the action when the focus is shifted from the end of a row. If
 72      * wrap is true, continue on to the next or previous row. If false, then do
 73      * nothing.
 74      */
 75     boolean            wrap;
 76 
 77     /**
 78      * Construct a TabKeyActionHandler. The TabKeyActionHandler handles the tab
 79      * as well as the left and right arrow keys. The selected cell is shifted
 80      * forward or backward as given by the value of delta (+1, -1).
 81      * 
 82      * @param rowHeaderTable    A JTable containing the row header columns for the
 83      *                          identity table.
 84      * @param mainTable         A JTable containing the remainder of the columns.
 85      * @param rowSelectionModel 
 86      * @param rowHeaderColumnSelectionModel
 87      * @param mainColumnSelectionModel
 88      * @param delta
 89      * @param wrap
 90      */
 91     public TabKeyActionHandler(JTable             rowHeaderTable,           JTable             mainTable,
 92                                ListSelectionModel rowSelectionModel,        ListSelectionModel headerColumnSelectionModel,
 93                                ListSelectionModel mainColumnSelectionModel, int                delta,
 94                                boolean            wrap)
 95     {
 96         this.rowHeaderTable             = rowHeaderTable;
 97         this.mainTable                  = mainTable;
 98         this.rowSelectionModel          = rowSelectionModel;
 99         this.rowHeaderColumnSelectionModel = headerColumnSelectionModel;
100         this.mainColumnSelectionModel   = mainColumnSelectionModel;
101         this.delta                      = delta;
102         this.wrap                       = wrap;
103     }
104     
105     /**
106      * Handle the key event by shifting the selected cell forward or backward
107      * one column.
108      * 
109      * @param event
110      */
111     public void actionPerformed(ActionEvent event)
112     {
113         JTable currentTable = (JTable)event.getSource();
114         
115         // If the table is being edited and we can't stop it, return immediatly.
116         if (currentTable.isEditing() && !currentTable.getCellEditor().stopCellEditing())
117         {
118             return;
119         }
120         
121         adjustColumn(currentTable);
122     }
123     
124     /**
125      * Adjust the selected table cell forward or backward one column.
126      * 
127      * @param currentTable
128      */
129     public void adjustColumn(JTable currentTable)
130     {
131         // This handles the case where no cell is selected. Just as with the
132         // standard JTable, the selection is shifted onto the JTable.
133         int rowIndex = rowSelectionModel.getLeadSelectionIndex();
134         if (rowIndex < 0)
135         {
136             rowIndex = 0;
137         }
138         
139         int columnIndex;
140         
141         // We have to know which table is the crrently selected table before we
142         // can adjust the selected cell. Deal with the rowHeaderTable case
143         // first.
144         if (currentTable == rowHeaderTable)
145         {
146             // 
147             columnIndex = rowHeaderColumnSelectionModel.getLeadSelectionIndex();
148             // If no column is selected
149             if (columnIndex < 0)
150             {
151                 // shift to the frst column
152                 columnIndex = 0;
153             }
154             
155             // Move the cell forward or backward as per the (+1, -1) value of
156             // delta.
157             columnIndex += delta;
158             
159             // If we have walked off the edge of the row header table.
160             if (columnIndex > rowHeaderTable.getColumnCount()-1)
161             {
162                 // Stepped from the row header onto the left edge (0 column) of the main table.
163                 Rectangle currentCell = mainTable.getCellRect(rowIndex, 0, true);
164                 // Make sure that the cell is visable. This may scroll the main table.
165                 mainTable.scrollRectToVisible(currentCell);
166                 // Clear the selectio in the row header.
167                 rowHeaderColumnSelectionModel.clearSelection();
168                 // Shift the focus from the row header table to the main table.
169                 mainTable.requestFocusInWindow();
170                 // We know that the selected column is the first main column.
171                 mainColumnSelectionModel.setSelectionInterval(0, 0);
172                 // And we are on the same row as previously.
173                 rowSelectionModel.setSelectionInterval(rowIndex, rowIndex);
174                 return;
175             }
176             else if (columnIndex < 0)
177             {
178                 // We just walked off the left edge of the row. For example,
179                 // shift-tab in the first column, if we don't wrap to the next
180                 // column, we're done.
181                 if (!wrap)
182                 {
183                     return;
184                 }
185                 // We will want the last column in the previous row.
186                 rowIndex -=1;
187                 columnIndex = mainTable.getColumnCount()-1;
188                 
189                 // If we just stepped off the top of the table too
190                 if (rowIndex < 0)
191                 {
192                     // Wrap around to the bottom, just as the stock JTable.
193                     rowIndex = mainTable.getRowCount()-1;
194                 }
195                 
196                 // Wrap from the left edge the header column, to the right edge of the previous
197                 // column in the main table. Scroll to the appropriate column in the main table.
198                 Rectangle currentCell = mainTable.getCellRect(rowIndex, columnIndex, true);
199                 mainTable.scrollRectToVisible(currentCell);
200                 rowHeaderColumnSelectionModel.clearSelection();
201                 mainTable.requestFocusInWindow();
202                 mainColumnSelectionModel.setSelectionInterval(columnIndex, columnIndex);
203                 rowSelectionModel.setSelectionInterval(rowIndex, rowIndex);
204                 return;
205             }
206             else
207             {
208                 // Still within the row header, so select, and scroll to the appropriate column.
209                 Rectangle currentCell = rowHeaderTable.getCellRect(rowIndex, columnIndex, true);
210                 rowHeaderTable.scrollRectToVisible(currentCell);
211                 rowHeaderColumnSelectionModel.setSelectionInterval(columnIndex, columnIndex);
212                 rowSelectionModel.setSelectionInterval(rowIndex, rowIndex);
213                 return;
214             }
215         }
216         else
217         {
218             // A cell in the main table is the currently selected cell.
219             columnIndex = mainColumnSelectionModel.getLeadSelectionIndex();
220             if (columnIndex < 0)
221             {
222                 columnIndex = 0;
223             }
224             
225             columnIndex += delta;
226             
227             // If we have walked off the right edge of the main table.
228             if (columnIndex > mainTable.getColumnCount()-1)
229             {
230                 // If we don't wrap to the next column, we're done.
231                 if (!wrap)
232                 {
233                     return;
234                 }
235                 
236                 // Wrap to the first column of the next row.
237                 rowIndex += 1;
238                 columnIndex = 0;
239                 
240                 // And if we walked off the bottom of the table,
241                 if (rowIndex > mainTable.getRowCount()-1)
242                 {
243                     // wrap to the top.
244                     rowIndex = 0;
245                 }
246                 
247                 // We have walked of the right edge of the main table. Step around to the zero column of
248                 // the next row of the row header. Scroll to the selected row header cell, and to the
249                 // leftmost (0) column of the main table.
250                 Rectangle currentCell = rowHeaderTable.getCellRect(rowIndex, columnIndex, true);
251                 rowHeaderTable.scrollRectToVisible(currentCell);
252                 currentCell = mainTable.getCellRect(0, 0, true);
253                 mainTable.scrollRectToVisible(currentCell);
254                 mainColumnSelectionModel.clearSelection();
255                 rowHeaderTable.requestFocusInWindow();
256                 rowHeaderColumnSelectionModel.setSelectionInterval(columnIndex, columnIndex);
257                 rowSelectionModel.setSelectionInterval(rowIndex, rowIndex);
258                 return;
259             }
260             else if (columnIndex < 0)
261             {
262                 // We just stepped from the left edge of the main table to the right edge of the header table.
263                 // Select and scroll to the rightmost (highest value) column in the row header.
264                 Rectangle currentCell = rowHeaderTable.getCellRect(rowIndex, columnIndex, true);
265                 rowHeaderTable.scrollRectToVisible(currentCell);
266                 mainColumnSelectionModel.clearSelection();
267                 rowHeaderTable.requestFocusInWindow();
268                 // This is the highest (rightmost) column in the header.
269                 columnIndex = rowHeaderTable.getColumnCount()-1;
270                 rowHeaderColumnSelectionModel.setSelectionInterval(columnIndex, columnIndex);
271                 rowSelectionModel.setSelectionInterval(rowIndex, rowIndex);
272                 return;
273             }
274             else
275             {
276                 // Step to the next cell in the main table.
277                 Rectangle currentCell = mainTable.getCellRect(rowIndex, columnIndex, true);
278                 mainTable.scrollRectToVisible(currentCell);
279                 mainColumnSelectionModel.setSelectionInterval(columnIndex, columnIndex);
280                 rowSelectionModel.setSelectionInterval(rowIndex, rowIndex);
281                 return;
282             }
283         }
284     }
285 }
286 
287