OSDN Git Service

d3abba217cdec58c914333184526c558d80d6711
[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.
273               isCellSelected(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
484       (table.getInputMap
485        (JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent());
486     parentActionMap.setParent(table.getActionMap().getParent());
487     table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
488       setParent(parentInputMap);
489     table.getActionMap().setParent(parentActionMap);
490   }
491
492   /**
493    * This class is used to mimmic the behaviour of the JDK when registering
494    * keyboard actions.  It is the same as the private class used in JComponent
495    * for the same reason.  This class receives an action event and dispatches
496    * it to the true receiver after altering the actionCommand property of the
497    * event.
498    */
499   private static class ActionListenerProxy
500     extends AbstractAction
501   {
502     ActionListener target;
503     String bindingCommandName;
504
505     public ActionListenerProxy(ActionListener li, 
506                                String cmd)
507     {
508       target = li;
509       bindingCommandName = cmd;
510     }
511
512     public void actionPerformed(ActionEvent e)
513     {
514       ActionEvent derivedEvent = new ActionEvent(e.getSource(),
515                                                  e.getID(),
516                                                  bindingCommandName,
517                                                  e.getModifiers());
518       target.actionPerformed(derivedEvent);
519     }
520   }
521
522   /**
523    * This class implements the actions that we want to happen
524    * when specific keys are pressed for the JTable.  The actionPerformed
525    * method is called when a key that has been registered for the JTable
526    * is received.
527    */
528   class TableAction extends AbstractAction
529   {
530     /**
531      * What to do when this action is called.
532      *
533      * @param e the ActionEvent that caused this action.
534      */
535     public void actionPerformed (ActionEvent e)
536     {
537       DefaultListSelectionModel rowModel = (DefaultListSelectionModel) table.getSelectionModel();
538       DefaultListSelectionModel colModel = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel();
539
540       int rowLead = rowModel.getLeadSelectionIndex();
541       int rowMax = table.getModel().getRowCount() - 1;
542       
543       int colLead = colModel.getLeadSelectionIndex();
544       int colMax = table.getModel().getColumnCount() - 1;
545       
546       String command = e.getActionCommand();
547       
548       if (command.equals("selectPreviousRowExtendSelection"))
549         {
550           rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
551         }
552       else if (command.equals("selectLastColumn"))
553         {
554           colModel.setSelectionInterval(colMax, colMax);
555         }
556       else if (command.equals("startEditing"))
557         {
558           if (table.isCellEditable(rowLead, colLead))
559             table.editCellAt(rowLead,colLead);
560         }
561       else if (command.equals("selectFirstRowExtendSelection"))
562         {              
563           rowModel.setLeadSelectionIndex(0);
564         }
565       else if (command.equals("selectFirstColumn"))
566         {
567           colModel.setSelectionInterval(0, 0);
568         }
569       else if (command.equals("selectFirstColumnExtendSelection"))
570         {
571           colModel.setLeadSelectionIndex(0);
572         }      
573       else if (command.equals("selectLastRow"))
574         {
575           rowModel.setSelectionInterval(rowMax,rowMax);
576         }
577       else if (command.equals("selectNextRowExtendSelection"))
578         {
579           rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
580         }
581       else if (command.equals("selectFirstRow"))
582         {
583           rowModel.setSelectionInterval(0,0);
584         }
585       else if (command.equals("selectNextColumnExtendSelection"))
586         {
587           colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
588         }
589       else if (command.equals("selectLastColumnExtendSelection"))
590         {
591           colModel.setLeadSelectionIndex(colMax);
592         }
593       else if (command.equals("selectPreviousColumnExtendSelection"))
594         {
595           colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
596         }
597       else if (command.equals("selectNextRow"))
598         {
599           rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
600                                         Math.min(rowLead + 1, rowMax));
601         }
602       else if (command.equals("scrollUpExtendSelection"))
603         {
604           int target;
605           if (rowLead == getFirstVisibleRowIndex())
606             target = Math.max
607               (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
625               (colMax, colLead + (getLastVisibleColumnIndex() -
626                                   getFirstVisibleColumnIndex() + 1));
627           else
628             target = getLastVisibleColumnIndex();
629           
630           colModel.setSelectionInterval(target, target);
631           rowModel.setSelectionInterval(rowLead, rowLead);
632         }
633       else if (command.equals("selectPreviousColumn"))
634         {
635           colModel.setSelectionInterval(Math.max(colLead - 1, 0),
636                                         Math.max(colLead - 1, 0));
637         }
638       else if (command.equals("scrollLeftChangeSelection"))
639         {
640           int target;
641           if (colLead == getFirstVisibleColumnIndex())
642             target = Math.max
643               (0, colLead - (getLastVisibleColumnIndex() -
644                              getFirstVisibleColumnIndex() + 1));
645           else
646             target = getFirstVisibleColumnIndex();
647           
648           colModel.setSelectionInterval(target, target);
649           rowModel.setSelectionInterval(rowLead, rowLead);
650         }
651       else if (command.equals("clearSelection"))
652         {
653           table.clearSelection();
654         }
655       else if (command.equals("cancel"))
656         {
657           // FIXME: implement other parts of "cancel" like undo-ing last
658           // selection.  Right now it just calls editingCancelled if
659           // we're currently editing.
660           if (table.isEditing())
661             table.editingCanceled(new ChangeEvent("cancel"));
662         }
663       else if (command.equals("selectNextRowCell")
664                || command.equals("selectPreviousRowCell")
665                || command.equals("selectNextColumnCell")
666                || command.equals("selectPreviousColumnCell"))
667         {
668           // If nothing is selected, select the first cell in the table
669           if (table.getSelectedRowCount() == 0 && 
670               table.getSelectedColumnCount() == 0)
671             {
672               rowModel.setSelectionInterval(0, 0);
673               colModel.setSelectionInterval(0, 0);
674               return;
675             }
676           
677           // If the lead selection index isn't selected (ie a remove operation
678           // happened, then set the lead to the first selected cell in the
679           // table
680           if (!table.isCellSelected(rowLead, colLead))
681             {
682               rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(), 
683                                             rowModel.getMinSelectionIndex());
684               colModel.addSelectionInterval(colModel.getMinSelectionIndex(), 
685                                             colModel.getMinSelectionIndex());
686               return;
687             }
688           
689           // multRowsSelected and multColsSelected tell us if multiple rows or
690           // columns are selected, respectively
691           boolean multRowsSelected, multColsSelected;
692           multRowsSelected = table.getSelectedRowCount() > 1 &&
693             table.getRowSelectionAllowed();
694           
695           multColsSelected = table.getSelectedColumnCount() > 1 &&
696             table.getColumnSelectionAllowed();
697           
698           // If there is just one selection, select the next cell, and wrap
699           // when you get to the edges of the table.
700           if (!multColsSelected && !multRowsSelected)
701             {
702               if (command.indexOf("Column") != -1) 
703                 advanceSingleSelection(colModel, colMax, rowModel, rowMax, 
704                                        (command.equals
705                                         ("selectPreviousColumnCell")));
706               else
707                 advanceSingleSelection(rowModel, rowMax, colModel, colMax, 
708                                        (command.equals 
709                                         ("selectPreviousRowCell")));
710               return;
711             }
712           
713           
714           // rowMinSelected and rowMaxSelected are the minimum and maximum
715           // values respectively of selected cells in the row selection model
716           // Similarly for colMinSelected and colMaxSelected.
717           int rowMaxSelected = table.getRowSelectionAllowed() ? 
718             rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1;
719           int rowMinSelected = table.getRowSelectionAllowed() ? 
720             rowModel.getMinSelectionIndex() : 0; 
721           int colMaxSelected = table.getColumnSelectionAllowed() ? 
722             colModel.getMaxSelectionIndex() : 
723             table.getModel().getColumnCount() - 1;
724           int colMinSelected = table.getColumnSelectionAllowed() ? 
725             colModel.getMinSelectionIndex() : 0;
726           
727           // If there are multiple rows and columns selected, select the next
728           // cell and wrap at the edges of the selection.  
729           if (command.indexOf("Column") != -1) 
730             advanceMultipleSelection(colModel, colMinSelected, colMaxSelected, 
731                                      rowModel, rowMinSelected, rowMaxSelected, 
732                                      (command.equals
733                                       ("selectPreviousColumnCell")), true);
734           
735           else
736             advanceMultipleSelection(rowModel, rowMinSelected, rowMaxSelected, 
737                                      colModel, colMinSelected, colMaxSelected, 
738                                      (command.equals 
739                                       ("selectPreviousRowCell")), false);
740         }
741       else if (command.equals("selectNextColumn"))
742         {
743           colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
744                                         Math.min(colLead + 1, colMax));
745         }
746       else if (command.equals("scrollLeftExtendSelection"))
747         {
748           int target;
749           if (colLead == getFirstVisibleColumnIndex())
750             target = Math.max
751               (0, colLead - (getLastVisibleColumnIndex() -
752                              getFirstVisibleColumnIndex() + 1));
753           else
754             target = getFirstVisibleColumnIndex();
755           
756           colModel.setLeadSelectionIndex(target);
757           rowModel.setLeadSelectionIndex(rowLead);
758         }
759       else if (command.equals("scrollDownChangeSelection"))
760         {
761           int target;
762           if (rowLead == getLastVisibleRowIndex())
763             target = Math.min
764               (rowMax, rowLead + (getLastVisibleRowIndex() - 
765                                   getFirstVisibleRowIndex() + 1));
766           else
767             target = getLastVisibleRowIndex();
768           
769           rowModel.setSelectionInterval(target, target);
770           colModel.setSelectionInterval(colLead, colLead);
771         }
772       else if (command.equals("scrollRightExtendSelection"))
773         {
774           int target;
775           if (colLead == getLastVisibleColumnIndex())
776             target = Math.min
777               (colMax, colLead + (getLastVisibleColumnIndex() -
778                                   getFirstVisibleColumnIndex() + 1));
779           else
780             target = getLastVisibleColumnIndex();
781           
782           colModel.setLeadSelectionIndex(target);
783           rowModel.setLeadSelectionIndex(rowLead);
784         }
785       else if (command.equals("selectAll"))
786         {
787           table.selectAll();
788         }
789       else if (command.equals("selectLastRowExtendSelection"))
790         {
791           rowModel.setLeadSelectionIndex(rowMax);
792           colModel.setLeadSelectionIndex(colLead);
793         }
794       else if (command.equals("scrollDownExtendSelection"))
795         {
796           int target;
797           if (rowLead == getLastVisibleRowIndex())
798             target = Math.min
799               (rowMax, rowLead + (getLastVisibleRowIndex() - 
800                                   getFirstVisibleRowIndex() + 1));
801           else
802             target = getLastVisibleRowIndex();
803           
804           rowModel.setLeadSelectionIndex(target);
805           colModel.setLeadSelectionIndex(colLead);
806         }      
807       else if (command.equals("scrollUpChangeSelection"))
808         {
809           int target;
810           if (rowLead == getFirstVisibleRowIndex())
811             target = Math.max
812               (0, rowLead - (getLastVisibleRowIndex() - 
813                              getFirstVisibleRowIndex() + 1));
814           else
815             target = getFirstVisibleRowIndex();
816           
817           rowModel.setSelectionInterval(target, target);
818           colModel.setSelectionInterval(colLead, colLead);
819         }
820       else if (command.equals("selectNextRowChangeLead"))
821           {
822             if (rowModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
823               {
824                 // just "selectNextRow"
825                 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
826                                               Math.min(rowLead + 1, rowMax));
827                 colModel.setSelectionInterval(colLead,colLead);
828               }
829             else
830               rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
831           }
832       else if (command.equals("selectPreviousRowChangeLead"))
833         {
834           if (rowModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
835             {
836               // just selectPreviousRow
837               rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
838                                             Math.min(rowLead -1, 0));
839               colModel.setSelectionInterval(colLead,colLead);
840             }
841           else
842             rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0));
843         }
844       else if (command.equals("selectNextColumnChangeLead"))
845         {
846           if (colModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
847             {
848               // just selectNextColumn
849               rowModel.setSelectionInterval(rowLead,rowLead);
850               colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
851                                             Math.min(colLead + 1, colMax));
852             }
853           else
854             colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax));
855         }
856       else if (command.equals("selectPreviousColumnChangeLead"))
857         {
858           if (colModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)            
859             {
860               // just selectPreviousColumn
861               rowModel.setSelectionInterval(rowLead,rowLead);
862               colModel.setSelectionInterval(Math.max(colLead - 1, 0),
863                                             Math.max(colLead - 1, 0));
864               
865             }
866           else
867             colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0));
868         }
869       else if (command.equals("addToSelection"))
870           {
871             if (!table.isEditing())
872               {
873                 int oldRowAnchor = rowModel.getAnchorSelectionIndex();
874                 int oldColAnchor = colModel.getAnchorSelectionIndex();
875                 rowModel.addSelectionInterval(rowLead, rowLead);
876                 colModel.addSelectionInterval(colLead, colLead);
877                 rowModel.setAnchorSelectionIndex(oldRowAnchor);
878                 colModel.setAnchorSelectionIndex(oldColAnchor);
879               }
880           }
881       else if (command.equals("extendTo"))
882         {
883           rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(),
884                                         rowLead);
885           colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(),
886                                         colLead);
887         }
888       else if (command.equals("toggleAndAnchor"))
889         {
890           if (rowModel.isSelectedIndex(rowLead))
891             rowModel.removeSelectionInterval(rowLead, rowLead);
892           else
893             rowModel.addSelectionInterval(rowLead, rowLead);
894           
895           if (colModel.isSelectedIndex(colLead))
896             colModel.removeSelectionInterval(colLead, colLead);
897           else
898             colModel.addSelectionInterval(colLead, colLead);
899           
900           rowModel.setAnchorSelectionIndex(rowLead);
901           colModel.setAnchorSelectionIndex(colLead);
902         }
903       else if (command.equals("stopEditing"))
904         {
905           table.editingStopped(new ChangeEvent(command));
906         }
907       else 
908         {
909           // If we're here that means we bound this TableAction class
910           // to a keyboard input but we either want to ignore that input
911           // or we just haven't implemented its action yet.
912           
913           // Uncomment the following line to print the names of unused bindings
914           // when their keys are pressed
915           
916           // System.out.println ("not implemented: "+e.getActionCommand());
917         }
918
919       // Any commands whose keyStrokes should be used by the Editor should not
920       // cause editing to be stopped: ie, the SPACE sends "addToSelection" but 
921       // if the table is in editing mode, the space should not cause us to stop
922       // editing because it should be used by the Editor.
923       if (table.isEditing() && command != "startEditing"
924           && command != "addToSelection")
925         table.editingStopped(new ChangeEvent("update"));
926             
927       table.scrollRectToVisible
928         (table.getCellRect(rowModel.getLeadSelectionIndex(), 
929                            colModel.getLeadSelectionIndex(), false));
930     }
931     
932     /**
933      * Returns the column index of the first visible column.
934      * @return the column index of the first visible column.
935      */
936     int getFirstVisibleColumnIndex()
937     {
938       ComponentOrientation or = table.getComponentOrientation();
939       Rectangle r = table.getVisibleRect();
940       if (!or.isLeftToRight())
941         r.translate((int) r.getWidth() - 1, 0);
942       return table.columnAtPoint(r.getLocation());
943     }
944     
945     /**
946      * Returns the column index of the last visible column.
947      *
948      */
949     int getLastVisibleColumnIndex()
950     {
951       ComponentOrientation or = table.getComponentOrientation();
952       Rectangle r = table.getVisibleRect();
953       if (or.isLeftToRight())
954         r.translate((int) r.getWidth() - 1, 0);
955       return table.columnAtPoint(r.getLocation());      
956     }
957     
958     /**
959      * Returns the row index of the first visible row.
960      *
961      */
962     int getFirstVisibleRowIndex()
963     {
964       ComponentOrientation or = table.getComponentOrientation();
965       Rectangle r = table.getVisibleRect();
966       if (!or.isLeftToRight())
967         r.translate((int) r.getWidth() - 1, 0);
968       return table.rowAtPoint(r.getLocation());
969     }
970     
971     /**
972      * Returns the row index of the last visible row.
973      *
974      */
975     int getLastVisibleRowIndex()
976     {
977       ComponentOrientation or = table.getComponentOrientation();
978       Rectangle r = table.getVisibleRect();
979       r.translate(0, (int) r.getHeight() - 1);
980       if (or.isLeftToRight())
981         r.translate((int) r.getWidth() - 1, 0);
982       // The next if makes sure that we don't return -1 simply because
983       // there is white space at the bottom of the table (ie, the display
984       // area is larger than the table)
985       if (table.rowAtPoint(r.getLocation()) == -1)
986         {
987           if (getFirstVisibleRowIndex() == -1)
988             return -1;
989           else
990             return table.getModel().getRowCount() - 1;
991         }
992       return table.rowAtPoint(r.getLocation());
993     }
994
995     /**
996      * A helper method for the key bindings.  Used because the actions
997      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
998      *
999      * Selects the next (previous if SHIFT pressed) column for TAB, or row for
1000      * ENTER from within the currently selected cells.
1001      *
1002      * @param firstModel the ListSelectionModel for columns (TAB) or
1003      * rows (ENTER)
1004      * @param firstMin the first selected index in firstModel
1005      * @param firstMax the last selected index in firstModel
1006      * @param secondModel the ListSelectionModel for rows (TAB) or 
1007      * columns (ENTER)
1008      * @param secondMin the first selected index in secondModel
1009      * @param secondMax the last selected index in secondModel
1010      * @param reverse true if shift was held for the event
1011      * @param eventIsTab true if TAB was pressed, false if ENTER pressed
1012      */
1013     void advanceMultipleSelection (ListSelectionModel firstModel, int firstMin,
1014                                    int firstMax, ListSelectionModel secondModel, 
1015                                    int secondMin, int secondMax, boolean reverse,
1016                                    boolean eventIsTab)
1017     {
1018       // If eventIsTab, all the "firsts" correspond to columns, otherwise, to rows
1019       // "seconds" correspond to the opposite
1020       int firstLead = firstModel.getLeadSelectionIndex();
1021       int secondLead = secondModel.getLeadSelectionIndex();
1022       int numFirsts = eventIsTab ? 
1023         table.getModel().getColumnCount() : table.getModel().getRowCount();
1024       int numSeconds = eventIsTab ? 
1025         table.getModel().getRowCount() : table.getModel().getColumnCount();
1026
1027       // check if we have to wrap the "firsts" around, going to the other side
1028       if ((firstLead == firstMax && !reverse) || 
1029           (reverse && firstLead == firstMin))
1030         {
1031           firstModel.addSelectionInterval(reverse ? firstMax : firstMin, 
1032                                           reverse ? firstMax : firstMin);
1033           
1034           // check if we have to wrap the "seconds"
1035           if ((secondLead == secondMax && !reverse) || 
1036               (reverse && secondLead == secondMin))
1037             secondModel.addSelectionInterval(reverse ? secondMax : secondMin, 
1038                                              reverse ? secondMax : secondMin);
1039
1040           // if we're not wrapping the seconds, we have to find out where we
1041           // are within the secondModel and advance to the next cell (or 
1042           // go back to the previous cell if reverse == true)
1043           else
1044             {
1045               int[] secondsSelected;
1046               if (eventIsTab && table.getRowSelectionAllowed() || 
1047                   !eventIsTab && table.getColumnSelectionAllowed())
1048                 secondsSelected = eventIsTab ? 
1049                   table.getSelectedRows() : table.getSelectedColumns();
1050               else
1051                 {
1052                   // if row selection is not allowed, then the entire column gets
1053                   // selected when you click on it, so consider ALL rows selected
1054                   secondsSelected = new int[numSeconds];
1055                   for (int i = 0; i < numSeconds; i++)
1056                   secondsSelected[i] = i;
1057                 }
1058
1059               // and now find the "next" index within the model
1060               int secondIndex = reverse ? secondsSelected.length - 1 : 0;
1061               if (!reverse)
1062                 while (secondsSelected[secondIndex] <= secondLead)
1063                   secondIndex++;
1064               else
1065                 while (secondsSelected[secondIndex] >= secondLead)
1066                   secondIndex--;
1067               
1068               // and select it - updating the lead selection index
1069               secondModel.addSelectionInterval(secondsSelected[secondIndex], 
1070                                                secondsSelected[secondIndex]);
1071             }
1072         }
1073       // We didn't have to wrap the firsts, so just find the "next" first
1074       // and select it, we don't have to change "seconds"
1075       else
1076         {
1077           int[] firstsSelected;
1078           if (eventIsTab && table.getColumnSelectionAllowed() || 
1079               !eventIsTab && table.getRowSelectionAllowed())
1080             firstsSelected = eventIsTab ? 
1081               table.getSelectedColumns() : table.getSelectedRows();
1082           else
1083             {
1084               // if selection not allowed, consider ALL firsts to be selected
1085               firstsSelected = new int[numFirsts];
1086               for (int i = 0; i < numFirsts; i++)
1087                 firstsSelected[i] = i;
1088             }
1089           int firstIndex = reverse ? firstsSelected.length - 1 : 0;
1090           if (!reverse)
1091             while (firstsSelected[firstIndex] <= firstLead)
1092               firstIndex++;
1093           else 
1094             while (firstsSelected[firstIndex] >= firstLead)
1095               firstIndex--;
1096           firstModel.addSelectionInterval(firstsSelected[firstIndex], 
1097                                           firstsSelected[firstIndex]);
1098           secondModel.addSelectionInterval(secondLead, secondLead);
1099         }
1100     }
1101     
1102     /** 
1103      * A helper method for the key  bindings. Used because the actions
1104      * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1105      *
1106      * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER)
1107      * in the table, changing the current selection.  All cells in the table
1108      * are eligible, not just the ones that are currently selected.
1109      * @param firstModel the ListSelectionModel for columns (TAB) or rows
1110      * (ENTER)
1111      * @param firstMax the last index in firstModel
1112      * @param secondModel the ListSelectionModel for rows (TAB) or columns
1113      * (ENTER)
1114      * @param secondMax the last index in secondModel
1115      * @param reverse true if SHIFT was pressed for the event
1116      */
1117
1118     void advanceSingleSelection (ListSelectionModel firstModel, int firstMax, 
1119                                  ListSelectionModel secondModel, int secondMax, 
1120                                  boolean reverse)
1121     {
1122       // for TABs, "first" corresponds to columns and "seconds" to rows.
1123       // the opposite is true for ENTERs
1124       int firstLead = firstModel.getLeadSelectionIndex();
1125       int secondLead = secondModel.getLeadSelectionIndex();
1126       
1127       // if we are going backwards subtract 2 because we later add 1
1128       // for a net change of -1
1129       if (reverse && (firstLead == 0))
1130         {
1131           // check if we have to wrap around
1132           if (secondLead == 0)
1133             secondLead += secondMax + 1;
1134           secondLead -= 2;
1135         }
1136       
1137       // do we have to wrap the "seconds"?
1138       if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax))
1139         secondModel.setSelectionInterval((secondLead + 1)%(secondMax + 1), 
1140                                          (secondLead + 1)%(secondMax + 1));
1141       // if not, just reselect the current lead
1142       else
1143         secondModel.setSelectionInterval(secondLead, secondLead);
1144       
1145       // if we are going backwards, subtract 2  because we add 1 later
1146       // for net change of -1
1147       if (reverse)
1148         {
1149           // check for wraparound
1150           if (firstLead == 0)
1151             firstLead += firstMax + 1;
1152           firstLead -= 2;
1153         }
1154       // select the next "first"
1155       firstModel.setSelectionInterval ((firstLead + 1)%(firstMax + 1), 
1156                                        (firstLead + 1)%(firstMax + 1));
1157     }
1158   }
1159
1160   protected void installListeners() 
1161   {
1162     if (focusListener == null)
1163       focusListener = createFocusListener();
1164     table.addFocusListener(focusListener);
1165     if (keyListener == null)
1166       keyListener = createKeyListener();
1167     table.addKeyListener(keyListener);
1168     if (mouseInputListener == null)
1169       mouseInputListener = createMouseInputListener();
1170     table.addMouseListener(mouseInputListener);    
1171     table.addMouseMotionListener(mouseInputListener);
1172     if (propertyChangeListener == null)
1173       propertyChangeListener = new PropertyChangeHandler();
1174     table.addPropertyChangeListener(propertyChangeListener);
1175   }
1176
1177   protected void uninstallDefaults() 
1178   {
1179     // TODO: this method used to do the following which is not
1180     // quite right (at least it breaks apps that run fine with the
1181     // JDK):
1182     //
1183     // table.setFont(null);
1184     // table.setGridColor(null);
1185     // table.setForeground(null);
1186     // table.setBackground(null);
1187     // table.setSelectionForeground(null);
1188     // table.setSelectionBackground(null);
1189     //
1190     // This would leave the component in a corrupt state, which is
1191     // not acceptable. A possible solution would be to have component
1192     // level defaults installed, that get overridden by the UI defaults
1193     // and get restored in this method. I am not quite sure about this
1194     // though. / Roman Kennke
1195   }
1196
1197   protected void uninstallKeyboardActions() 
1198     throws NotImplementedException
1199   {
1200     // TODO: Implement this properly.
1201   }
1202
1203   protected void uninstallListeners() 
1204   {
1205     table.removeFocusListener(focusListener);  
1206     table.removeKeyListener(keyListener);
1207     table.removeMouseListener(mouseInputListener);    
1208     table.removeMouseMotionListener(mouseInputListener);
1209     table.removePropertyChangeListener(propertyChangeListener);
1210     propertyChangeListener = null;
1211   }
1212
1213   public void installUI(JComponent comp) 
1214   {
1215     table = (JTable)comp;
1216     rendererPane = new CellRendererPane();
1217     table.add(rendererPane);
1218
1219     installDefaults();
1220     installKeyboardActions();
1221     installListeners();
1222   }
1223
1224   public void uninstallUI(JComponent c) 
1225   {
1226     uninstallListeners();
1227     uninstallKeyboardActions();
1228     uninstallDefaults(); 
1229
1230     table.remove(rendererPane);
1231     rendererPane = null;
1232     table = null;
1233   }
1234
1235   /**
1236    * Paints a single cell in the table.
1237    *
1238    * @param g The graphics context to paint in
1239    * @param row The row number to paint
1240    * @param col The column number to paint
1241    * @param bounds The bounds of the cell to paint, assuming a coordinate
1242    * system beginning at <code>(0,0)</code> in the upper left corner of the
1243    * table
1244    * @param rend A cell renderer to paint with
1245    */
1246   void paintCell(Graphics g, int row, int col, Rectangle bounds,
1247                  TableCellRenderer rend)
1248   {
1249     Component comp = table.prepareRenderer(rend, row, col);
1250     rendererPane.paintComponent(g, comp, table, bounds);
1251   }
1252   
1253   /**
1254    * Paint the associated table.
1255    */
1256   public void paint(Graphics gfx, JComponent ignored) 
1257   {
1258     int ncols = table.getColumnCount();
1259     int nrows = table.getRowCount();
1260     if (nrows == 0 || ncols == 0)
1261       return;
1262
1263     Rectangle clip = gfx.getClipBounds();
1264
1265     // Determine the range of cells that are within the clip bounds.
1266     Point p1 = new Point(clip.x, clip.y);
1267     int c0 = table.columnAtPoint(p1);
1268     if (c0 == -1)
1269       c0 = 0;
1270     int r0 = table.rowAtPoint(p1);
1271     if (r0 == -1)
1272       r0 = 0;
1273     Point p2 = new Point(clip.x + clip.width, clip.y + clip.height);
1274     int cn = table.columnAtPoint(p2);
1275     if (cn == -1)
1276       cn = table.getColumnCount() - 1;
1277     int rn = table.rowAtPoint(p2);
1278     if (rn == -1)
1279       rn = table.getRowCount() - 1;
1280
1281     int columnMargin = table.getColumnModel().getColumnMargin();
1282     int rowMargin = table.getRowMargin();
1283
1284     TableColumnModel cmodel = table.getColumnModel();
1285     int [] widths = new int[cn+1];
1286     for (int i = c0; i <=cn ; i++)
1287       {
1288         widths[i] = cmodel.getColumn(i).getWidth() - columnMargin;
1289       }
1290     
1291     Rectangle bounds = table.getCellRect(r0, c0, false);
1292     // The left boundary of the area being repainted.
1293     int left = bounds.x;
1294     
1295     // The top boundary of the area being repainted.
1296     int top = bounds.y;
1297     
1298     // The bottom boundary of the area being repainted.
1299     int bottom;
1300     
1301     // paint the cell contents
1302     Color grid = table.getGridColor();    
1303     for (int r = r0; r <= rn; ++r)
1304       {
1305         for (int c = c0; c <= cn; ++c)
1306           {
1307             bounds.width = widths[c];
1308             paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c));
1309             bounds.x += widths[c] + columnMargin;
1310           }
1311         bounds.x = left;
1312         bounds.y += table.getRowHeight(r);
1313         // Update row height for tables with custom heights.
1314         bounds.height = table.getRowHeight(r + 1) - rowMargin;
1315       }
1316     
1317     bottom = bounds.y - rowMargin;
1318
1319     // paint vertical grid lines
1320     if (grid != null && table.getShowVerticalLines())
1321       {    
1322         Color save = gfx.getColor();
1323         gfx.setColor(grid);
1324         int x = left - columnMargin;
1325         for (int c = c0; c <= cn; ++c)
1326           {
1327             // The vertical grid is draw right from the cells, so we 
1328             // add before drawing.
1329             x += widths[c] + columnMargin;
1330             gfx.drawLine(x, top, x, bottom);
1331           }
1332         gfx.setColor(save);
1333       }
1334
1335     // paint horizontal grid lines    
1336     if (grid != null && table.getShowHorizontalLines())
1337       {    
1338         Color save = gfx.getColor();
1339         gfx.setColor(grid);
1340         int y = top - rowMargin;
1341         for (int r = r0; r <= rn; ++r)
1342           {
1343             // The horizontal grid is draw below the cells, so we 
1344             // add before drawing.
1345             y += table.getRowHeight(r);
1346             gfx.drawLine(left, y, p2.x, y);
1347           }
1348         gfx.setColor(save);
1349       }
1350   }
1351 }