1 /* BasicTableUI.java --
2 Copyright (C) 2004 Free Software Foundation, Inc.
4 This file is part of GNU Classpath.
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)
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.
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
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
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. */
39 package javax.swing.plaf.basic;
41 import java.awt.Color;
42 import java.awt.Component;
43 import java.awt.ComponentOrientation;
44 import java.awt.Dimension;
45 import java.awt.Graphics;
46 import java.awt.Point;
47 import java.awt.Rectangle;
48 import java.awt.event.ActionEvent;
49 import java.awt.event.FocusEvent;
50 import java.awt.event.FocusListener;
51 import java.awt.event.KeyEvent;
52 import java.awt.event.KeyListener;
53 import java.awt.event.MouseEvent;
54 import java.beans.PropertyChangeEvent;
55 import java.beans.PropertyChangeListener;
57 import javax.swing.AbstractAction;
58 import javax.swing.Action;
59 import javax.swing.ActionMap;
60 import javax.swing.CellRendererPane;
61 import javax.swing.DefaultCellEditor;
62 import javax.swing.DefaultListSelectionModel;
63 import javax.swing.InputMap;
64 import javax.swing.JComponent;
65 import javax.swing.JTable;
66 import javax.swing.ListSelectionModel;
67 import javax.swing.LookAndFeel;
68 import javax.swing.SwingUtilities;
69 import javax.swing.TransferHandler;
70 import javax.swing.UIManager;
71 import javax.swing.border.Border;
72 import javax.swing.event.ChangeEvent;
73 import javax.swing.event.MouseInputListener;
74 import javax.swing.plaf.ActionMapUIResource;
75 import javax.swing.plaf.ComponentUI;
76 import javax.swing.plaf.TableUI;
77 import javax.swing.table.TableCellEditor;
78 import javax.swing.table.TableCellRenderer;
79 import javax.swing.table.TableColumn;
80 import javax.swing.table.TableColumnModel;
81 import javax.swing.table.TableModel;
83 public class BasicTableUI extends TableUI
85 public static ComponentUI createUI(JComponent comp)
87 return new BasicTableUI();
90 protected FocusListener focusListener;
91 protected KeyListener keyListener;
92 protected MouseInputListener mouseInputListener;
93 protected CellRendererPane rendererPane;
94 protected JTable table;
96 /** The normal cell border. */
99 /** The action bound to KeyStrokes. */
103 * Listens for changes to the tables properties.
105 private PropertyChangeListener propertyChangeListener;
108 * Handles key events for the JTable. Key events should be handled through
109 * the InputMap/ActionMap mechanism since JDK1.3. This class is only there
110 * for backwards compatibility.
112 * @author Roman Kennke (kennke@aicas.com)
114 public class KeyHandler implements KeyListener
118 * Receives notification that a key has been pressed and released.
119 * Activates the editing session for the focused cell by pressing the
122 * @param event the key event
124 public void keyTyped(KeyEvent event)
126 // Key events should be handled through the InputMap/ActionMap mechanism
127 // since JDK1.3. This class is only there for backwards compatibility.
129 // Editor activation is a specific kind of response to ''any''
130 // character key. Hence it is handled here.
131 if (!table.isEditing() && table.isEnabled())
133 int r = table.getSelectedRow();
134 int c = table.getSelectedColumn();
135 if (table.isCellEditable(r, c))
136 table.editCellAt(r, c);
141 * Receives notification that a key has been pressed.
143 * @param event the key event
145 public void keyPressed(KeyEvent event)
147 // Key events should be handled through the InputMap/ActionMap mechanism
148 // since JDK1.3. This class is only there for backwards compatibility.
152 * Receives notification that a key has been released.
154 * @param event the key event
156 public void keyReleased(KeyEvent event)
158 // Key events should be handled through the InputMap/ActionMap mechanism
159 // since JDK1.3. This class is only there for backwards compatibility.
163 public class FocusHandler implements FocusListener
165 public void focusGained(FocusEvent e)
167 // The only thing that is affected by a focus change seems to be
168 // how the lead cell is painted. So we repaint this cell.
172 public void focusLost(FocusEvent e)
174 // The only thing that is affected by a focus change seems to be
175 // how the lead cell is painted. So we repaint this cell.
180 * Repaints the lead cell in response to a focus change, to refresh
181 * the display of the focus indicator.
183 private void repaintLeadCell()
185 int rowCount = table.getRowCount();
186 int columnCount = table.getColumnCount();
187 int rowLead = table.getSelectionModel().getLeadSelectionIndex();
188 int columnLead = table.getColumnModel().getSelectionModel().
189 getLeadSelectionIndex();
190 if (rowLead >= 0 && rowLead < rowCount && columnLead >= 0
191 && columnLead < columnCount)
193 Rectangle dirtyRect = table.getCellRect(rowLead, columnLead, false);
194 table.repaint(dirtyRect);
199 public class MouseInputHandler implements MouseInputListener
203 private void updateSelection(boolean controlPressed)
206 int lo_row = table.rowAtPoint(begin);
207 int hi_row = table.rowAtPoint(curr);
208 ListSelectionModel rowModel = table.getSelectionModel();
209 if (lo_row != -1 && hi_row != -1)
211 if (controlPressed && rowModel.getSelectionMode()
212 != ListSelectionModel.SINGLE_SELECTION)
213 rowModel.addSelectionInterval(lo_row, hi_row);
215 rowModel.setSelectionInterval(lo_row, hi_row);
218 // Update the columns
219 int lo_col = table.columnAtPoint(begin);
220 int hi_col = table.columnAtPoint(curr);
221 ListSelectionModel colModel = table.getColumnModel().
223 if (lo_col != -1 && hi_col != -1)
225 if (controlPressed && colModel.getSelectionMode() !=
226 ListSelectionModel.SINGLE_SELECTION)
227 colModel.addSelectionInterval(lo_col, hi_col);
229 colModel.setSelectionInterval(lo_col, hi_col);
234 * For the double click, start the cell editor.
236 public void mouseClicked(MouseEvent e)
238 Point p = e.getPoint();
239 int row = table.rowAtPoint(p);
240 int col = table.columnAtPoint(p);
241 if (table.isCellEditable(row, col))
243 // If the cell editor is the default editor, we request the
244 // number of the required clicks from it. Otherwise,
245 // require two clicks (double click).
246 TableCellEditor editor = table.getCellEditor(row, col);
247 if (editor instanceof DefaultCellEditor)
249 DefaultCellEditor ce = (DefaultCellEditor) editor;
250 if (e.getClickCount() < ce.getClickCountToStart())
253 table.editCellAt(row, col);
257 public void mouseDragged(MouseEvent e)
259 if (table.isEnabled())
261 curr = new Point(e.getX(), e.getY());
262 updateSelection(e.isControlDown());
266 public void mouseEntered(MouseEvent e)
268 // Nothing to do here.
271 public void mouseExited(MouseEvent e)
273 // Nothing to do here.
276 public void mouseMoved(MouseEvent e)
278 // Nothing to do here.
281 public void mousePressed(MouseEvent e)
283 if (table.isEnabled())
285 ListSelectionModel rowModel = table.getSelectionModel();
286 ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
287 int rowLead = rowModel.getLeadSelectionIndex();
288 int colLead = colModel.getLeadSelectionIndex();
290 begin = new Point(e.getX(), e.getY());
291 curr = new Point(e.getX(), e.getY());
292 //if control is pressed and the cell is already selected, deselect it
293 if (e.isControlDown() && table.isCellSelected(
294 table.rowAtPoint(begin), table.columnAtPoint(begin)))
296 table.getSelectionModel().
297 removeSelectionInterval(table.rowAtPoint(begin),
298 table.rowAtPoint(begin));
299 table.getColumnModel().getSelectionModel().
300 removeSelectionInterval(table.columnAtPoint(begin),
301 table.columnAtPoint(begin));
304 updateSelection(e.isControlDown());
306 // If we were editing, but the moved to another cell, stop editing
307 if (rowLead != rowModel.getLeadSelectionIndex() ||
308 colLead != colModel.getLeadSelectionIndex())
309 if (table.isEditing())
310 table.editingStopped(new ChangeEvent(e));
312 // Must request focus explicitly.
313 table.requestFocusInWindow();
317 public void mouseReleased(MouseEvent e)
319 if (table.isEnabled())
328 * Listens for changes to the model property of the JTable and adjusts some
331 * @author Roman Kennke (kennke@aicas.com)
333 private class PropertyChangeHandler implements PropertyChangeListener
336 * Receives notification if one of the JTable's properties changes.
338 * @param ev the property change event
340 public void propertyChange(PropertyChangeEvent ev)
342 String propName = ev.getPropertyName();
343 if (propName.equals("model"))
345 ListSelectionModel rowSel = table.getSelectionModel();
346 rowSel.clearSelection();
347 ListSelectionModel colSel = table.getColumnModel().getSelectionModel();
348 colSel.clearSelection();
349 TableModel model = table.getModel();
351 // Adjust lead and anchor selection indices of the row and column
353 if (model.getRowCount() > 0)
355 rowSel.setAnchorSelectionIndex(0);
356 rowSel.setLeadSelectionIndex(0);
360 rowSel.setAnchorSelectionIndex(-1);
361 rowSel.setLeadSelectionIndex(-1);
363 if (model.getColumnCount() > 0)
365 colSel.setAnchorSelectionIndex(0);
366 colSel.setLeadSelectionIndex(0);
370 colSel.setAnchorSelectionIndex(-1);
371 colSel.setLeadSelectionIndex(-1);
377 protected FocusListener createFocusListener()
379 return new FocusHandler();
382 protected MouseInputListener createMouseInputListener()
384 return new MouseInputHandler();
389 * Creates and returns a key listener for the JTable.
391 * @return a key listener for the JTable
393 protected KeyListener createKeyListener()
395 return new KeyHandler();
399 * Return the maximum size of the table. The maximum height is the row
400 * height times the number of rows. The maximum width is the sum of
401 * the maximum widths of each column.
403 * @param comp the component whose maximum size is being queried,
405 * @return a Dimension object representing the maximum size of the table,
406 * or null if the table has no elements.
408 public Dimension getMaximumSize(JComponent comp)
410 int maxTotalColumnWidth = 0;
411 for (int i = 0; i < table.getColumnCount(); i++)
412 maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth();
414 return new Dimension(maxTotalColumnWidth, getHeight());
418 * Return the minimum size of the table. The minimum height is the row
419 * height times the number of rows. The minimum width is the sum of
420 * the minimum widths of each column.
422 * @param comp the component whose minimum size is being queried,
424 * @return a Dimension object representing the minimum size of the table,
425 * or null if the table has no elements.
427 public Dimension getMinimumSize(JComponent comp)
429 int minTotalColumnWidth = 0;
430 for (int i = 0; i < table.getColumnCount(); i++)
431 minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth();
433 return new Dimension(minTotalColumnWidth, getHeight());
437 * Returns the preferred size for the table of that UI.
439 * @param comp ignored, the <code>table</code> field is used instead
441 * @return the preferred size for the table of that UI
443 public Dimension getPreferredSize(JComponent comp)
445 int prefTotalColumnWidth = 0;
446 TableColumnModel tcm = table.getColumnModel();
448 for (int i = 0; i < tcm.getColumnCount(); i++)
450 TableColumn col = tcm.getColumn(i);
451 prefTotalColumnWidth += col.getPreferredWidth();
454 return new Dimension(prefTotalColumnWidth, getHeight());
458 * Returns the table height. This helper method is used by
459 * {@link #getMinimumSize(JComponent)}, {@link #getPreferredSize(JComponent)}
460 * and {@link #getMaximumSize(JComponent)} to determine the table height.
462 * @return the table height
464 private int getHeight()
467 int rowCount = table.getRowCount();
468 if (rowCount > 0 && table.getColumnCount() > 0)
470 Rectangle r = table.getCellRect(rowCount - 1, 0, true);
471 height = r.y + r.height;
476 protected void installDefaults()
478 LookAndFeel.installColorsAndFont(table, "Table.background",
479 "Table.foreground", "Table.font");
480 table.setGridColor(UIManager.getColor("Table.gridColor"));
481 table.setSelectionForeground(UIManager.getColor("Table.selectionForeground"));
482 table.setSelectionBackground(UIManager.getColor("Table.selectionBackground"));
483 table.setOpaque(true);
487 * Installs keyboard actions on the table.
489 protected void installKeyboardActions()
491 // Install the input map.
493 (InputMap) SharedUIDefaults.get("Table.ancestorInputMap");
494 SwingUtilities.replaceUIInputMap(table,
495 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
498 // FIXME: The JDK uses a LazyActionMap for parentActionMap
499 SwingUtilities.replaceUIActionMap(table, getActionMap());
504 * Fetches the action map from the UI defaults, or create a new one
505 * if the action map hasn't been initialized.
507 * @return the action map
509 private ActionMap getActionMap()
511 ActionMap am = (ActionMap) UIManager.get("Table.actionMap");
514 am = createDefaultActions();
515 UIManager.getLookAndFeelDefaults().put("Table.actionMap", am);
520 private ActionMap createDefaultActions()
522 ActionMapUIResource am = new ActionMapUIResource();
523 Action action = new TableAction();
525 am.put("cut", TransferHandler.getCutAction());
526 am.put("copy", TransferHandler.getCopyAction());
527 am.put("paste", TransferHandler.getPasteAction());
529 am.put("cancel", action);
530 am.put("selectAll", action);
531 am.put("clearSelection", action);
532 am.put("startEditing", action);
534 am.put("selectNextRow", action);
535 am.put("selectNextRowCell", action);
536 am.put("selectNextRowExtendSelection", action);
537 am.put("selectNextRowChangeLead", action);
539 am.put("selectPreviousRow", action);
540 am.put("selectPreviousRowCell", action);
541 am.put("selectPreviousRowExtendSelection", action);
542 am.put("selectPreviousRowChangeLead", action);
544 am.put("selectNextColumn", action);
545 am.put("selectNextColumnCell", action);
546 am.put("selectNextColumnExtendSelection", action);
547 am.put("selectNextColumnChangeLead", action);
549 am.put("selectPreviousColumn", action);
550 am.put("selectPreviousColumnCell", action);
551 am.put("selectPreviousColumnExtendSelection", action);
552 am.put("selectPreviousColumnChangeLead", action);
554 am.put("scrollLeftChangeSelection", action);
555 am.put("scrollLeftExtendSelection", action);
556 am.put("scrollRightChangeSelection", action);
557 am.put("scrollRightExtendSelection", action);
559 am.put("scrollUpChangeSelection", action);
560 am.put("scrollUpExtendSelection", action);
561 am.put("scrollDownChangeSelection", action);
562 am.put("scrolldownExtendSelection", action);
564 am.put("selectFirstColumn", action);
565 am.put("selectFirstColumnExtendSelection", action);
566 am.put("selectLastColumn", action);
567 am.put("selectLastColumnExtendSelection", action);
569 am.put("selectFirstRow", action);
570 am.put("selectFirstRowExtendSelection", action);
571 am.put("selectLastRow", action);
572 am.put("selectLastRowExtendSelection", action);
574 am.put("addToSelection", action);
575 am.put("toggleAndAnchor", action);
576 am.put("extendTo", action);
577 am.put("moveSelectionTo", action);
583 * This class implements the actions that we want to happen
584 * when specific keys are pressed for the JTable. The actionPerformed
585 * method is called when a key that has been registered for the JTable
588 private static class TableAction
589 extends AbstractAction
592 * What to do when this action is called.
594 * @param e the ActionEvent that caused this action.
596 public void actionPerformed(ActionEvent e)
598 JTable table = (JTable) e.getSource();
600 DefaultListSelectionModel rowModel
601 = (DefaultListSelectionModel) table.getSelectionModel();
602 DefaultListSelectionModel colModel
603 = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel();
605 int rowLead = rowModel.getLeadSelectionIndex();
606 int rowMax = table.getModel().getRowCount() - 1;
608 int colLead = colModel.getLeadSelectionIndex();
609 int colMax = table.getModel().getColumnCount() - 1;
611 // The command with which the action has been called is stored
612 // in this undocumented action value. This allows us to have only
613 // one Action instance to serve all keyboard input for JTable.
614 String command = (String) getValue("__command__");
615 if (command.equals("selectPreviousRowExtendSelection"))
617 rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
619 else if (command.equals("selectLastColumn"))
621 colModel.setSelectionInterval(colMax, colMax);
623 else if (command.equals("startEditing"))
625 if (table.isCellEditable(rowLead, colLead))
626 table.editCellAt(rowLead, colLead);
628 else if (command.equals("selectFirstRowExtendSelection"))
630 rowModel.setLeadSelectionIndex(0);
632 else if (command.equals("selectFirstColumn"))
634 colModel.setSelectionInterval(0, 0);
636 else if (command.equals("selectFirstColumnExtendSelection"))
638 colModel.setLeadSelectionIndex(0);
640 else if (command.equals("selectLastRow"))
642 rowModel.setSelectionInterval(rowMax, rowMax);
644 else if (command.equals("selectNextRowExtendSelection"))
646 rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
648 else if (command.equals("selectFirstRow"))
650 rowModel.setSelectionInterval(0, 0);
652 else if (command.equals("selectNextColumnExtendSelection"))
654 colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
656 else if (command.equals("selectLastColumnExtendSelection"))
658 colModel.setLeadSelectionIndex(colMax);
660 else if (command.equals("selectPreviousColumnExtendSelection"))
662 colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
664 else if (command.equals("selectNextRow"))
666 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
667 Math.min(rowLead + 1, rowMax));
669 else if (command.equals("scrollUpExtendSelection"))
672 if (rowLead == getFirstVisibleRowIndex(table))
673 target = Math.max(0, rowLead - (getLastVisibleRowIndex(table)
674 - getFirstVisibleRowIndex(table) + 1));
676 target = getFirstVisibleRowIndex(table);
678 rowModel.setLeadSelectionIndex(target);
679 colModel.setLeadSelectionIndex(colLead);
681 else if (command.equals("selectPreviousRow"))
683 rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
684 Math.max(rowLead - 1, 0));
686 else if (command.equals("scrollRightChangeSelection"))
689 if (colLead == getLastVisibleColumnIndex(table))
690 target = Math.min(colMax, colLead
691 + (getLastVisibleColumnIndex(table)
692 - getFirstVisibleColumnIndex(table) + 1));
694 target = getLastVisibleColumnIndex(table);
696 colModel.setSelectionInterval(target, target);
697 rowModel.setSelectionInterval(rowLead, rowLead);
699 else if (command.equals("selectPreviousColumn"))
701 colModel.setSelectionInterval(Math.max(colLead - 1, 0),
702 Math.max(colLead - 1, 0));
704 else if (command.equals("scrollLeftChangeSelection"))
707 if (colLead == getFirstVisibleColumnIndex(table))
708 target = Math.max(0, colLead - (getLastVisibleColumnIndex(table)
709 - getFirstVisibleColumnIndex(table) + 1));
711 target = getFirstVisibleColumnIndex(table);
713 colModel.setSelectionInterval(target, target);
714 rowModel.setSelectionInterval(rowLead, rowLead);
716 else if (command.equals("clearSelection"))
718 table.clearSelection();
720 else if (command.equals("cancel"))
722 // FIXME: implement other parts of "cancel" like undo-ing last
723 // selection. Right now it just calls editingCancelled if
724 // we're currently editing.
725 if (table.isEditing())
726 table.editingCanceled(new ChangeEvent("cancel"));
728 else if (command.equals("selectNextRowCell")
729 || command.equals("selectPreviousRowCell")
730 || command.equals("selectNextColumnCell")
731 || command.equals("selectPreviousColumnCell"))
733 // If nothing is selected, select the first cell in the table
734 if (table.getSelectedRowCount() == 0 &&
735 table.getSelectedColumnCount() == 0)
737 rowModel.setSelectionInterval(0, 0);
738 colModel.setSelectionInterval(0, 0);
742 // If the lead selection index isn't selected (ie a remove operation
743 // happened, then set the lead to the first selected cell in the
745 if (!table.isCellSelected(rowLead, colLead))
747 rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(),
748 rowModel.getMinSelectionIndex());
749 colModel.addSelectionInterval(colModel.getMinSelectionIndex(),
750 colModel.getMinSelectionIndex());
754 // multRowsSelected and multColsSelected tell us if multiple rows or
755 // columns are selected, respectively
756 boolean multRowsSelected, multColsSelected;
757 multRowsSelected = table.getSelectedRowCount() > 1 &&
758 table.getRowSelectionAllowed();
760 multColsSelected = table.getSelectedColumnCount() > 1 &&
761 table.getColumnSelectionAllowed();
763 // If there is just one selection, select the next cell, and wrap
764 // when you get to the edges of the table.
765 if (!multColsSelected && !multRowsSelected)
767 if (command.indexOf("Column") != -1)
768 advanceSingleSelection(colModel, colMax, rowModel, rowMax,
769 command.equals("selectPreviousColumnCell"));
771 advanceSingleSelection(rowModel, rowMax, colModel, colMax,
772 command.equals("selectPreviousRowCell"));
777 // rowMinSelected and rowMaxSelected are the minimum and maximum
778 // values respectively of selected cells in the row selection model
779 // Similarly for colMinSelected and colMaxSelected.
780 int rowMaxSelected = table.getRowSelectionAllowed() ?
781 rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1;
782 int rowMinSelected = table.getRowSelectionAllowed() ?
783 rowModel.getMinSelectionIndex() : 0;
784 int colMaxSelected = table.getColumnSelectionAllowed() ?
785 colModel.getMaxSelectionIndex() :
786 table.getModel().getColumnCount() - 1;
787 int colMinSelected = table.getColumnSelectionAllowed() ?
788 colModel.getMinSelectionIndex() : 0;
790 // If there are multiple rows and columns selected, select the next
791 // cell and wrap at the edges of the selection.
792 if (command.indexOf("Column") != -1)
793 advanceMultipleSelection(table, colModel, colMinSelected,
794 colMaxSelected, rowModel, rowMinSelected,
796 command.equals("selectPreviousColumnCell"),
800 advanceMultipleSelection(table, rowModel, rowMinSelected,
801 rowMaxSelected, colModel, colMinSelected,
803 command.equals("selectPreviousRowCell"),
806 else if (command.equals("selectNextColumn"))
808 colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
809 Math.min(colLead + 1, colMax));
811 else if (command.equals("scrollLeftExtendSelection"))
814 if (colLead == getFirstVisibleColumnIndex(table))
815 target = Math.max(0, colLead - (getLastVisibleColumnIndex(table)
816 - getFirstVisibleColumnIndex(table) + 1));
818 target = getFirstVisibleColumnIndex(table);
820 colModel.setLeadSelectionIndex(target);
821 rowModel.setLeadSelectionIndex(rowLead);
823 else if (command.equals("scrollDownChangeSelection"))
826 if (rowLead == getLastVisibleRowIndex(table))
827 target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table)
828 - getFirstVisibleRowIndex(table) + 1));
830 target = getLastVisibleRowIndex(table);
832 rowModel.setSelectionInterval(target, target);
833 colModel.setSelectionInterval(colLead, colLead);
835 else if (command.equals("scrollRightExtendSelection"))
838 if (colLead == getLastVisibleColumnIndex(table))
839 target = Math.min(colMax, colLead + (getLastVisibleColumnIndex(table)
840 - getFirstVisibleColumnIndex(table) + 1));
842 target = getLastVisibleColumnIndex(table);
844 colModel.setLeadSelectionIndex(target);
845 rowModel.setLeadSelectionIndex(rowLead);
847 else if (command.equals("selectAll"))
851 else if (command.equals("selectLastRowExtendSelection"))
853 rowModel.setLeadSelectionIndex(rowMax);
854 colModel.setLeadSelectionIndex(colLead);
856 else if (command.equals("scrollDownExtendSelection"))
859 if (rowLead == getLastVisibleRowIndex(table))
860 target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table)
861 - getFirstVisibleRowIndex(table) + 1));
863 target = getLastVisibleRowIndex(table);
865 rowModel.setLeadSelectionIndex(target);
866 colModel.setLeadSelectionIndex(colLead);
868 else if (command.equals("scrollUpChangeSelection"))
871 if (rowLead == getFirstVisibleRowIndex(table))
872 target = Math.max(0, rowLead - (getLastVisibleRowIndex(table)
873 - getFirstVisibleRowIndex(table) + 1));
875 target = getFirstVisibleRowIndex(table);
877 rowModel.setSelectionInterval(target, target);
878 colModel.setSelectionInterval(colLead, colLead);
880 else if (command.equals("selectNextRowChangeLead"))
882 if (rowModel.getSelectionMode()
883 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
885 // just "selectNextRow"
886 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
887 Math.min(rowLead + 1, rowMax));
888 colModel.setSelectionInterval(colLead, colLead);
891 rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
893 else if (command.equals("selectPreviousRowChangeLead"))
895 if (rowModel.getSelectionMode()
896 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
898 // just selectPreviousRow
899 rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
900 Math.min(rowLead - 1, 0));
901 colModel.setSelectionInterval(colLead, colLead);
904 rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0));
906 else if (command.equals("selectNextColumnChangeLead"))
908 if (colModel.getSelectionMode()
909 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
911 // just selectNextColumn
912 rowModel.setSelectionInterval(rowLead, rowLead);
913 colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
914 Math.min(colLead + 1, colMax));
917 colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax));
919 else if (command.equals("selectPreviousColumnChangeLead"))
921 if (colModel.getSelectionMode()
922 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
924 // just selectPreviousColumn
925 rowModel.setSelectionInterval(rowLead, rowLead);
926 colModel.setSelectionInterval(Math.max(colLead - 1, 0),
927 Math.max(colLead - 1, 0));
931 colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0));
933 else if (command.equals("addToSelection"))
935 if (!table.isEditing())
937 int oldRowAnchor = rowModel.getAnchorSelectionIndex();
938 int oldColAnchor = colModel.getAnchorSelectionIndex();
939 rowModel.addSelectionInterval(rowLead, rowLead);
940 colModel.addSelectionInterval(colLead, colLead);
941 rowModel.setAnchorSelectionIndex(oldRowAnchor);
942 colModel.setAnchorSelectionIndex(oldColAnchor);
945 else if (command.equals("extendTo"))
947 rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(),
949 colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(),
952 else if (command.equals("toggleAndAnchor"))
954 if (rowModel.isSelectedIndex(rowLead))
955 rowModel.removeSelectionInterval(rowLead, rowLead);
957 rowModel.addSelectionInterval(rowLead, rowLead);
959 if (colModel.isSelectedIndex(colLead))
960 colModel.removeSelectionInterval(colLead, colLead);
962 colModel.addSelectionInterval(colLead, colLead);
964 rowModel.setAnchorSelectionIndex(rowLead);
965 colModel.setAnchorSelectionIndex(colLead);
967 else if (command.equals("stopEditing"))
969 table.editingStopped(new ChangeEvent(command));
973 // If we're here that means we bound this TableAction class
974 // to a keyboard input but we either want to ignore that input
975 // or we just haven't implemented its action yet.
977 // Uncomment the following line to print the names of unused bindings
978 // when their keys are pressed
980 // System.out.println ("not implemented: "+e.getActionCommand());
983 // Any commands whose keyStrokes should be used by the Editor should not
984 // cause editing to be stopped: ie, the SPACE sends "addToSelection" but
985 // if the table is in editing mode, the space should not cause us to stop
986 // editing because it should be used by the Editor.
987 if (table.isEditing() && command != "startEditing"
988 && command != "addToSelection")
989 table.editingStopped(new ChangeEvent("update"));
991 table.scrollRectToVisible(table.getCellRect(
992 rowModel.getLeadSelectionIndex(), colModel.getLeadSelectionIndex(),
997 * Returns the column index of the first visible column.
998 * @return the column index of the first visible column.
1000 int getFirstVisibleColumnIndex(JTable table)
1002 ComponentOrientation or = table.getComponentOrientation();
1003 Rectangle r = table.getVisibleRect();
1004 if (!or.isLeftToRight())
1005 r.translate((int) r.getWidth() - 1, 0);
1006 return table.columnAtPoint(r.getLocation());
1010 * Returns the column index of the last visible column.
1013 int getLastVisibleColumnIndex(JTable table)
1015 ComponentOrientation or = table.getComponentOrientation();
1016 Rectangle r = table.getVisibleRect();
1017 if (or.isLeftToRight())
1018 r.translate((int) r.getWidth() - 1, 0);
1019 return table.columnAtPoint(r.getLocation());
1023 * Returns the row index of the first visible row.
1026 int getFirstVisibleRowIndex(JTable table)
1028 ComponentOrientation or = table.getComponentOrientation();
1029 Rectangle r = table.getVisibleRect();
1030 if (!or.isLeftToRight())
1031 r.translate((int) r.getWidth() - 1, 0);
1032 return table.rowAtPoint(r.getLocation());
1036 * Returns the row index of the last visible row.
1039 int getLastVisibleRowIndex(JTable table)
1041 ComponentOrientation or = table.getComponentOrientation();
1042 Rectangle r = table.getVisibleRect();
1043 r.translate(0, (int) r.getHeight() - 1);
1044 if (or.isLeftToRight())
1045 r.translate((int) r.getWidth() - 1, 0);
1046 // The next if makes sure that we don't return -1 simply because
1047 // there is white space at the bottom of the table (ie, the display
1048 // area is larger than the table)
1049 if (table.rowAtPoint(r.getLocation()) == -1)
1051 if (getFirstVisibleRowIndex(table) == -1)
1054 return table.getModel().getRowCount() - 1;
1056 return table.rowAtPoint(r.getLocation());
1060 * A helper method for the key bindings. Used because the actions
1061 * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1063 * Selects the next (previous if SHIFT pressed) column for TAB, or row for
1064 * ENTER from within the currently selected cells.
1066 * @param firstModel the ListSelectionModel for columns (TAB) or
1068 * @param firstMin the first selected index in firstModel
1069 * @param firstMax the last selected index in firstModel
1070 * @param secondModel the ListSelectionModel for rows (TAB) or
1072 * @param secondMin the first selected index in secondModel
1073 * @param secondMax the last selected index in secondModel
1074 * @param reverse true if shift was held for the event
1075 * @param eventIsTab true if TAB was pressed, false if ENTER pressed
1077 void advanceMultipleSelection(JTable table, ListSelectionModel firstModel,
1079 int firstMax, ListSelectionModel secondModel,
1080 int secondMin, int secondMax, boolean reverse,
1083 // If eventIsTab, all the "firsts" correspond to columns, otherwise, to
1084 // rows "seconds" correspond to the opposite
1085 int firstLead = firstModel.getLeadSelectionIndex();
1086 int secondLead = secondModel.getLeadSelectionIndex();
1087 int numFirsts = eventIsTab ?
1088 table.getModel().getColumnCount() : table.getModel().getRowCount();
1089 int numSeconds = eventIsTab ?
1090 table.getModel().getRowCount() : table.getModel().getColumnCount();
1092 // check if we have to wrap the "firsts" around, going to the other side
1093 if ((firstLead == firstMax && !reverse) ||
1094 (reverse && firstLead == firstMin))
1096 firstModel.addSelectionInterval(reverse ? firstMax : firstMin,
1097 reverse ? firstMax : firstMin);
1099 // check if we have to wrap the "seconds"
1100 if ((secondLead == secondMax && !reverse) ||
1101 (reverse && secondLead == secondMin))
1102 secondModel.addSelectionInterval(reverse ? secondMax : secondMin,
1103 reverse ? secondMax : secondMin);
1105 // if we're not wrapping the seconds, we have to find out where we
1106 // are within the secondModel and advance to the next cell (or
1107 // go back to the previous cell if reverse == true)
1110 int[] secondsSelected;
1111 if (eventIsTab && table.getRowSelectionAllowed() ||
1112 !eventIsTab && table.getColumnSelectionAllowed())
1113 secondsSelected = eventIsTab ?
1114 table.getSelectedRows() : table.getSelectedColumns();
1117 // if row selection is not allowed, then the entire column gets
1118 // selected when you click on it, so consider ALL rows selected
1119 secondsSelected = new int[numSeconds];
1120 for (int i = 0; i < numSeconds; i++)
1121 secondsSelected[i] = i;
1124 // and now find the "next" index within the model
1125 int secondIndex = reverse ? secondsSelected.length - 1 : 0;
1127 while (secondsSelected[secondIndex] <= secondLead)
1130 while (secondsSelected[secondIndex] >= secondLead)
1133 // and select it - updating the lead selection index
1134 secondModel.addSelectionInterval(secondsSelected[secondIndex],
1135 secondsSelected[secondIndex]);
1138 // We didn't have to wrap the firsts, so just find the "next" first
1139 // and select it, we don't have to change "seconds"
1142 int[] firstsSelected;
1143 if (eventIsTab && table.getColumnSelectionAllowed() ||
1144 !eventIsTab && table.getRowSelectionAllowed())
1145 firstsSelected = eventIsTab ?
1146 table.getSelectedColumns() : table.getSelectedRows();
1149 // if selection not allowed, consider ALL firsts to be selected
1150 firstsSelected = new int[numFirsts];
1151 for (int i = 0; i < numFirsts; i++)
1152 firstsSelected[i] = i;
1154 int firstIndex = reverse ? firstsSelected.length - 1 : 0;
1156 while (firstsSelected[firstIndex] <= firstLead)
1159 while (firstsSelected[firstIndex] >= firstLead)
1161 firstModel.addSelectionInterval(firstsSelected[firstIndex],
1162 firstsSelected[firstIndex]);
1163 secondModel.addSelectionInterval(secondLead, secondLead);
1168 * A helper method for the key bindings. Used because the actions
1169 * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1171 * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER)
1172 * in the table, changing the current selection. All cells in the table
1173 * are eligible, not just the ones that are currently selected.
1174 * @param firstModel the ListSelectionModel for columns (TAB) or rows
1176 * @param firstMax the last index in firstModel
1177 * @param secondModel the ListSelectionModel for rows (TAB) or columns
1179 * @param secondMax the last index in secondModel
1180 * @param reverse true if SHIFT was pressed for the event
1183 void advanceSingleSelection(ListSelectionModel firstModel, int firstMax,
1184 ListSelectionModel secondModel, int secondMax,
1187 // for TABs, "first" corresponds to columns and "seconds" to rows.
1188 // the opposite is true for ENTERs
1189 int firstLead = firstModel.getLeadSelectionIndex();
1190 int secondLead = secondModel.getLeadSelectionIndex();
1192 // if we are going backwards subtract 2 because we later add 1
1193 // for a net change of -1
1194 if (reverse && (firstLead == 0))
1196 // check if we have to wrap around
1197 if (secondLead == 0)
1198 secondLead += secondMax + 1;
1202 // do we have to wrap the "seconds"?
1203 if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax))
1204 secondModel.setSelectionInterval((secondLead + 1) % (secondMax + 1),
1205 (secondLead + 1) % (secondMax + 1));
1206 // if not, just reselect the current lead
1208 secondModel.setSelectionInterval(secondLead, secondLead);
1210 // if we are going backwards, subtract 2 because we add 1 later
1211 // for net change of -1
1214 // check for wraparound
1216 firstLead += firstMax + 1;
1219 // select the next "first"
1220 firstModel.setSelectionInterval((firstLead + 1) % (firstMax + 1),
1221 (firstLead + 1) % (firstMax + 1));
1225 protected void installListeners()
1227 if (focusListener == null)
1228 focusListener = createFocusListener();
1229 table.addFocusListener(focusListener);
1230 if (keyListener == null)
1231 keyListener = createKeyListener();
1232 table.addKeyListener(keyListener);
1233 if (mouseInputListener == null)
1234 mouseInputListener = createMouseInputListener();
1235 table.addMouseListener(mouseInputListener);
1236 table.addMouseMotionListener(mouseInputListener);
1237 if (propertyChangeListener == null)
1238 propertyChangeListener = new PropertyChangeHandler();
1239 table.addPropertyChangeListener(propertyChangeListener);
1243 * Uninstalls UI defaults that have been installed by
1244 * {@link #installDefaults()}.
1246 protected void uninstallDefaults()
1248 // Nothing to do here for now.
1252 * Uninstalls the keyboard actions that have been installed by
1253 * {@link #installKeyboardActions()}.
1255 protected void uninstallKeyboardActions()
1257 SwingUtilities.replaceUIInputMap(table, JComponent.
1258 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1259 SwingUtilities.replaceUIActionMap(table, null);
1262 protected void uninstallListeners()
1264 table.removeFocusListener(focusListener);
1265 table.removeKeyListener(keyListener);
1266 table.removeMouseListener(mouseInputListener);
1267 table.removeMouseMotionListener(mouseInputListener);
1268 table.removePropertyChangeListener(propertyChangeListener);
1269 propertyChangeListener = null;
1272 public void installUI(JComponent comp)
1274 table = (JTable) comp;
1275 rendererPane = new CellRendererPane();
1276 table.add(rendererPane);
1279 installKeyboardActions();
1283 public void uninstallUI(JComponent c)
1285 uninstallListeners();
1286 uninstallKeyboardActions();
1287 uninstallDefaults();
1289 table.remove(rendererPane);
1290 rendererPane = null;
1295 * Paints a single cell in the table.
1297 * @param g The graphics context to paint in
1298 * @param row The row number to paint
1299 * @param col The column number to paint
1300 * @param bounds The bounds of the cell to paint, assuming a coordinate
1301 * system beginning at <code>(0,0)</code> in the upper left corner of the
1303 * @param rend A cell renderer to paint with
1305 void paintCell(Graphics g, int row, int col, Rectangle bounds,
1306 TableCellRenderer rend)
1308 Component comp = table.prepareRenderer(rend, row, col);
1309 rendererPane.paintComponent(g, comp, table, bounds);
1313 * Paint the associated table.
1315 public void paint(Graphics gfx, JComponent ignored)
1317 int ncols = table.getColumnCount();
1318 int nrows = table.getRowCount();
1319 if (nrows == 0 || ncols == 0)
1322 Rectangle clip = gfx.getClipBounds();
1324 // Determine the range of cells that are within the clip bounds.
1325 Point p1 = new Point(clip.x, clip.y);
1326 int c0 = table.columnAtPoint(p1);
1329 int r0 = table.rowAtPoint(p1);
1332 Point p2 = new Point(clip.x + clip.width, clip.y + clip.height);
1333 int cn = table.columnAtPoint(p2);
1335 cn = table.getColumnCount() - 1;
1336 int rn = table.rowAtPoint(p2);
1338 rn = table.getRowCount() - 1;
1340 int columnMargin = table.getColumnModel().getColumnMargin();
1341 int rowMargin = table.getRowMargin();
1343 TableColumnModel cmodel = table.getColumnModel();
1344 int[] widths = new int[cn + 1];
1345 for (int i = c0; i <= cn; i++)
1347 widths[i] = cmodel.getColumn(i).getWidth() - columnMargin;
1350 Rectangle bounds = table.getCellRect(r0, c0, false);
1351 // The left boundary of the area being repainted.
1352 int left = bounds.x;
1354 // The top boundary of the area being repainted.
1357 // The bottom boundary of the area being repainted.
1360 // paint the cell contents
1361 Color grid = table.getGridColor();
1362 for (int r = r0; r <= rn; ++r)
1364 for (int c = c0; c <= cn; ++c)
1366 bounds.width = widths[c];
1367 paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c));
1368 bounds.x += widths[c] + columnMargin;
1371 bounds.y += table.getRowHeight(r);
1372 // Update row height for tables with custom heights.
1373 bounds.height = table.getRowHeight(r + 1) - rowMargin;
1376 bottom = bounds.y - rowMargin;
1378 // paint vertical grid lines
1379 if (grid != null && table.getShowVerticalLines())
1381 Color save = gfx.getColor();
1383 int x = left - columnMargin;
1384 for (int c = c0; c <= cn; ++c)
1386 // The vertical grid is draw right from the cells, so we
1387 // add before drawing.
1388 x += widths[c] + columnMargin;
1389 gfx.drawLine(x, top, x, bottom);
1394 // paint horizontal grid lines
1395 if (grid != null && table.getShowHorizontalLines())
1397 Color save = gfx.getColor();
1399 int y = top - rowMargin;
1400 for (int r = r0; r <= rn; ++r)
1402 // The horizontal grid is draw below the cells, so we
1403 // add before drawing.
1404 y += table.getRowHeight(r);
1405 gfx.drawLine(left, y, p2.x, y);