OSDN Git Service

2006-06-13 Thomas Fitzsimmons <fitzsim@redhat.com>
[pf3gnuchains/gcc-fork.git] / libjava / classpath / javax / swing / plaf / basic / BasicTableUI.java
1 /* BasicTableUI.java --
2    Copyright (C) 2004 Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 02110-1301 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package javax.swing.plaf.basic;
40
41 import gnu.classpath.NotImplementedException;
42
43 import java.awt.Color;
44 import java.awt.Component;
45 import java.awt.ComponentOrientation;
46 import java.awt.Dimension;
47 import java.awt.Graphics;
48 import java.awt.Point;
49 import java.awt.Rectangle;
50 import java.awt.event.ActionEvent;
51 import java.awt.event.ActionListener;
52 import java.awt.event.FocusEvent;
53 import java.awt.event.FocusListener;
54 import java.awt.event.KeyEvent;
55 import java.awt.event.KeyListener;
56 import java.awt.event.MouseEvent;
57 import java.beans.PropertyChangeEvent;
58 import java.beans.PropertyChangeListener;
59
60 import javax.swing.AbstractAction;
61 import javax.swing.ActionMap;
62 import javax.swing.CellRendererPane;
63 import javax.swing.DefaultCellEditor;
64 import javax.swing.DefaultListSelectionModel;
65 import javax.swing.InputMap;
66 import javax.swing.JComponent;
67 import javax.swing.JTable;
68 import javax.swing.KeyStroke;
69 import javax.swing.ListSelectionModel;
70 import javax.swing.LookAndFeel;
71 import javax.swing.UIManager;
72 import javax.swing.border.Border;
73 import javax.swing.event.ChangeEvent;
74 import javax.swing.event.MouseInputListener;
75 import javax.swing.plaf.ActionMapUIResource;
76 import javax.swing.plaf.ComponentUI;
77 import javax.swing.plaf.InputMapUIResource;
78 import javax.swing.plaf.TableUI;
79 import javax.swing.table.TableCellEditor;
80 import javax.swing.table.TableCellRenderer;
81 import javax.swing.table.TableColumn;
82 import javax.swing.table.TableColumnModel;
83 import javax.swing.table.TableModel;
84
85 public class BasicTableUI extends TableUI
86 {
87   public static ComponentUI createUI(JComponent comp) 
88   {
89     return new BasicTableUI();
90   }
91
92   protected FocusListener focusListener;  
93   protected KeyListener keyListener;   
94   protected MouseInputListener  mouseInputListener;   
95   protected CellRendererPane rendererPane;   
96   protected JTable table;
97
98   /** The normal cell border. */
99   Border cellBorder;
100
101   /** The action bound to KeyStrokes. */
102   TableAction action;
103
104   /**
105    * Listens for changes to the tables properties.
106    */
107   private PropertyChangeListener propertyChangeListener;
108
109   /**
110    * Handles key events for the JTable. Key events should be handled through
111    * the InputMap/ActionMap mechanism since JDK1.3. This class is only there
112    * for backwards compatibility.
113    * 
114    * @author Roman Kennke (kennke@aicas.com)
115    */
116   public class KeyHandler implements KeyListener
117   {
118
119     /**
120      * Receives notification that a key has been pressed and released.
121      * Activates the editing session for the focused cell by pressing the
122      * character keys.
123      *
124      * @param event the key event
125      */
126     public void keyTyped(KeyEvent event)
127     {
128       // Key events should be handled through the InputMap/ActionMap mechanism
129       // since JDK1.3. This class is only there for backwards compatibility.
130       
131       // Editor activation is a specific kind of response to ''any''
132       // character key. Hence it is handled here.
133       if (!table.isEditing() && table.isEnabled())
134         {
135           int r = table.getSelectedRow();
136           int c = table.getSelectedColumn();
137           if (table.isCellEditable(r, c))
138             table.editCellAt(r, c);
139         }
140     }
141
142     /**
143      * Receives notification that a key has been pressed.
144      *
145      * @param event the key event
146      */
147     public void keyPressed(KeyEvent event)
148     {
149       // Key events should be handled through the InputMap/ActionMap mechanism
150       // since JDK1.3. This class is only there for backwards compatibility.
151     }
152
153     /**
154      * Receives notification that a key has been released.
155      *
156      * @param event the key event
157      */
158     public void keyReleased(KeyEvent event)
159     {
160       // Key events should be handled through the InputMap/ActionMap mechanism
161       // since JDK1.3. This class is only there for backwards compatibility.
162     }
163   }
164
165   public class FocusHandler implements FocusListener
166   {
167     public void focusGained(FocusEvent e) 
168     {
169       // TODO: Implement this properly.
170     }
171
172     public void focusLost(FocusEvent e) 
173     {
174       // TODO: Implement this properly.
175     }
176   }
177
178   public class MouseInputHandler implements MouseInputListener
179   {
180     Point begin, curr;
181
182     private void updateSelection(boolean controlPressed)
183     {
184       // Update the rows
185       int lo_row = table.rowAtPoint(begin);
186       int hi_row  = table.rowAtPoint(curr);
187       ListSelectionModel rowModel = table.getSelectionModel();
188       if (lo_row != -1 && hi_row != -1)
189         {
190           if (controlPressed && rowModel.getSelectionMode() 
191               != ListSelectionModel.SINGLE_SELECTION)
192             rowModel.addSelectionInterval(lo_row, hi_row);
193           else
194             rowModel.setSelectionInterval(lo_row, hi_row);
195         }
196       
197       // Update the columns
198       int lo_col = table.columnAtPoint(begin);
199       int hi_col = table.columnAtPoint(curr);
200       ListSelectionModel colModel = table.getColumnModel().
201         getSelectionModel();
202       if (lo_col != -1 && hi_col != -1)
203         {
204           if (controlPressed && colModel.getSelectionMode() != 
205               ListSelectionModel.SINGLE_SELECTION)
206             colModel.addSelectionInterval(lo_col, hi_col);
207           else
208             colModel.setSelectionInterval(lo_col, hi_col);
209         }
210     }
211     
212     /**
213      * For the double click, start the cell editor.
214      */
215     public void mouseClicked(MouseEvent e)
216     {
217       Point p = e.getPoint();
218       int row = table.rowAtPoint(p);
219       int col = table.columnAtPoint(p);
220       if (table.isCellEditable(row, col))
221         {
222           // If the cell editor is the default editor, we request the
223           // number of the required clicks from it. Otherwise,
224           // require two clicks (double click).
225           TableCellEditor editor = table.getCellEditor(row, col);
226           if (editor instanceof DefaultCellEditor)
227             {
228               DefaultCellEditor ce = (DefaultCellEditor) editor;
229               if (e.getClickCount() < ce.getClickCountToStart())
230                 return;
231             }
232           table.editCellAt(row, col);
233         }
234     }
235
236     public void mouseDragged(MouseEvent e) 
237     {
238       if (table.isEnabled())
239         {
240           curr = new Point(e.getX(), e.getY());
241           updateSelection(e.isControlDown());
242         }
243     }
244
245     public void mouseEntered(MouseEvent e) 
246     {
247       // TODO: What should be done here, if anything?
248     }
249
250     public void mouseExited(MouseEvent e) 
251     {
252       // TODO: What should be done here, if anything?
253     }
254
255     public void mouseMoved(MouseEvent e) 
256     {
257       // TODO: What should be done here, if anything?
258     }
259
260     public void mousePressed(MouseEvent e) 
261     {
262       if (table.isEnabled())
263         {
264           ListSelectionModel rowModel = table.getSelectionModel();
265           ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
266           int rowLead = rowModel.getLeadSelectionIndex();
267           int colLead = colModel.getLeadSelectionIndex();
268
269           begin = new Point(e.getX(), e.getY());
270           curr = new Point(e.getX(), e.getY());
271           //if control is pressed and the cell is already selected, deselect it
272           if (e.isControlDown() && table.isCellSelected(
273               table.rowAtPoint(begin), table.columnAtPoint(begin)))
274             {                                       
275               table.getSelectionModel().
276               removeSelectionInterval(table.rowAtPoint(begin), 
277                                       table.rowAtPoint(begin));
278               table.getColumnModel().getSelectionModel().
279               removeSelectionInterval(table.columnAtPoint(begin), 
280                                       table.columnAtPoint(begin));
281             }
282           else
283             updateSelection(e.isControlDown());
284
285           // If we were editing, but the moved to another cell, stop editing
286           if (rowLead != rowModel.getLeadSelectionIndex() ||
287               colLead != colModel.getLeadSelectionIndex())
288             if (table.isEditing())
289               table.editingStopped(new ChangeEvent(e));
290         }
291     }
292
293     public void mouseReleased(MouseEvent e) 
294     {
295       if (table.isEnabled())
296         {
297           begin = null;
298           curr = null;
299         }
300     }
301   }
302
303   /**
304    * Listens for changes to the model property of the JTable and adjusts some
305    * settings.
306    *
307    * @author Roman Kennke (kennke@aicas.com)
308    */
309   private class PropertyChangeHandler implements PropertyChangeListener
310   {
311     /**
312      * Receives notification if one of the JTable's properties changes.
313      *
314      * @param ev the property change event
315      */
316     public void propertyChange(PropertyChangeEvent ev)
317     {
318       String propName = ev.getPropertyName();
319       if (propName.equals("model"))
320         {
321           ListSelectionModel rowSel = table.getSelectionModel();
322           rowSel.clearSelection();
323           ListSelectionModel colSel = table.getColumnModel().getSelectionModel();
324           colSel.clearSelection();
325           TableModel model = table.getModel();
326
327           // Adjust lead and anchor selection indices of the row and column
328           // selection models.
329           if (model.getRowCount() > 0)
330             {
331               rowSel.setAnchorSelectionIndex(0);
332               rowSel.setLeadSelectionIndex(0);
333             }
334           else
335             {
336               rowSel.setAnchorSelectionIndex(-1);
337               rowSel.setLeadSelectionIndex(-1);
338             }
339           if (model.getColumnCount() > 0)
340             {
341               colSel.setAnchorSelectionIndex(0);
342               colSel.setLeadSelectionIndex(0);
343             }
344           else
345             {
346               colSel.setAnchorSelectionIndex(-1);
347               colSel.setLeadSelectionIndex(-1);
348             }
349         }
350     }
351   }
352
353   protected FocusListener createFocusListener() 
354   {
355     return new FocusHandler();
356   }
357
358   protected MouseInputListener createMouseInputListener() 
359   {
360     return new MouseInputHandler();
361   }
362
363
364   /**
365    * Creates and returns a key listener for the JTable.
366    *
367    * @return a key listener for the JTable
368    */
369   protected KeyListener createKeyListener()
370   {
371     return new KeyHandler();
372   }
373
374   /**
375    * Return the maximum size of the table. The maximum height is the row 
376     * height times the number of rows. The maximum width is the sum of 
377     * the maximum widths of each column.
378     * 
379     *  @param comp the component whose maximum size is being queried,
380     *  this is ignored.
381     *  @return a Dimension object representing the maximum size of the table,
382     *  or null if the table has no elements.
383    */
384   public Dimension getMaximumSize(JComponent comp) 
385   {
386     int maxTotalColumnWidth = 0;
387     for (int i = 0; i < table.getColumnCount(); i++)
388       maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth();
389
390     return new Dimension(maxTotalColumnWidth, getHeight());
391   }
392
393   /**
394    * Return the minimum size of the table. The minimum height is the row 
395     * height times the number of rows. The minimum width is the sum of 
396     * the minimum widths of each column.
397     * 
398     *  @param comp the component whose minimum size is being queried,
399     *  this is ignored.
400     *  @return a Dimension object representing the minimum size of the table,
401     *  or null if the table has no elements.
402    */
403   public Dimension getMinimumSize(JComponent comp) 
404   {
405     int minTotalColumnWidth = 0;
406     for (int i = 0; i < table.getColumnCount(); i++)
407       minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth();
408
409     return new Dimension(minTotalColumnWidth, getHeight());
410   }
411
412   /**
413    * Returns the preferred size for the table of that UI.
414    *
415    * @param comp ignored, the <code>table</code> field is used instead
416    *
417    * @return the preferred size for the table of that UI
418    */
419   public Dimension getPreferredSize(JComponent comp) 
420   {
421     int prefTotalColumnWidth = 0;
422     for (int i = 0; i < table.getColumnCount(); i++)
423       {
424         TableColumn col = table.getColumnModel().getColumn(i);
425         prefTotalColumnWidth += col.getPreferredWidth();
426       }
427     return new Dimension(prefTotalColumnWidth, getHeight());
428   }
429
430   /**
431    * Returns the table height. This helper method is used by
432    * {@link #getMinimumSize(JComponent)}, {@link #getPreferredSize(JComponent)}
433    * and {@link #getMaximumSize(JComponent)} to determine the table height.
434    *
435    * @return the table height
436    */
437   private int getHeight()
438   {
439     int height = 0;
440     int rowCount = table.getRowCount(); 
441     if (rowCount > 0 && table.getColumnCount() > 0)
442       {
443         Rectangle r = table.getCellRect(rowCount - 1, 0, true);
444         height = r.y + r.height;
445       }
446     return height;
447   }
448
449   protected void installDefaults() 
450   {
451     LookAndFeel.installColorsAndFont(table, "Table.background",
452                                      "Table.foreground", "Table.font");
453     table.setGridColor(UIManager.getColor("Table.gridColor"));
454     table.setSelectionForeground(UIManager.getColor("Table.selectionForeground"));
455     table.setSelectionBackground(UIManager.getColor("Table.selectionBackground"));
456     table.setOpaque(true);
457   }
458
459   protected void installKeyboardActions() 
460   {
461     InputMap ancestorMap = (InputMap) UIManager.get("Table.ancestorInputMap");
462     InputMapUIResource parentInputMap = new InputMapUIResource();
463     // FIXME: The JDK uses a LazyActionMap for parentActionMap
464     ActionMap parentActionMap = new ActionMapUIResource();
465     action = new TableAction();
466     Object keys[] = ancestorMap.allKeys();
467     // Register key bindings in the UI InputMap-ActionMap pair
468     for (int i = 0; i < keys.length; i++)
469       {
470         KeyStroke stroke = (KeyStroke) keys[i];
471         String actionString = (String) ancestorMap.get(stroke);
472
473         parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(),
474                                                   stroke.getModifiers()),
475                            actionString);
476
477         parentActionMap.put(actionString, 
478                             new ActionListenerProxy(action, actionString));
479
480       }
481     // Set the UI InputMap-ActionMap pair to be the parents of the
482     // JTable's InputMap-ActionMap pair
483     parentInputMap.setParent(table.getInputMap(
484         JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent());
485     parentActionMap.setParent(table.getActionMap().getParent());
486     table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
487       setParent(parentInputMap);
488     table.getActionMap().setParent(parentActionMap);
489   }
490
491   /**
492    * This class is used to mimmic the behaviour of the JDK when registering
493    * keyboard actions.  It is the same as the private class used in JComponent
494    * for the same reason.  This class receives an action event and dispatches
495    * it to the true receiver after altering the actionCommand property of the
496    * event.
497    */
498   private static class ActionListenerProxy
499     extends AbstractAction
500   {
501     ActionListener target;
502     String bindingCommandName;
503
504     public ActionListenerProxy(ActionListener li, 
505                                String cmd)
506     {
507       target = li;
508       bindingCommandName = cmd;
509     }
510
511     public void actionPerformed(ActionEvent e)
512     {
513       ActionEvent derivedEvent = new ActionEvent(e.getSource(),
514                                                  e.getID(),
515                                                  bindingCommandName,
516                                                  e.getModifiers());
517       target.actionPerformed(derivedEvent);
518     }
519   }
520
521   /**
522    * This class implements the actions that we want to happen
523    * when specific keys are pressed for the JTable.  The actionPerformed
524    * method is called when a key that has been registered for the JTable
525    * is received.
526    */
527   class TableAction extends AbstractAction
528   {
529     /**
530      * What to do when this action is called.
531      *
532      * @param e the ActionEvent that caused this action.
533      */
534     public void actionPerformed(ActionEvent e)
535     {
536       DefaultListSelectionModel rowModel 
537           = (DefaultListSelectionModel) table.getSelectionModel();
538       DefaultListSelectionModel colModel 
539           = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel();
540
541       int rowLead = rowModel.getLeadSelectionIndex();
542       int rowMax = table.getModel().getRowCount() - 1;
543       
544       int colLead = colModel.getLeadSelectionIndex();
545       int colMax = table.getModel().getColumnCount() - 1;
546       
547       String command = e.getActionCommand();
548       
549       if (command.equals("selectPreviousRowExtendSelection"))
550         {
551           rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
552         }
553       else if (command.equals("selectLastColumn"))
554         {
555           colModel.setSelectionInterval(colMax, colMax);
556         }
557       else if (command.equals("startEditing"))
558         {
559           if (table.isCellEditable(rowLead, colLead))
560             table.editCellAt(rowLead, colLead);
561         }
562       else if (command.equals("selectFirstRowExtendSelection"))
563         {              
564           rowModel.setLeadSelectionIndex(0);
565         }
566       else if (command.equals("selectFirstColumn"))
567         {
568           colModel.setSelectionInterval(0, 0);
569         }
570       else if (command.equals("selectFirstColumnExtendSelection"))
571         {
572           colModel.setLeadSelectionIndex(0);
573         }      
574       else if (command.equals("selectLastRow"))
575         {
576           rowModel.setSelectionInterval(rowMax, rowMax);
577         }
578       else if (command.equals("selectNextRowExtendSelection"))
579         {
580           rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
581         }
582       else if (command.equals("selectFirstRow"))
583         {
584           rowModel.setSelectionInterval(0, 0);
585         }
586       else if (command.equals("selectNextColumnExtendSelection"))
587         {
588           colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
589         }
590       else if (command.equals("selectLastColumnExtendSelection"))
591         {
592           colModel.setLeadSelectionIndex(colMax);
593         }
594       else if (command.equals("selectPreviousColumnExtendSelection"))
595         {
596           colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
597         }
598       else if (command.equals("selectNextRow"))
599         {
600           rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
601                                         Math.min(rowLead + 1, rowMax));
602         }
603       else if (command.equals("scrollUpExtendSelection"))
604         {
605           int target;
606           if (rowLead == getFirstVisibleRowIndex())
607             target = Math.max(0, rowLead - (getLastVisibleRowIndex() 
608                 - getFirstVisibleRowIndex() + 1));
609           else
610             target = getFirstVisibleRowIndex();
611           
612           rowModel.setLeadSelectionIndex(target);
613           colModel.setLeadSelectionIndex(colLead);
614         }
615       else if (command.equals("selectPreviousRow"))
616         {
617           rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
618                                         Math.max(rowLead - 1, 0));
619         }
620       else if (command.equals("scrollRightChangeSelection"))
621         {
622           int target;
623           if (colLead == getLastVisibleColumnIndex())
624             target = Math.min(colMax, colLead + (getLastVisibleColumnIndex() 
625                 - getFirstVisibleColumnIndex() + 1));
626           else
627             target = getLastVisibleColumnIndex();
628           
629           colModel.setSelectionInterval(target, target);
630           rowModel.setSelectionInterval(rowLead, rowLead);
631         }
632       else if (command.equals("selectPreviousColumn"))
633         {
634           colModel.setSelectionInterval(Math.max(colLead - 1, 0),
635                                         Math.max(colLead - 1, 0));
636         }
637       else if (command.equals("scrollLeftChangeSelection"))
638         {
639           int target;
640           if (colLead == getFirstVisibleColumnIndex())
641             target = Math.max(0, colLead - (getLastVisibleColumnIndex() 
642                 - getFirstVisibleColumnIndex() + 1));
643           else
644             target = getFirstVisibleColumnIndex();
645           
646           colModel.setSelectionInterval(target, target);
647           rowModel.setSelectionInterval(rowLead, rowLead);
648         }
649       else if (command.equals("clearSelection"))
650         {
651           table.clearSelection();
652         }
653       else if (command.equals("cancel"))
654         {
655           // FIXME: implement other parts of "cancel" like undo-ing last
656           // selection.  Right now it just calls editingCancelled if
657           // we're currently editing.
658           if (table.isEditing())
659             table.editingCanceled(new ChangeEvent("cancel"));
660         }
661       else if (command.equals("selectNextRowCell")
662                || command.equals("selectPreviousRowCell")
663                || command.equals("selectNextColumnCell")
664                || command.equals("selectPreviousColumnCell"))
665         {
666           // If nothing is selected, select the first cell in the table
667           if (table.getSelectedRowCount() == 0 && 
668               table.getSelectedColumnCount() == 0)
669             {
670               rowModel.setSelectionInterval(0, 0);
671               colModel.setSelectionInterval(0, 0);
672               return;
673             }
674           
675           // If the lead selection index isn't selected (ie a remove operation
676           // happened, then set the lead to the first selected cell in the
677           // table
678           if (!table.isCellSelected(rowLead, colLead))
679             {
680               rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(), 
681                                             rowModel.getMinSelectionIndex());
682               colModel.addSelectionInterval(colModel.getMinSelectionIndex(), 
683                                             colModel.getMinSelectionIndex());
684               return;
685             }
686           
687           // multRowsSelected and multColsSelected tell us if multiple rows or
688           // columns are selected, respectively
689           boolean multRowsSelected, multColsSelected;
690           multRowsSelected = table.getSelectedRowCount() > 1 &&
691             table.getRowSelectionAllowed();
692           
693           multColsSelected = table.getSelectedColumnCount() > 1 &&
694             table.getColumnSelectionAllowed();
695           
696           // If there is just one selection, select the next cell, and wrap
697           // when you get to the edges of the table.
698           if (!multColsSelected && !multRowsSelected)
699             {
700               if (command.indexOf("Column") != -1) 
701                 advanceSingleSelection(colModel, colMax, rowModel, rowMax, 
702                     command.equals("selectPreviousColumnCell"));
703               else
704                 advanceSingleSelection(rowModel, rowMax, colModel, colMax, 
705                     command.equals("selectPreviousRowCell"));
706               return;
707             }
708           
709           
710           // rowMinSelected and rowMaxSelected are the minimum and maximum
711           // values respectively of selected cells in the row selection model
712           // Similarly for colMinSelected and colMaxSelected.
713           int rowMaxSelected = table.getRowSelectionAllowed() ? 
714             rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1;
715           int rowMinSelected = table.getRowSelectionAllowed() ? 
716             rowModel.getMinSelectionIndex() : 0; 
717           int colMaxSelected = table.getColumnSelectionAllowed() ? 
718             colModel.getMaxSelectionIndex() : 
719             table.getModel().getColumnCount() - 1;
720           int colMinSelected = table.getColumnSelectionAllowed() ? 
721             colModel.getMinSelectionIndex() : 0;
722           
723           // If there are multiple rows and columns selected, select the next
724           // cell and wrap at the edges of the selection.  
725           if (command.indexOf("Column") != -1) 
726             advanceMultipleSelection(colModel, colMinSelected, colMaxSelected, 
727                 rowModel, rowMinSelected, rowMaxSelected, 
728                 command.equals("selectPreviousColumnCell"), true);
729           
730           else
731             advanceMultipleSelection(rowModel, rowMinSelected, rowMaxSelected, 
732                 colModel, colMinSelected, colMaxSelected, 
733                 command.equals("selectPreviousRowCell"), false);
734         }
735       else if (command.equals("selectNextColumn"))
736         {
737           colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
738                                         Math.min(colLead + 1, colMax));
739         }
740       else if (command.equals("scrollLeftExtendSelection"))
741         {
742           int target;
743           if (colLead == getFirstVisibleColumnIndex())
744             target = Math.max(0, colLead - (getLastVisibleColumnIndex() 
745                 - getFirstVisibleColumnIndex() + 1));
746           else
747             target = getFirstVisibleColumnIndex();
748           
749           colModel.setLeadSelectionIndex(target);
750           rowModel.setLeadSelectionIndex(rowLead);
751         }
752       else if (command.equals("scrollDownChangeSelection"))
753         {
754           int target;
755           if (rowLead == getLastVisibleRowIndex())
756             target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex() 
757                 - getFirstVisibleRowIndex() + 1));
758           else
759             target = getLastVisibleRowIndex();
760           
761           rowModel.setSelectionInterval(target, target);
762           colModel.setSelectionInterval(colLead, colLead);
763         }
764       else if (command.equals("scrollRightExtendSelection"))
765         {
766           int target;
767           if (colLead == getLastVisibleColumnIndex())
768             target = Math.min(colMax, colLead + (getLastVisibleColumnIndex() 
769                 - getFirstVisibleColumnIndex() + 1));
770           else
771             target = getLastVisibleColumnIndex();
772           
773           colModel.setLeadSelectionIndex(target);
774           rowModel.setLeadSelectionIndex(rowLead);
775         }
776       else if (command.equals("selectAll"))
777         {
778           table.selectAll();
779         }
780       else if (command.equals("selectLastRowExtendSelection"))
781         {
782           rowModel.setLeadSelectionIndex(rowMax);
783           colModel.setLeadSelectionIndex(colLead);
784         }
785       else if (command.equals("scrollDownExtendSelection"))
786         {
787           int target;
788           if (rowLead == getLastVisibleRowIndex())
789             target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex() 
790                 - getFirstVisibleRowIndex() + 1));
791           else
792             target = getLastVisibleRowIndex();
793           
794           rowModel.setLeadSelectionIndex(target);
795           colModel.setLeadSelectionIndex(colLead);
796         }      
797       else if (command.equals("scrollUpChangeSelection"))
798         {
799           int target;
800           if (rowLead == getFirstVisibleRowIndex())
801             target = Math.max(0, rowLead - (getLastVisibleRowIndex() 
802                 - getFirstVisibleRowIndex() + 1));
803           else
804             target = getFirstVisibleRowIndex();
805           
806           rowModel.setSelectionInterval(target, target);
807           colModel.setSelectionInterval(colLead, colLead);
808         }
809       else if (command.equals("selectNextRowChangeLead"))
810           {
811             if (rowModel.getSelectionMode() 
812                 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
813               {
814                 // just "selectNextRow"
815                 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
816                                               Math.min(rowLead + 1, rowMax));
817                 colModel.setSelectionInterval(colLead, colLead);
818               }
819             else
820               rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
821           }
822       else if (command.equals("selectPreviousRowChangeLead"))
823         {
824           if (rowModel.getSelectionMode() 
825               != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
826             {
827               // just selectPreviousRow
828               rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
829                                             Math.min(rowLead - 1, 0));
830               colModel.setSelectionInterval(colLead, colLead);
831             }
832           else
833             rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0));
834         }
835       else if (command.equals("selectNextColumnChangeLead"))
836         {
837           if (colModel.getSelectionMode() 
838               != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
839             {
840               // just selectNextColumn
841               rowModel.setSelectionInterval(rowLead, rowLead);
842               colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
843                                             Math.min(colLead + 1, colMax));
844             }
845           else
846             colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax));
847         }
848       else if (command.equals("selectPreviousColumnChangeLead"))
849         {
850           if (colModel.getSelectionMode() 
851               != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
852             {
853               // just selectPreviousColumn
854               rowModel.setSelectionInterval(rowLead, rowLead);
855               colModel.setSelectionInterval(Math.max(colLead - 1, 0),
856                                             Math.max(colLead - 1, 0));
857               
858             }
859           else
860             colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0));
861         }
862       else if (command.equals("addToSelection"))
863           {
864             if (!table.isEditing())
865               {
866                 int oldRowAnchor = rowModel.getAnchorSelectionIndex();
867                 int oldColAnchor = colModel.getAnchorSelectionIndex();
868                 rowModel.addSelectionInterval(rowLead, rowLead);
869                 colModel.addSelectionInterval(colLead, colLead);
870                 rowModel.setAnchorSelectionIndex(oldRowAnchor);
871                 colModel.setAnchorSelectionIndex(oldColAnchor);
872               }
873           }
874       else if (command.equals("extendTo"))
875         {
876           rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(),
877                                         rowLead);
878           colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(),
879                                         colLead);
880         }
881       else if (command.equals("toggleAndAnchor"))
882         {
883           if (rowModel.isSelectedIndex(rowLead))
884             rowModel.removeSelectionInterval(rowLead, rowLead);
885           else
886             rowModel.addSelectionInterval(rowLead, rowLead);
887           
888           if (colModel.isSelectedIndex(colLead))
889             colModel.removeSelectionInterval(colLead, colLead);
890           else
891             colModel.addSelectionInterval(colLead, colLead);
892           
893           rowModel.setAnchorSelectionIndex(rowLead);
894           colModel.setAnchorSelectionIndex(colLead);
895         }
896       else if (command.equals("stopEditing"))
897         {
898           table.editingStopped(new ChangeEvent(command));
899         }
900       else 
901         {
902           // If we're here that means we bound this TableAction class
903           // to a keyboard input but we either want to ignore that input
904           // or we just haven't implemented its action yet.
905           
906           // Uncomment the following line to print the names of unused bindings
907           // when their keys are pressed
908           
909           // System.out.println ("not implemented: "+e.getActionCommand());
910         }
911
912       // Any commands whose keyStrokes should be used by the Editor should not
913       // cause editing to be stopped: ie, the SPACE sends "addToSelection" but 
914       // if the table is in editing mode, the space should not cause us to stop
915       // editing because it should be used by the Editor.
916       if (table.isEditing() && command != "startEditing"
917           && command != "addToSelection")
918         table.editingStopped(new ChangeEvent("update"));
919             
920       table.scrollRectToVisible(table.getCellRect(
921           rowModel.getLeadSelectionIndex(), colModel.getLeadSelectionIndex(), 
922           false));
923     }
924     
925     /**
926      * Returns the column index of the first visible column.
927      * @return the column index of the first visible column.
928      */
929     int getFirstVisibleColumnIndex()
930     {
931       ComponentOrientation or = table.getComponentOrientation();
932       Rectangle r = table.getVisibleRect();
933       if (!or.isLeftToRight())
934         r.translate((int) r.getWidth() - 1, 0);
935       return table.columnAtPoint(r.getLocation());
936     }
937     
938     /**
939      * Returns the column index of the last visible column.
940      *
941      */
942     int getLastVisibleColumnIndex()
943     {
944       ComponentOrientation or = table.getComponentOrientation();
945       Rectangle r = table.getVisibleRect();
946       if (or.isLeftToRight())
947         r.translate((int) r.getWidth() - 1, 0);
948       return table.columnAtPoint(r.getLocation());      
949     }
950     
951     /**
952      * Returns the row index of the first visible row.
953      *
954      */
955     int getFirstVisibleRowIndex()
956     {
957       ComponentOrientation or = table.getComponentOrientation();
958       Rectangle r = table.getVisibleRect();
959       if (!or.isLeftToRight())
960         r.translate((int) r.getWidth() - 1, 0);
961       return table.rowAtPoint(r.getLocation());
962     }
963     
964     /**
965      * Returns the row index of the last visible row.
966      *
967      */
968     int getLastVisibleRowIndex()
969     {
970       ComponentOrientation or = table.getComponentOrientation();
971       Rectangle r = table.getVisibleRect();
972       r.translate(0, (int) r.getHeight() - 1);
973       if (or.isLeftToRight())
974         r.translate((int) r.getWidth() - 1, 0);
975       // The next if makes sure that we don't return -1 simply because
976       // there is white space at the bottom of the table (ie, the display
977       // area is larger than the table)
978       if (table.rowAtPoint(r.getLocation()) == -1)
979         {
980           if (getFirstVisibleRowIndex() == -1)
981             return -1;
982           else
983             return table.getModel().getRowCount() - 1;
984         }
985       return table.rowAtPoint(r.getLocation());
986     }
987
988     /**
989      * A helper method for the key bindings.  Used because the actions
990      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
991      *
992      * Selects the next (previous if SHIFT pressed) column for TAB, or row for
993      * ENTER from within the currently selected cells.
994      *
995      * @param firstModel the ListSelectionModel for columns (TAB) or
996      * rows (ENTER)
997      * @param firstMin the first selected index in firstModel
998      * @param firstMax the last selected index in firstModel
999      * @param secondModel the ListSelectionModel for rows (TAB) or 
1000      * columns (ENTER)
1001      * @param secondMin the first selected index in secondModel
1002      * @param secondMax the last selected index in secondModel
1003      * @param reverse true if shift was held for the event
1004      * @param eventIsTab true if TAB was pressed, false if ENTER pressed
1005      */
1006     void advanceMultipleSelection(ListSelectionModel firstModel, int firstMin,
1007                                   int firstMax, ListSelectionModel secondModel, 
1008                                   int secondMin, int secondMax, boolean reverse,
1009                                   boolean eventIsTab)
1010     {
1011       // If eventIsTab, all the "firsts" correspond to columns, otherwise, to 
1012       // rows "seconds" correspond to the opposite
1013       int firstLead = firstModel.getLeadSelectionIndex();
1014       int secondLead = secondModel.getLeadSelectionIndex();
1015       int numFirsts = eventIsTab ? 
1016         table.getModel().getColumnCount() : table.getModel().getRowCount();
1017       int numSeconds = eventIsTab ? 
1018         table.getModel().getRowCount() : table.getModel().getColumnCount();
1019
1020       // check if we have to wrap the "firsts" around, going to the other side
1021       if ((firstLead == firstMax && !reverse) || 
1022           (reverse && firstLead == firstMin))
1023         {
1024           firstModel.addSelectionInterval(reverse ? firstMax : firstMin, 
1025                                           reverse ? firstMax : firstMin);
1026           
1027           // check if we have to wrap the "seconds"
1028           if ((secondLead == secondMax && !reverse) || 
1029               (reverse && secondLead == secondMin))
1030             secondModel.addSelectionInterval(reverse ? secondMax : secondMin, 
1031                                              reverse ? secondMax : secondMin);
1032
1033           // if we're not wrapping the seconds, we have to find out where we
1034           // are within the secondModel and advance to the next cell (or 
1035           // go back to the previous cell if reverse == true)
1036           else
1037             {
1038               int[] secondsSelected;
1039               if (eventIsTab && table.getRowSelectionAllowed() || 
1040                   !eventIsTab && table.getColumnSelectionAllowed())
1041                 secondsSelected = eventIsTab ? 
1042                   table.getSelectedRows() : table.getSelectedColumns();
1043               else
1044                 {
1045                   // if row selection is not allowed, then the entire column gets
1046                   // selected when you click on it, so consider ALL rows selected
1047                   secondsSelected = new int[numSeconds];
1048                   for (int i = 0; i < numSeconds; i++)
1049                   secondsSelected[i] = i;
1050                 }
1051
1052               // and now find the "next" index within the model
1053               int secondIndex = reverse ? secondsSelected.length - 1 : 0;
1054               if (!reverse)
1055                 while (secondsSelected[secondIndex] <= secondLead)
1056                   secondIndex++;
1057               else
1058                 while (secondsSelected[secondIndex] >= secondLead)
1059                   secondIndex--;
1060               
1061               // and select it - updating the lead selection index
1062               secondModel.addSelectionInterval(secondsSelected[secondIndex], 
1063                                                secondsSelected[secondIndex]);
1064             }
1065         }
1066       // We didn't have to wrap the firsts, so just find the "next" first
1067       // and select it, we don't have to change "seconds"
1068       else
1069         {
1070           int[] firstsSelected;
1071           if (eventIsTab && table.getColumnSelectionAllowed() || 
1072               !eventIsTab && table.getRowSelectionAllowed())
1073             firstsSelected = eventIsTab ? 
1074               table.getSelectedColumns() : table.getSelectedRows();
1075           else
1076             {
1077               // if selection not allowed, consider ALL firsts to be selected
1078               firstsSelected = new int[numFirsts];
1079               for (int i = 0; i < numFirsts; i++)
1080                 firstsSelected[i] = i;
1081             }
1082           int firstIndex = reverse ? firstsSelected.length - 1 : 0;
1083           if (!reverse)
1084             while (firstsSelected[firstIndex] <= firstLead)
1085               firstIndex++;
1086           else 
1087             while (firstsSelected[firstIndex] >= firstLead)
1088               firstIndex--;
1089           firstModel.addSelectionInterval(firstsSelected[firstIndex], 
1090                                           firstsSelected[firstIndex]);
1091           secondModel.addSelectionInterval(secondLead, secondLead);
1092         }
1093     }
1094     
1095     /** 
1096      * A helper method for the key  bindings. Used because the actions
1097      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1098      *
1099      * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER)
1100      * in the table, changing the current selection.  All cells in the table
1101      * are eligible, not just the ones that are currently selected.
1102      * @param firstModel the ListSelectionModel for columns (TAB) or rows
1103      * (ENTER)
1104      * @param firstMax the last index in firstModel
1105      * @param secondModel the ListSelectionModel for rows (TAB) or columns
1106      * (ENTER)
1107      * @param secondMax the last index in secondModel
1108      * @param reverse true if SHIFT was pressed for the event
1109      */
1110
1111     void advanceSingleSelection(ListSelectionModel firstModel, int firstMax, 
1112                                 ListSelectionModel secondModel, int secondMax, 
1113                                 boolean reverse)
1114     {
1115       // for TABs, "first" corresponds to columns and "seconds" to rows.
1116       // the opposite is true for ENTERs
1117       int firstLead = firstModel.getLeadSelectionIndex();
1118       int secondLead = secondModel.getLeadSelectionIndex();
1119       
1120       // if we are going backwards subtract 2 because we later add 1
1121       // for a net change of -1
1122       if (reverse && (firstLead == 0))
1123         {
1124           // check if we have to wrap around
1125           if (secondLead == 0)
1126             secondLead += secondMax + 1;
1127           secondLead -= 2;
1128         }
1129       
1130       // do we have to wrap the "seconds"?
1131       if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax))
1132         secondModel.setSelectionInterval((secondLead + 1) % (secondMax + 1), 
1133                                          (secondLead + 1) % (secondMax + 1));
1134       // if not, just reselect the current lead
1135       else
1136         secondModel.setSelectionInterval(secondLead, secondLead);
1137       
1138       // if we are going backwards, subtract 2  because we add 1 later
1139       // for net change of -1
1140       if (reverse)
1141         {
1142           // check for wraparound
1143           if (firstLead == 0)
1144             firstLead += firstMax + 1;
1145           firstLead -= 2;
1146         }
1147       // select the next "first"
1148       firstModel.setSelectionInterval((firstLead + 1) % (firstMax + 1), 
1149                                       (firstLead + 1) % (firstMax + 1));
1150     }
1151   }
1152
1153   protected void installListeners() 
1154   {
1155     if (focusListener == null)
1156       focusListener = createFocusListener();
1157     table.addFocusListener(focusListener);
1158     if (keyListener == null)
1159       keyListener = createKeyListener();
1160     table.addKeyListener(keyListener);
1161     if (mouseInputListener == null)
1162       mouseInputListener = createMouseInputListener();
1163     table.addMouseListener(mouseInputListener);    
1164     table.addMouseMotionListener(mouseInputListener);
1165     if (propertyChangeListener == null)
1166       propertyChangeListener = new PropertyChangeHandler();
1167     table.addPropertyChangeListener(propertyChangeListener);
1168   }
1169
1170   protected void uninstallDefaults() 
1171   {
1172     // TODO: this method used to do the following which is not
1173     // quite right (at least it breaks apps that run fine with the
1174     // JDK):
1175     //
1176     // table.setFont(null);
1177     // table.setGridColor(null);
1178     // table.setForeground(null);
1179     // table.setBackground(null);
1180     // table.setSelectionForeground(null);
1181     // table.setSelectionBackground(null);
1182     //
1183     // This would leave the component in a corrupt state, which is
1184     // not acceptable. A possible solution would be to have component
1185     // level defaults installed, that get overridden by the UI defaults
1186     // and get restored in this method. I am not quite sure about this
1187     // though. / Roman Kennke
1188   }
1189
1190   protected void uninstallKeyboardActions() 
1191     throws NotImplementedException
1192   {
1193     // TODO: Implement this properly.
1194   }
1195
1196   protected void uninstallListeners() 
1197   {
1198     table.removeFocusListener(focusListener);  
1199     table.removeKeyListener(keyListener);
1200     table.removeMouseListener(mouseInputListener);    
1201     table.removeMouseMotionListener(mouseInputListener);
1202     table.removePropertyChangeListener(propertyChangeListener);
1203     propertyChangeListener = null;
1204   }
1205
1206   public void installUI(JComponent comp) 
1207   {
1208     table = (JTable) comp;
1209     rendererPane = new CellRendererPane();
1210     table.add(rendererPane);
1211
1212     installDefaults();
1213     installKeyboardActions();
1214     installListeners();
1215   }
1216
1217   public void uninstallUI(JComponent c) 
1218   {
1219     uninstallListeners();
1220     uninstallKeyboardActions();
1221     uninstallDefaults(); 
1222
1223     table.remove(rendererPane);
1224     rendererPane = null;
1225     table = null;
1226   }
1227
1228   /**
1229    * Paints a single cell in the table.
1230    *
1231    * @param g The graphics context to paint in
1232    * @param row The row number to paint
1233    * @param col The column number to paint
1234    * @param bounds The bounds of the cell to paint, assuming a coordinate
1235    * system beginning at <code>(0,0)</code> in the upper left corner of the
1236    * table
1237    * @param rend A cell renderer to paint with
1238    */
1239   void paintCell(Graphics g, int row, int col, Rectangle bounds,
1240                  TableCellRenderer rend)
1241   {
1242     Component comp = table.prepareRenderer(rend, row, col);
1243     rendererPane.paintComponent(g, comp, table, bounds);
1244   }
1245   
1246   /**
1247    * Paint the associated table.
1248    */
1249   public void paint(Graphics gfx, JComponent ignored) 
1250   {
1251     int ncols = table.getColumnCount();
1252     int nrows = table.getRowCount();
1253     if (nrows == 0 || ncols == 0)
1254       return;
1255
1256     Rectangle clip = gfx.getClipBounds();
1257
1258     // Determine the range of cells that are within the clip bounds.
1259     Point p1 = new Point(clip.x, clip.y);
1260     int c0 = table.columnAtPoint(p1);
1261     if (c0 == -1)
1262       c0 = 0;
1263     int r0 = table.rowAtPoint(p1);
1264     if (r0 == -1)
1265       r0 = 0;
1266     Point p2 = new Point(clip.x + clip.width, clip.y + clip.height);
1267     int cn = table.columnAtPoint(p2);
1268     if (cn == -1)
1269       cn = table.getColumnCount() - 1;
1270     int rn = table.rowAtPoint(p2);
1271     if (rn == -1)
1272       rn = table.getRowCount() - 1;
1273
1274     int columnMargin = table.getColumnModel().getColumnMargin();
1275     int rowMargin = table.getRowMargin();
1276
1277     TableColumnModel cmodel = table.getColumnModel();
1278     int[] widths = new int[cn + 1];
1279     for (int i = c0; i <= cn; i++)
1280       {
1281         widths[i] = cmodel.getColumn(i).getWidth() - columnMargin;
1282       }
1283     
1284     Rectangle bounds = table.getCellRect(r0, c0, false);
1285     // The left boundary of the area being repainted.
1286     int left = bounds.x;
1287     
1288     // The top boundary of the area being repainted.
1289     int top = bounds.y;
1290     
1291     // The bottom boundary of the area being repainted.
1292     int bottom;
1293     
1294     // paint the cell contents
1295     Color grid = table.getGridColor();    
1296     for (int r = r0; r <= rn; ++r)
1297       {
1298         for (int c = c0; c <= cn; ++c)
1299           {
1300             bounds.width = widths[c];
1301             paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c));
1302             bounds.x += widths[c] + columnMargin;
1303           }
1304         bounds.x = left;
1305         bounds.y += table.getRowHeight(r);
1306         // Update row height for tables with custom heights.
1307         bounds.height = table.getRowHeight(r + 1) - rowMargin;
1308       }
1309     
1310     bottom = bounds.y - rowMargin;
1311
1312     // paint vertical grid lines
1313     if (grid != null && table.getShowVerticalLines())
1314       {    
1315         Color save = gfx.getColor();
1316         gfx.setColor(grid);
1317         int x = left - columnMargin;
1318         for (int c = c0; c <= cn; ++c)
1319           {
1320             // The vertical grid is draw right from the cells, so we 
1321             // add before drawing.
1322             x += widths[c] + columnMargin;
1323             gfx.drawLine(x, top, x, bottom);
1324           }
1325         gfx.setColor(save);
1326       }
1327
1328     // paint horizontal grid lines    
1329     if (grid != null && table.getShowHorizontalLines())
1330       {    
1331         Color save = gfx.getColor();
1332         gfx.setColor(grid);
1333         int y = top - rowMargin;
1334         for (int r = r0; r <= rn; ++r)
1335           {
1336             // The horizontal grid is draw below the cells, so we 
1337             // add before drawing.
1338             y += table.getRowHeight(r);
1339             gfx.drawLine(left, y, p2.x, y);
1340           }
1341         gfx.setColor(save);
1342       }
1343   }
1344 }