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 gnu.classpath.NotImplementedException;
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;
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;
85 public class BasicTableUI extends TableUI
87 public static ComponentUI createUI(JComponent comp)
89 return new BasicTableUI();
92 protected FocusListener focusListener;
93 protected KeyListener keyListener;
94 protected MouseInputListener mouseInputListener;
95 protected CellRendererPane rendererPane;
96 protected JTable table;
98 /** The normal cell border. */
101 /** The action bound to KeyStrokes. */
105 * Listens for changes to the tables properties.
107 private PropertyChangeListener propertyChangeListener;
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.
114 * @author Roman Kennke (kennke@aicas.com)
116 public class KeyHandler implements KeyListener
120 * Receives notification that a key has been pressed and released.
121 * Activates the editing session for the focused cell by pressing the
124 * @param event the key event
126 public void keyTyped(KeyEvent event)
128 // Key events should be handled through the InputMap/ActionMap mechanism
129 // since JDK1.3. This class is only there for backwards compatibility.
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())
135 int r = table.getSelectedRow();
136 int c = table.getSelectedColumn();
137 if (table.isCellEditable(r, c))
138 table.editCellAt(r, c);
143 * Receives notification that a key has been pressed.
145 * @param event the key event
147 public void keyPressed(KeyEvent event)
149 // Key events should be handled through the InputMap/ActionMap mechanism
150 // since JDK1.3. This class is only there for backwards compatibility.
154 * Receives notification that a key has been released.
156 * @param event the key event
158 public void keyReleased(KeyEvent event)
160 // Key events should be handled through the InputMap/ActionMap mechanism
161 // since JDK1.3. This class is only there for backwards compatibility.
165 public class FocusHandler implements FocusListener
167 public void focusGained(FocusEvent e)
169 // TODO: Implement this properly.
172 public void focusLost(FocusEvent e)
174 // TODO: Implement this properly.
178 public class MouseInputHandler implements MouseInputListener
182 private void updateSelection(boolean controlPressed)
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)
190 if (controlPressed && rowModel.getSelectionMode()
191 != ListSelectionModel.SINGLE_SELECTION)
192 rowModel.addSelectionInterval(lo_row, hi_row);
194 rowModel.setSelectionInterval(lo_row, hi_row);
197 // Update the columns
198 int lo_col = table.columnAtPoint(begin);
199 int hi_col = table.columnAtPoint(curr);
200 ListSelectionModel colModel = table.getColumnModel().
202 if (lo_col != -1 && hi_col != -1)
204 if (controlPressed && colModel.getSelectionMode() !=
205 ListSelectionModel.SINGLE_SELECTION)
206 colModel.addSelectionInterval(lo_col, hi_col);
208 colModel.setSelectionInterval(lo_col, hi_col);
213 * For the double click, start the cell editor.
215 public void mouseClicked(MouseEvent e)
217 Point p = e.getPoint();
218 int row = table.rowAtPoint(p);
219 int col = table.columnAtPoint(p);
220 if (table.isCellEditable(row, col))
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)
228 DefaultCellEditor ce = (DefaultCellEditor) editor;
229 if (e.getClickCount() < ce.getClickCountToStart())
232 table.editCellAt(row, col);
236 public void mouseDragged(MouseEvent e)
238 if (table.isEnabled())
240 curr = new Point(e.getX(), e.getY());
241 updateSelection(e.isControlDown());
245 public void mouseEntered(MouseEvent e)
247 // TODO: What should be done here, if anything?
250 public void mouseExited(MouseEvent e)
252 // TODO: What should be done here, if anything?
255 public void mouseMoved(MouseEvent e)
257 // TODO: What should be done here, if anything?
260 public void mousePressed(MouseEvent e)
262 if (table.isEnabled())
264 ListSelectionModel rowModel = table.getSelectionModel();
265 ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
266 int rowLead = rowModel.getLeadSelectionIndex();
267 int colLead = colModel.getLeadSelectionIndex();
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)))
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));
283 updateSelection(e.isControlDown());
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));
293 public void mouseReleased(MouseEvent e)
295 if (table.isEnabled())
304 * Listens for changes to the model property of the JTable and adjusts some
307 * @author Roman Kennke (kennke@aicas.com)
309 private class PropertyChangeHandler implements PropertyChangeListener
312 * Receives notification if one of the JTable's properties changes.
314 * @param ev the property change event
316 public void propertyChange(PropertyChangeEvent ev)
318 String propName = ev.getPropertyName();
319 if (propName.equals("model"))
321 ListSelectionModel rowSel = table.getSelectionModel();
322 rowSel.clearSelection();
323 ListSelectionModel colSel = table.getColumnModel().getSelectionModel();
324 colSel.clearSelection();
325 TableModel model = table.getModel();
327 // Adjust lead and anchor selection indices of the row and column
329 if (model.getRowCount() > 0)
331 rowSel.setAnchorSelectionIndex(0);
332 rowSel.setLeadSelectionIndex(0);
336 rowSel.setAnchorSelectionIndex(-1);
337 rowSel.setLeadSelectionIndex(-1);
339 if (model.getColumnCount() > 0)
341 colSel.setAnchorSelectionIndex(0);
342 colSel.setLeadSelectionIndex(0);
346 colSel.setAnchorSelectionIndex(-1);
347 colSel.setLeadSelectionIndex(-1);
353 protected FocusListener createFocusListener()
355 return new FocusHandler();
358 protected MouseInputListener createMouseInputListener()
360 return new MouseInputHandler();
365 * Creates and returns a key listener for the JTable.
367 * @return a key listener for the JTable
369 protected KeyListener createKeyListener()
371 return new KeyHandler();
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.
379 * @param comp the component whose maximum size is being queried,
381 * @return a Dimension object representing the maximum size of the table,
382 * or null if the table has no elements.
384 public Dimension getMaximumSize(JComponent comp)
386 int maxTotalColumnWidth = 0;
387 for (int i = 0; i < table.getColumnCount(); i++)
388 maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth();
390 return new Dimension(maxTotalColumnWidth, getHeight());
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.
398 * @param comp the component whose minimum size is being queried,
400 * @return a Dimension object representing the minimum size of the table,
401 * or null if the table has no elements.
403 public Dimension getMinimumSize(JComponent comp)
405 int minTotalColumnWidth = 0;
406 for (int i = 0; i < table.getColumnCount(); i++)
407 minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth();
409 return new Dimension(minTotalColumnWidth, getHeight());
413 * Returns the preferred size for the table of that UI.
415 * @param comp ignored, the <code>table</code> field is used instead
417 * @return the preferred size for the table of that UI
419 public Dimension getPreferredSize(JComponent comp)
421 int prefTotalColumnWidth = 0;
422 for (int i = 0; i < table.getColumnCount(); i++)
424 TableColumn col = table.getColumnModel().getColumn(i);
425 prefTotalColumnWidth += col.getPreferredWidth();
427 return new Dimension(prefTotalColumnWidth, getHeight());
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.
435 * @return the table height
437 private int getHeight()
440 int rowCount = table.getRowCount();
441 if (rowCount > 0 && table.getColumnCount() > 0)
443 Rectangle r = table.getCellRect(rowCount - 1, 0, true);
444 height = r.y + r.height;
449 protected void installDefaults()
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);
459 protected void installKeyboardActions()
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++)
470 KeyStroke stroke = (KeyStroke)keys[i];
471 String actionString = (String) ancestorMap.get(stroke);
473 parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(),
474 stroke.getModifiers()),
477 parentActionMap.put (actionString,
478 new ActionListenerProxy (action, actionString));
481 // Set the UI InputMap-ActionMap pair to be the parents of the
482 // JTable's InputMap-ActionMap pair
483 parentInputMap.setParent
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);
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
499 private static class ActionListenerProxy
500 extends AbstractAction
502 ActionListener target;
503 String bindingCommandName;
505 public ActionListenerProxy(ActionListener li,
509 bindingCommandName = cmd;
512 public void actionPerformed(ActionEvent e)
514 ActionEvent derivedEvent = new ActionEvent(e.getSource(),
518 target.actionPerformed(derivedEvent);
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
528 class TableAction extends AbstractAction
531 * What to do when this action is called.
533 * @param e the ActionEvent that caused this action.
535 public void actionPerformed (ActionEvent e)
537 DefaultListSelectionModel rowModel = (DefaultListSelectionModel) table.getSelectionModel();
538 DefaultListSelectionModel colModel = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel();
540 int rowLead = rowModel.getLeadSelectionIndex();
541 int rowMax = table.getModel().getRowCount() - 1;
543 int colLead = colModel.getLeadSelectionIndex();
544 int colMax = table.getModel().getColumnCount() - 1;
546 String command = e.getActionCommand();
548 if (command.equals("selectPreviousRowExtendSelection"))
550 rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
552 else if (command.equals("selectLastColumn"))
554 colModel.setSelectionInterval(colMax, colMax);
556 else if (command.equals("startEditing"))
558 if (table.isCellEditable(rowLead, colLead))
559 table.editCellAt(rowLead,colLead);
561 else if (command.equals("selectFirstRowExtendSelection"))
563 rowModel.setLeadSelectionIndex(0);
565 else if (command.equals("selectFirstColumn"))
567 colModel.setSelectionInterval(0, 0);
569 else if (command.equals("selectFirstColumnExtendSelection"))
571 colModel.setLeadSelectionIndex(0);
573 else if (command.equals("selectLastRow"))
575 rowModel.setSelectionInterval(rowMax,rowMax);
577 else if (command.equals("selectNextRowExtendSelection"))
579 rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
581 else if (command.equals("selectFirstRow"))
583 rowModel.setSelectionInterval(0,0);
585 else if (command.equals("selectNextColumnExtendSelection"))
587 colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
589 else if (command.equals("selectLastColumnExtendSelection"))
591 colModel.setLeadSelectionIndex(colMax);
593 else if (command.equals("selectPreviousColumnExtendSelection"))
595 colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
597 else if (command.equals("selectNextRow"))
599 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
600 Math.min(rowLead + 1, rowMax));
602 else if (command.equals("scrollUpExtendSelection"))
605 if (rowLead == getFirstVisibleRowIndex())
607 (0, rowLead - (getLastVisibleRowIndex() -
608 getFirstVisibleRowIndex() + 1));
610 target = getFirstVisibleRowIndex();
612 rowModel.setLeadSelectionIndex(target);
613 colModel.setLeadSelectionIndex(colLead);
615 else if (command.equals("selectPreviousRow"))
617 rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
618 Math.max(rowLead - 1, 0));
620 else if (command.equals("scrollRightChangeSelection"))
623 if (colLead == getLastVisibleColumnIndex())
625 (colMax, colLead + (getLastVisibleColumnIndex() -
626 getFirstVisibleColumnIndex() + 1));
628 target = getLastVisibleColumnIndex();
630 colModel.setSelectionInterval(target, target);
631 rowModel.setSelectionInterval(rowLead, rowLead);
633 else if (command.equals("selectPreviousColumn"))
635 colModel.setSelectionInterval(Math.max(colLead - 1, 0),
636 Math.max(colLead - 1, 0));
638 else if (command.equals("scrollLeftChangeSelection"))
641 if (colLead == getFirstVisibleColumnIndex())
643 (0, colLead - (getLastVisibleColumnIndex() -
644 getFirstVisibleColumnIndex() + 1));
646 target = getFirstVisibleColumnIndex();
648 colModel.setSelectionInterval(target, target);
649 rowModel.setSelectionInterval(rowLead, rowLead);
651 else if (command.equals("clearSelection"))
653 table.clearSelection();
655 else if (command.equals("cancel"))
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"));
663 else if (command.equals("selectNextRowCell")
664 || command.equals("selectPreviousRowCell")
665 || command.equals("selectNextColumnCell")
666 || command.equals("selectPreviousColumnCell"))
668 // If nothing is selected, select the first cell in the table
669 if (table.getSelectedRowCount() == 0 &&
670 table.getSelectedColumnCount() == 0)
672 rowModel.setSelectionInterval(0, 0);
673 colModel.setSelectionInterval(0, 0);
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
680 if (!table.isCellSelected(rowLead, colLead))
682 rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(),
683 rowModel.getMinSelectionIndex());
684 colModel.addSelectionInterval(colModel.getMinSelectionIndex(),
685 colModel.getMinSelectionIndex());
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();
695 multColsSelected = table.getSelectedColumnCount() > 1 &&
696 table.getColumnSelectionAllowed();
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)
702 if (command.indexOf("Column") != -1)
703 advanceSingleSelection(colModel, colMax, rowModel, rowMax,
705 ("selectPreviousColumnCell")));
707 advanceSingleSelection(rowModel, rowMax, colModel, colMax,
709 ("selectPreviousRowCell")));
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;
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,
733 ("selectPreviousColumnCell")), true);
736 advanceMultipleSelection(rowModel, rowMinSelected, rowMaxSelected,
737 colModel, colMinSelected, colMaxSelected,
739 ("selectPreviousRowCell")), false);
741 else if (command.equals("selectNextColumn"))
743 colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
744 Math.min(colLead + 1, colMax));
746 else if (command.equals("scrollLeftExtendSelection"))
749 if (colLead == getFirstVisibleColumnIndex())
751 (0, colLead - (getLastVisibleColumnIndex() -
752 getFirstVisibleColumnIndex() + 1));
754 target = getFirstVisibleColumnIndex();
756 colModel.setLeadSelectionIndex(target);
757 rowModel.setLeadSelectionIndex(rowLead);
759 else if (command.equals("scrollDownChangeSelection"))
762 if (rowLead == getLastVisibleRowIndex())
764 (rowMax, rowLead + (getLastVisibleRowIndex() -
765 getFirstVisibleRowIndex() + 1));
767 target = getLastVisibleRowIndex();
769 rowModel.setSelectionInterval(target, target);
770 colModel.setSelectionInterval(colLead, colLead);
772 else if (command.equals("scrollRightExtendSelection"))
775 if (colLead == getLastVisibleColumnIndex())
777 (colMax, colLead + (getLastVisibleColumnIndex() -
778 getFirstVisibleColumnIndex() + 1));
780 target = getLastVisibleColumnIndex();
782 colModel.setLeadSelectionIndex(target);
783 rowModel.setLeadSelectionIndex(rowLead);
785 else if (command.equals("selectAll"))
789 else if (command.equals("selectLastRowExtendSelection"))
791 rowModel.setLeadSelectionIndex(rowMax);
792 colModel.setLeadSelectionIndex(colLead);
794 else if (command.equals("scrollDownExtendSelection"))
797 if (rowLead == getLastVisibleRowIndex())
799 (rowMax, rowLead + (getLastVisibleRowIndex() -
800 getFirstVisibleRowIndex() + 1));
802 target = getLastVisibleRowIndex();
804 rowModel.setLeadSelectionIndex(target);
805 colModel.setLeadSelectionIndex(colLead);
807 else if (command.equals("scrollUpChangeSelection"))
810 if (rowLead == getFirstVisibleRowIndex())
812 (0, rowLead - (getLastVisibleRowIndex() -
813 getFirstVisibleRowIndex() + 1));
815 target = getFirstVisibleRowIndex();
817 rowModel.setSelectionInterval(target, target);
818 colModel.setSelectionInterval(colLead, colLead);
820 else if (command.equals("selectNextRowChangeLead"))
822 if (rowModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
824 // just "selectNextRow"
825 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
826 Math.min(rowLead + 1, rowMax));
827 colModel.setSelectionInterval(colLead,colLead);
830 rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
832 else if (command.equals("selectPreviousRowChangeLead"))
834 if (rowModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
836 // just selectPreviousRow
837 rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
838 Math.min(rowLead -1, 0));
839 colModel.setSelectionInterval(colLead,colLead);
842 rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0));
844 else if (command.equals("selectNextColumnChangeLead"))
846 if (colModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
848 // just selectNextColumn
849 rowModel.setSelectionInterval(rowLead,rowLead);
850 colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
851 Math.min(colLead + 1, colMax));
854 colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax));
856 else if (command.equals("selectPreviousColumnChangeLead"))
858 if (colModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
860 // just selectPreviousColumn
861 rowModel.setSelectionInterval(rowLead,rowLead);
862 colModel.setSelectionInterval(Math.max(colLead - 1, 0),
863 Math.max(colLead - 1, 0));
867 colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0));
869 else if (command.equals("addToSelection"))
871 if (!table.isEditing())
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);
881 else if (command.equals("extendTo"))
883 rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(),
885 colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(),
888 else if (command.equals("toggleAndAnchor"))
890 if (rowModel.isSelectedIndex(rowLead))
891 rowModel.removeSelectionInterval(rowLead, rowLead);
893 rowModel.addSelectionInterval(rowLead, rowLead);
895 if (colModel.isSelectedIndex(colLead))
896 colModel.removeSelectionInterval(colLead, colLead);
898 colModel.addSelectionInterval(colLead, colLead);
900 rowModel.setAnchorSelectionIndex(rowLead);
901 colModel.setAnchorSelectionIndex(colLead);
903 else if (command.equals("stopEditing"))
905 table.editingStopped(new ChangeEvent(command));
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.
913 // Uncomment the following line to print the names of unused bindings
914 // when their keys are pressed
916 // System.out.println ("not implemented: "+e.getActionCommand());
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"));
927 table.scrollRectToVisible
928 (table.getCellRect(rowModel.getLeadSelectionIndex(),
929 colModel.getLeadSelectionIndex(), false));
933 * Returns the column index of the first visible column.
934 * @return the column index of the first visible column.
936 int getFirstVisibleColumnIndex()
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());
946 * Returns the column index of the last visible column.
949 int getLastVisibleColumnIndex()
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());
959 * Returns the row index of the first visible row.
962 int getFirstVisibleRowIndex()
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());
972 * Returns the row index of the last visible row.
975 int getLastVisibleRowIndex()
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)
987 if (getFirstVisibleRowIndex() == -1)
990 return table.getModel().getRowCount() - 1;
992 return table.rowAtPoint(r.getLocation());
996 * A helper method for the key bindings. Used because the actions
997 * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
999 * Selects the next (previous if SHIFT pressed) column for TAB, or row for
1000 * ENTER from within the currently selected cells.
1002 * @param firstModel the ListSelectionModel for columns (TAB) or
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
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
1013 void advanceMultipleSelection (ListSelectionModel firstModel, int firstMin,
1014 int firstMax, ListSelectionModel secondModel,
1015 int secondMin, int secondMax, boolean reverse,
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();
1027 // check if we have to wrap the "firsts" around, going to the other side
1028 if ((firstLead == firstMax && !reverse) ||
1029 (reverse && firstLead == firstMin))
1031 firstModel.addSelectionInterval(reverse ? firstMax : firstMin,
1032 reverse ? firstMax : firstMin);
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);
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)
1045 int[] secondsSelected;
1046 if (eventIsTab && table.getRowSelectionAllowed() ||
1047 !eventIsTab && table.getColumnSelectionAllowed())
1048 secondsSelected = eventIsTab ?
1049 table.getSelectedRows() : table.getSelectedColumns();
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;
1059 // and now find the "next" index within the model
1060 int secondIndex = reverse ? secondsSelected.length - 1 : 0;
1062 while (secondsSelected[secondIndex] <= secondLead)
1065 while (secondsSelected[secondIndex] >= secondLead)
1068 // and select it - updating the lead selection index
1069 secondModel.addSelectionInterval(secondsSelected[secondIndex],
1070 secondsSelected[secondIndex]);
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"
1077 int[] firstsSelected;
1078 if (eventIsTab && table.getColumnSelectionAllowed() ||
1079 !eventIsTab && table.getRowSelectionAllowed())
1080 firstsSelected = eventIsTab ?
1081 table.getSelectedColumns() : table.getSelectedRows();
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;
1089 int firstIndex = reverse ? firstsSelected.length - 1 : 0;
1091 while (firstsSelected[firstIndex] <= firstLead)
1094 while (firstsSelected[firstIndex] >= firstLead)
1096 firstModel.addSelectionInterval(firstsSelected[firstIndex],
1097 firstsSelected[firstIndex]);
1098 secondModel.addSelectionInterval(secondLead, secondLead);
1103 * A helper method for the key bindings. Used because the actions
1104 * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
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
1111 * @param firstMax the last index in firstModel
1112 * @param secondModel the ListSelectionModel for rows (TAB) or columns
1114 * @param secondMax the last index in secondModel
1115 * @param reverse true if SHIFT was pressed for the event
1118 void advanceSingleSelection (ListSelectionModel firstModel, int firstMax,
1119 ListSelectionModel secondModel, int secondMax,
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();
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))
1131 // check if we have to wrap around
1132 if (secondLead == 0)
1133 secondLead += secondMax + 1;
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
1143 secondModel.setSelectionInterval(secondLead, secondLead);
1145 // if we are going backwards, subtract 2 because we add 1 later
1146 // for net change of -1
1149 // check for wraparound
1151 firstLead += firstMax + 1;
1154 // select the next "first"
1155 firstModel.setSelectionInterval ((firstLead + 1)%(firstMax + 1),
1156 (firstLead + 1)%(firstMax + 1));
1160 protected void installListeners()
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);
1177 protected void uninstallDefaults()
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
1183 // table.setFont(null);
1184 // table.setGridColor(null);
1185 // table.setForeground(null);
1186 // table.setBackground(null);
1187 // table.setSelectionForeground(null);
1188 // table.setSelectionBackground(null);
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
1197 protected void uninstallKeyboardActions()
1198 throws NotImplementedException
1200 // TODO: Implement this properly.
1203 protected void uninstallListeners()
1205 table.removeFocusListener(focusListener);
1206 table.removeKeyListener(keyListener);
1207 table.removeMouseListener(mouseInputListener);
1208 table.removeMouseMotionListener(mouseInputListener);
1209 table.removePropertyChangeListener(propertyChangeListener);
1210 propertyChangeListener = null;
1213 public void installUI(JComponent comp)
1215 table = (JTable)comp;
1216 rendererPane = new CellRendererPane();
1217 table.add(rendererPane);
1220 installKeyboardActions();
1224 public void uninstallUI(JComponent c)
1226 uninstallListeners();
1227 uninstallKeyboardActions();
1228 uninstallDefaults();
1230 table.remove(rendererPane);
1231 rendererPane = null;
1236 * Paints a single cell in the table.
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
1244 * @param rend A cell renderer to paint with
1246 void paintCell(Graphics g, int row, int col, Rectangle bounds,
1247 TableCellRenderer rend)
1249 Component comp = table.prepareRenderer(rend, row, col);
1250 rendererPane.paintComponent(g, comp, table, bounds);
1254 * Paint the associated table.
1256 public void paint(Graphics gfx, JComponent ignored)
1258 int ncols = table.getColumnCount();
1259 int nrows = table.getRowCount();
1260 if (nrows == 0 || ncols == 0)
1263 Rectangle clip = gfx.getClipBounds();
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);
1270 int r0 = table.rowAtPoint(p1);
1273 Point p2 = new Point(clip.x + clip.width, clip.y + clip.height);
1274 int cn = table.columnAtPoint(p2);
1276 cn = table.getColumnCount() - 1;
1277 int rn = table.rowAtPoint(p2);
1279 rn = table.getRowCount() - 1;
1281 int columnMargin = table.getColumnModel().getColumnMargin();
1282 int rowMargin = table.getRowMargin();
1284 TableColumnModel cmodel = table.getColumnModel();
1285 int [] widths = new int[cn+1];
1286 for (int i = c0; i <=cn ; i++)
1288 widths[i] = cmodel.getColumn(i).getWidth() - columnMargin;
1291 Rectangle bounds = table.getCellRect(r0, c0, false);
1292 // The left boundary of the area being repainted.
1293 int left = bounds.x;
1295 // The top boundary of the area being repainted.
1298 // The bottom boundary of the area being repainted.
1301 // paint the cell contents
1302 Color grid = table.getGridColor();
1303 for (int r = r0; r <= rn; ++r)
1305 for (int c = c0; c <= cn; ++c)
1307 bounds.width = widths[c];
1308 paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c));
1309 bounds.x += widths[c] + columnMargin;
1312 bounds.y += table.getRowHeight(r);
1313 // Update row height for tables with custom heights.
1314 bounds.height = table.getRowHeight(r + 1) - rowMargin;
1317 bottom = bounds.y - rowMargin;
1319 // paint vertical grid lines
1320 if (grid != null && table.getShowVerticalLines())
1322 Color save = gfx.getColor();
1324 int x = left - columnMargin;
1325 for (int c = c0; c <= cn; ++c)
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);
1335 // paint horizontal grid lines
1336 if (grid != null && table.getShowHorizontalLines())
1338 Color save = gfx.getColor();
1340 int y = top - rowMargin;
1341 for (int r = r0; r <= rn; ++r)
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);