Merge from mainline
[official-gcc.git] / libjava / classpath / javax / swing / plaf / basic / BasicTableUI.java
blob18b69120d115d0542d379bcd7508e4b4be1d33cd
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)
9 any later version.
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
19 02110-1301 USA.
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.
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.ActionListener;
50 import java.awt.event.FocusEvent;
51 import java.awt.event.FocusListener;
52 import java.awt.event.KeyEvent;
53 import java.awt.event.KeyListener;
54 import java.awt.event.MouseEvent;
55 import java.beans.PropertyChangeEvent;
56 import java.beans.PropertyChangeListener;
58 import javax.swing.AbstractAction;
59 import javax.swing.ActionMap;
60 import javax.swing.CellRendererPane;
61 import javax.swing.DefaultListSelectionModel;
62 import javax.swing.InputMap;
63 import javax.swing.JComponent;
64 import javax.swing.JTable;
65 import javax.swing.JTextField;
66 import javax.swing.KeyStroke;
67 import javax.swing.ListSelectionModel;
68 import javax.swing.LookAndFeel;
69 import javax.swing.UIManager;
70 import javax.swing.border.Border;
71 import javax.swing.event.ChangeEvent;
72 import javax.swing.event.MouseInputListener;
73 import javax.swing.plaf.ActionMapUIResource;
74 import javax.swing.plaf.ComponentUI;
75 import javax.swing.plaf.InputMapUIResource;
76 import javax.swing.plaf.TableUI;
77 import javax.swing.table.TableCellRenderer;
78 import javax.swing.table.TableColumn;
79 import javax.swing.table.TableColumnModel;
80 import javax.swing.table.TableModel;
82 public class BasicTableUI extends TableUI
84 public static ComponentUI createUI(JComponent comp)
86 return new BasicTableUI();
89 protected FocusListener focusListener;
90 protected KeyListener keyListener;
91 protected MouseInputListener mouseInputListener;
92 protected CellRendererPane rendererPane;
93 protected JTable table;
95 /** The normal cell border. */
96 Border cellBorder;
98 /** The action bound to KeyStrokes. */
99 TableAction action;
102 * Listens for changes to the tables properties.
104 private PropertyChangeListener propertyChangeListener;
107 * Handles key events for the JTable. Key events should be handled through
108 * the InputMap/ActionMap mechanism since JDK1.3. This class is only there
109 * for backwards compatibility.
111 * @author Roman Kennke (kennke@aicas.com)
113 public class KeyHandler implements KeyListener
117 * Receives notification that a key has been pressed and released.
119 * @param event the key event
121 public void keyTyped(KeyEvent event)
123 // Key events should be handled through the InputMap/ActionMap mechanism
124 // since JDK1.3. This class is only there for backwards compatibility.
128 * Receives notification that a key has been pressed.
130 * @param event the key event
132 public void keyPressed(KeyEvent event)
134 // Key events should be handled through the InputMap/ActionMap mechanism
135 // since JDK1.3. This class is only there for backwards compatibility.
139 * Receives notification that a key has been released.
141 * @param event the key event
143 public void keyReleased(KeyEvent event)
145 // Key events should be handled through the InputMap/ActionMap mechanism
146 // since JDK1.3. This class is only there for backwards compatibility.
150 public class FocusHandler implements FocusListener
152 public void focusGained(FocusEvent e)
154 // TODO: Implement this properly.
157 public void focusLost(FocusEvent e)
159 // TODO: Implement this properly.
163 public class MouseInputHandler implements MouseInputListener
165 Point begin, curr;
167 private void updateSelection(boolean controlPressed)
169 // Update the rows
170 int lo_row = table.rowAtPoint(begin);
171 int hi_row = table.rowAtPoint(curr);
172 ListSelectionModel rowModel = table.getSelectionModel();
173 if (lo_row != -1 && hi_row != -1)
175 if (controlPressed && rowModel.getSelectionMode()
176 != ListSelectionModel.SINGLE_SELECTION)
177 rowModel.addSelectionInterval(lo_row, hi_row);
178 else
179 rowModel.setSelectionInterval(lo_row, hi_row);
182 // Update the columns
183 int lo_col = table.columnAtPoint(begin);
184 int hi_col = table.columnAtPoint(curr);
185 ListSelectionModel colModel = table.getColumnModel().
186 getSelectionModel();
187 if (lo_col != -1 && hi_col != -1)
189 if (controlPressed && colModel.getSelectionMode() !=
190 ListSelectionModel.SINGLE_SELECTION)
191 colModel.addSelectionInterval(lo_col, hi_col);
192 else
193 colModel.setSelectionInterval(lo_col, hi_col);
197 public void mouseClicked(MouseEvent e)
199 // TODO: What should be done here, if anything?
202 public void mouseDragged(MouseEvent e)
204 if (table.isEnabled())
206 curr = new Point(e.getX(), e.getY());
207 updateSelection(e.isControlDown());
211 public void mouseEntered(MouseEvent e)
213 // TODO: What should be done here, if anything?
216 public void mouseExited(MouseEvent e)
218 // TODO: What should be done here, if anything?
221 public void mouseMoved(MouseEvent e)
223 // TODO: What should be done here, if anything?
226 public void mousePressed(MouseEvent e)
228 if (table.isEnabled())
230 ListSelectionModel rowModel = table.getSelectionModel();
231 ListSelectionModel colModel = table.getColumnModel().getSelectionModel();
232 int rowLead = rowModel.getLeadSelectionIndex();
233 int colLead = colModel.getLeadSelectionIndex();
235 begin = new Point(e.getX(), e.getY());
236 curr = new Point(e.getX(), e.getY());
237 //if control is pressed and the cell is already selected, deselect it
238 if (e.isControlDown() && table.
239 isCellSelected(table.rowAtPoint(begin),table.columnAtPoint(begin)))
241 table.getSelectionModel().
242 removeSelectionInterval(table.rowAtPoint(begin),
243 table.rowAtPoint(begin));
244 table.getColumnModel().getSelectionModel().
245 removeSelectionInterval(table.columnAtPoint(begin),
246 table.columnAtPoint(begin));
248 else
249 updateSelection(e.isControlDown());
251 // If we were editing, but the moved to another cell, stop editing
252 if (rowLead != rowModel.getLeadSelectionIndex() ||
253 colLead != colModel.getLeadSelectionIndex())
254 if (table.isEditing())
255 table.editingStopped(new ChangeEvent(e));
259 public void mouseReleased(MouseEvent e)
261 if (table.isEnabled())
263 begin = null;
264 curr = null;
270 * Listens for changes to the model property of the JTable and adjusts some
271 * settings.
273 * @author Roman Kennke (kennke@aicas.com)
275 private class PropertyChangeHandler implements PropertyChangeListener
278 * Receives notification if one of the JTable's properties changes.
280 * @param ev the property change event
282 public void propertyChange(PropertyChangeEvent ev)
284 String propName = ev.getPropertyName();
285 if (propName.equals("model"))
287 ListSelectionModel rowSel = table.getSelectionModel();
288 rowSel.clearSelection();
289 ListSelectionModel colSel = table.getColumnModel().getSelectionModel();
290 colSel.clearSelection();
291 TableModel model = table.getModel();
293 // Adjust lead and anchor selection indices of the row and column
294 // selection models.
295 if (model.getRowCount() > 0)
297 rowSel.setAnchorSelectionIndex(0);
298 rowSel.setLeadSelectionIndex(0);
300 else
302 rowSel.setAnchorSelectionIndex(-1);
303 rowSel.setLeadSelectionIndex(-1);
305 if (model.getColumnCount() > 0)
307 colSel.setAnchorSelectionIndex(0);
308 colSel.setLeadSelectionIndex(0);
310 else
312 colSel.setAnchorSelectionIndex(-1);
313 colSel.setLeadSelectionIndex(-1);
319 protected FocusListener createFocusListener()
321 return new FocusHandler();
324 protected MouseInputListener createMouseInputListener()
326 return new MouseInputHandler();
331 * Creates and returns a key listener for the JTable.
333 * @return a key listener for the JTable
335 protected KeyListener createKeyListener()
337 return new KeyHandler();
341 * Return the maximum size of the table. The maximum height is the row
342 * height times the number of rows. The maximum width is the sum of
343 * the maximum widths of each column.
345 * @param comp the component whose maximum size is being queried,
346 * this is ignored.
347 * @return a Dimension object representing the maximum size of the table,
348 * or null if the table has no elements.
350 public Dimension getMaximumSize(JComponent comp)
352 int maxTotalColumnWidth = 0;
353 for (int i = 0; i < table.getColumnCount(); i++)
354 maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth();
355 if (maxTotalColumnWidth == 0 || table.getRowCount() == 0)
356 return null;
357 return new Dimension(maxTotalColumnWidth, table.getRowCount()*table.getRowHeight());
361 * Return the minimum size of the table. The minimum height is the row
362 * height times the number of rows. The minimum width is the sum of
363 * the minimum widths of each column.
365 * @param comp the component whose minimum size is being queried,
366 * this is ignored.
367 * @return a Dimension object representing the minimum size of the table,
368 * or null if the table has no elements.
370 public Dimension getMinimumSize(JComponent comp)
372 int minTotalColumnWidth = 0;
373 for (int i = 0; i < table.getColumnCount(); i++)
374 minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth();
375 if (minTotalColumnWidth == 0 || table.getRowCount() == 0)
376 return null;
377 return new Dimension(minTotalColumnWidth, table.getRowCount()*table.getRowHeight());
380 public Dimension getPreferredSize(JComponent comp)
382 int width = table.getColumnModel().getTotalColumnWidth();
383 int height = table.getRowCount() * table.getRowHeight();
384 return new Dimension(width, height);
387 protected void installDefaults()
389 LookAndFeel.installColorsAndFont(table, "Table.background",
390 "Table.foreground", "Table.font");
391 table.setGridColor(UIManager.getColor("Table.gridColor"));
392 table.setSelectionForeground(UIManager.getColor("Table.selectionForeground"));
393 table.setSelectionBackground(UIManager.getColor("Table.selectionBackground"));
394 table.setOpaque(true);
395 rendererPane = new CellRendererPane();
398 protected void installKeyboardActions()
400 InputMap ancestorMap = (InputMap) UIManager.get("Table.ancestorInputMap");
401 InputMapUIResource parentInputMap = new InputMapUIResource();
402 // FIXME: The JDK uses a LazyActionMap for parentActionMap
403 ActionMap parentActionMap = new ActionMapUIResource();
404 action = new TableAction();
405 Object keys[] = ancestorMap.allKeys();
406 // Register key bindings in the UI InputMap-ActionMap pair
407 for (int i = 0; i < keys.length; i++)
409 KeyStroke stroke = (KeyStroke)keys[i];
410 String actionString = (String) ancestorMap.get(stroke);
412 parentInputMap.put(KeyStroke.getKeyStroke(stroke.getKeyCode(),
413 stroke.getModifiers()),
414 actionString);
416 parentActionMap.put (actionString,
417 new ActionListenerProxy (action, actionString));
420 // Set the UI InputMap-ActionMap pair to be the parents of the
421 // JTable's InputMap-ActionMap pair
422 parentInputMap.setParent
423 (table.getInputMap
424 (JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).getParent());
425 parentActionMap.setParent(table.getActionMap().getParent());
426 table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).
427 setParent(parentInputMap);
428 table.getActionMap().setParent(parentActionMap);
432 * This class is used to mimmic the behaviour of the JDK when registering
433 * keyboard actions. It is the same as the private class used in JComponent
434 * for the same reason. This class receives an action event and dispatches
435 * it to the true receiver after altering the actionCommand property of the
436 * event.
438 private static class ActionListenerProxy
439 extends AbstractAction
441 ActionListener target;
442 String bindingCommandName;
444 public ActionListenerProxy(ActionListener li,
445 String cmd)
447 target = li;
448 bindingCommandName = cmd;
451 public void actionPerformed(ActionEvent e)
453 ActionEvent derivedEvent = new ActionEvent(e.getSource(),
454 e.getID(),
455 bindingCommandName,
456 e.getModifiers());
457 target.actionPerformed(derivedEvent);
462 * This class implements the actions that we want to happen
463 * when specific keys are pressed for the JTable. The actionPerformed
464 * method is called when a key that has been registered for the JTable
465 * is received.
467 class TableAction extends AbstractAction
470 * What to do when this action is called.
472 * @param e the ActionEvent that caused this action.
474 public void actionPerformed (ActionEvent e)
476 DefaultListSelectionModel rowModel = (DefaultListSelectionModel) table.getSelectionModel();
477 DefaultListSelectionModel colModel = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel();
479 int rowLead = rowModel.getLeadSelectionIndex();
480 int rowMax = table.getModel().getRowCount() - 1;
482 int colLead = colModel.getLeadSelectionIndex();
483 int colMax = table.getModel().getColumnCount() - 1;
485 String command = e.getActionCommand();
487 if (command.equals("selectPreviousRowExtendSelection"))
489 rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0));
490 colModel.setLeadSelectionIndex(colLead);
492 else if (command.equals("selectLastColumn"))
494 rowModel.setSelectionInterval(rowLead, rowLead);
495 colModel.setSelectionInterval(colMax, colMax);
497 else if (command.equals("startEditing"))
499 if (table.isCellEditable(rowLead, colLead))
500 table.editCellAt(rowLead,colLead);
502 else if (command.equals("selectFirstRowExtendSelection"))
504 rowModel.setLeadSelectionIndex(0);
505 colModel.setLeadSelectionIndex(colLead);
507 else if (command.equals("selectFirstColumn"))
509 rowModel.setSelectionInterval(rowLead, rowLead);
510 colModel.setSelectionInterval(0, 0);
512 else if (command.equals("selectFirstColumnExtendSelection"))
514 colModel.setLeadSelectionIndex(0);
515 rowModel.setLeadSelectionIndex(rowLead);
517 else if (command.equals("selectLastRow"))
519 rowModel.setSelectionInterval(rowMax,rowMax);
520 colModel.setSelectionInterval(colLead, colLead);
522 else if (command.equals("selectNextRowExtendSelection"))
524 rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
525 colModel.setLeadSelectionIndex(colLead);
527 else if (command.equals("selectFirstRow"))
529 rowModel.setSelectionInterval(0,0);
530 colModel.setSelectionInterval(colLead, colLead);
532 else if (command.equals("selectNextColumnExtendSelection"))
534 colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax));
535 rowModel.setLeadSelectionIndex(rowLead);
537 else if (command.equals("selectLastColumnExtendSelection"))
539 colModel.setLeadSelectionIndex(colMax);
540 rowModel.setLeadSelectionIndex(rowLead);
542 else if (command.equals("selectPreviousColumnExtendSelection"))
544 colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0));
545 rowModel.setLeadSelectionIndex(rowLead);
547 else if (command.equals("selectNextRow"))
549 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
550 Math.min(rowLead + 1, rowMax));
551 colModel.setSelectionInterval(colLead,colLead);
553 else if (command.equals("scrollUpExtendSelection"))
555 int target;
556 if (rowLead == getFirstVisibleRowIndex())
557 target = Math.max
558 (0, rowLead - (getLastVisibleRowIndex() -
559 getFirstVisibleRowIndex() + 1));
560 else
561 target = getFirstVisibleRowIndex();
563 rowModel.setLeadSelectionIndex(target);
564 colModel.setLeadSelectionIndex(colLead);
566 else if (command.equals("selectPreviousRow"))
568 rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
569 Math.max(rowLead - 1, 0));
570 colModel.setSelectionInterval(colLead,colLead);
572 else if (command.equals("scrollRightChangeSelection"))
574 int target;
575 if (colLead == getLastVisibleColumnIndex())
576 target = Math.min
577 (colMax, colLead + (getLastVisibleColumnIndex() -
578 getFirstVisibleColumnIndex() + 1));
579 else
580 target = getLastVisibleColumnIndex();
582 colModel.setSelectionInterval(target, target);
583 rowModel.setSelectionInterval(rowLead, rowLead);
585 else if (command.equals("selectPreviousColumn"))
587 rowModel.setSelectionInterval(rowLead,rowLead);
588 colModel.setSelectionInterval(Math.max(colLead - 1, 0),
589 Math.max(colLead - 1, 0));
591 else if (command.equals("scrollLeftChangeSelection"))
593 int target;
594 if (colLead == getFirstVisibleColumnIndex())
595 target = Math.max
596 (0, colLead - (getLastVisibleColumnIndex() -
597 getFirstVisibleColumnIndex() + 1));
598 else
599 target = getFirstVisibleColumnIndex();
601 colModel.setSelectionInterval(target, target);
602 rowModel.setSelectionInterval(rowLead, rowLead);
604 else if (command.equals("clearSelection"))
606 table.clearSelection();
608 else if (command.equals("cancel"))
610 // FIXME: implement other parts of "cancel" like undo-ing last
611 // selection. Right now it just calls editingCancelled if
612 // we're currently editing.
613 if (table.isEditing())
614 table.editingCanceled(new ChangeEvent("cancel"));
616 else if (command.equals("selectNextRowCell")
617 || command.equals("selectPreviousRowCell")
618 || command.equals("selectNextColumnCell")
619 || command.equals("selectPreviousColumnCell"))
621 // If nothing is selected, select the first cell in the table
622 if (table.getSelectedRowCount() == 0 &&
623 table.getSelectedColumnCount() == 0)
625 rowModel.setSelectionInterval(0, 0);
626 colModel.setSelectionInterval(0, 0);
627 return;
630 // If the lead selection index isn't selected (ie a remove operation
631 // happened, then set the lead to the first selected cell in the
632 // table
633 if (!table.isCellSelected(rowLead, colLead))
635 rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(),
636 rowModel.getMinSelectionIndex());
637 colModel.addSelectionInterval(colModel.getMinSelectionIndex(),
638 colModel.getMinSelectionIndex());
639 return;
642 // multRowsSelected and multColsSelected tell us if multiple rows or
643 // columns are selected, respectively
644 boolean multRowsSelected, multColsSelected;
645 multRowsSelected = table.getSelectedRowCount() > 1 &&
646 table.getRowSelectionAllowed();
648 multColsSelected = table.getSelectedColumnCount() > 1 &&
649 table.getColumnSelectionAllowed();
651 // If there is just one selection, select the next cell, and wrap
652 // when you get to the edges of the table.
653 if (!multColsSelected && !multRowsSelected)
655 if (command.indexOf("Column") != -1)
656 advanceSingleSelection(colModel, colMax, rowModel, rowMax,
657 (command.equals
658 ("selectPreviousColumnCell")));
659 else
660 advanceSingleSelection(rowModel, rowMax, colModel, colMax,
661 (command.equals
662 ("selectPreviousRowCell")));
663 return;
667 // rowMinSelected and rowMaxSelected are the minimum and maximum
668 // values respectively of selected cells in the row selection model
669 // Similarly for colMinSelected and colMaxSelected.
670 int rowMaxSelected = table.getRowSelectionAllowed() ?
671 rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1;
672 int rowMinSelected = table.getRowSelectionAllowed() ?
673 rowModel.getMinSelectionIndex() : 0;
674 int colMaxSelected = table.getColumnSelectionAllowed() ?
675 colModel.getMaxSelectionIndex() :
676 table.getModel().getColumnCount() - 1;
677 int colMinSelected = table.getColumnSelectionAllowed() ?
678 colModel.getMinSelectionIndex() : 0;
680 // If there are multiple rows and columns selected, select the next
681 // cell and wrap at the edges of the selection.
682 if (command.indexOf("Column") != -1)
683 advanceMultipleSelection(colModel, colMinSelected, colMaxSelected,
684 rowModel, rowMinSelected, rowMaxSelected,
685 (command.equals
686 ("selectPreviousColumnCell")), true);
688 else
689 advanceMultipleSelection(rowModel, rowMinSelected, rowMaxSelected,
690 colModel, colMinSelected, colMaxSelected,
691 (command.equals
692 ("selectPreviousRowCell")), false);
694 else if (command.equals("selectNextColumn"))
696 rowModel.setSelectionInterval(rowLead,rowLead);
697 colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
698 Math.min(colLead + 1, colMax));
700 else if (command.equals("scrollLeftExtendSelection"))
702 int target;
703 if (colLead == getFirstVisibleColumnIndex())
704 target = Math.max
705 (0, colLead - (getLastVisibleColumnIndex() -
706 getFirstVisibleColumnIndex() + 1));
707 else
708 target = getFirstVisibleColumnIndex();
710 colModel.setLeadSelectionIndex(target);
711 rowModel.setLeadSelectionIndex(rowLead);
713 else if (command.equals("scrollDownChangeSelection"))
715 int target;
716 if (rowLead == getLastVisibleRowIndex())
717 target = Math.min
718 (rowMax, rowLead + (getLastVisibleRowIndex() -
719 getFirstVisibleRowIndex() + 1));
720 else
721 target = getLastVisibleRowIndex();
723 rowModel.setSelectionInterval(target, target);
724 colModel.setSelectionInterval(colLead, colLead);
726 else if (command.equals("scrollRightExtendSelection"))
728 int target;
729 if (colLead == getLastVisibleColumnIndex())
730 target = Math.min
731 (colMax, colLead + (getLastVisibleColumnIndex() -
732 getFirstVisibleColumnIndex() + 1));
733 else
734 target = getLastVisibleColumnIndex();
736 colModel.setLeadSelectionIndex(target);
737 rowModel.setLeadSelectionIndex(rowLead);
739 else if (command.equals("selectAll"))
741 table.selectAll();
743 else if (command.equals("selectLastRowExtendSelection"))
745 rowModel.setLeadSelectionIndex(rowMax);
746 colModel.setLeadSelectionIndex(colLead);
748 else if (command.equals("scrollDownExtendSelection"))
750 int target;
751 if (rowLead == getLastVisibleRowIndex())
752 target = Math.min
753 (rowMax, rowLead + (getLastVisibleRowIndex() -
754 getFirstVisibleRowIndex() + 1));
755 else
756 target = getLastVisibleRowIndex();
758 rowModel.setLeadSelectionIndex(target);
759 colModel.setLeadSelectionIndex(colLead);
761 else if (command.equals("scrollUpChangeSelection"))
763 int target;
764 if (rowLead == getFirstVisibleRowIndex())
765 target = Math.max
766 (0, rowLead - (getLastVisibleRowIndex() -
767 getFirstVisibleRowIndex() + 1));
768 else
769 target = getFirstVisibleRowIndex();
771 rowModel.setSelectionInterval(target, target);
772 colModel.setSelectionInterval(colLead, colLead);
774 else if (command.equals("selectNextRowChangeLead"))
776 if (rowModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
778 // just "selectNextRow"
779 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax),
780 Math.min(rowLead + 1, rowMax));
781 colModel.setSelectionInterval(colLead,colLead);
783 else
784 rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax));
786 else if (command.equals("selectPreviousRowChangeLead"))
788 if (rowModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
790 // just selectPreviousRow
791 rowModel.setSelectionInterval(Math.max(rowLead - 1, 0),
792 Math.min(rowLead -1, 0));
793 colModel.setSelectionInterval(colLead,colLead);
795 else
796 rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0));
798 else if (command.equals("selectNextColumnChangeLead"))
800 if (colModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
802 // just selectNextColumn
803 rowModel.setSelectionInterval(rowLead,rowLead);
804 colModel.setSelectionInterval(Math.min(colLead + 1, colMax),
805 Math.min(colLead + 1, colMax));
807 else
808 colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax));
810 else if (command.equals("selectPreviousColumnChangeLead"))
812 if (colModel.getSelectionMode() != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION)
814 // just selectPreviousColumn
815 rowModel.setSelectionInterval(rowLead,rowLead);
816 colModel.setSelectionInterval(Math.max(colLead - 1, 0),
817 Math.max(colLead - 1, 0));
820 else
821 colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0));
823 else if (command.equals("addToSelection"))
825 if (!table.isEditing())
827 int oldRowAnchor = rowModel.getAnchorSelectionIndex();
828 int oldColAnchor = colModel.getAnchorSelectionIndex();
829 rowModel.addSelectionInterval(rowLead, rowLead);
830 colModel.addSelectionInterval(colLead, colLead);
831 rowModel.setAnchorSelectionIndex(oldRowAnchor);
832 colModel.setAnchorSelectionIndex(oldColAnchor);
835 else if (command.equals("extendTo"))
837 rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(),
838 rowLead);
839 colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(),
840 colLead);
842 else if (command.equals("toggleAndAnchor"))
844 if (rowModel.isSelectedIndex(rowLead))
845 rowModel.removeSelectionInterval(rowLead, rowLead);
846 else
847 rowModel.addSelectionInterval(rowLead, rowLead);
849 if (colModel.isSelectedIndex(colLead))
850 colModel.removeSelectionInterval(colLead, colLead);
851 else
852 colModel.addSelectionInterval(colLead, colLead);
854 rowModel.setAnchorSelectionIndex(rowLead);
855 colModel.setAnchorSelectionIndex(colLead);
857 else
859 // If we're here that means we bound this TableAction class
860 // to a keyboard input but we either want to ignore that input
861 // or we just haven't implemented its action yet.
863 // Uncomment the following line to print the names of unused bindings
864 // when their keys are pressed
866 // System.out.println ("not implemented: "+e.getActionCommand());
869 // Any commands whose keyStrokes should be used by the Editor should not
870 // cause editing to be stopped: ie, the SPACE sends "addToSelection" but
871 // if the table is in editing mode, the space should not cause us to stop
872 // editing because it should be used by the Editor.
873 if (table.isEditing() && command != "startEditing"
874 && command != "addToSelection")
875 table.editingStopped(new ChangeEvent("update"));
877 table.scrollRectToVisible
878 (table.getCellRect(rowModel.getLeadSelectionIndex(),
879 colModel.getLeadSelectionIndex(), false));
880 table.repaint();
884 * Returns the column index of the first visible column.
885 * @return the column index of the first visible column.
887 int getFirstVisibleColumnIndex()
889 ComponentOrientation or = table.getComponentOrientation();
890 Rectangle r = table.getVisibleRect();
891 if (!or.isLeftToRight())
892 r.translate((int) r.getWidth() - 1, 0);
893 return table.columnAtPoint(r.getLocation());
897 * Returns the column index of the last visible column.
900 int getLastVisibleColumnIndex()
902 ComponentOrientation or = table.getComponentOrientation();
903 Rectangle r = table.getVisibleRect();
904 if (or.isLeftToRight())
905 r.translate((int) r.getWidth() - 1, 0);
906 return table.columnAtPoint(r.getLocation());
910 * Returns the row index of the first visible row.
913 int getFirstVisibleRowIndex()
915 ComponentOrientation or = table.getComponentOrientation();
916 Rectangle r = table.getVisibleRect();
917 if (!or.isLeftToRight())
918 r.translate((int) r.getWidth() - 1, 0);
919 return table.rowAtPoint(r.getLocation());
923 * Returns the row index of the last visible row.
926 int getLastVisibleRowIndex()
928 ComponentOrientation or = table.getComponentOrientation();
929 Rectangle r = table.getVisibleRect();
930 r.translate(0, (int) r.getHeight() - 1);
931 if (or.isLeftToRight())
932 r.translate((int) r.getWidth() - 1, 0);
933 // The next if makes sure that we don't return -1 simply because
934 // there is white space at the bottom of the table (ie, the display
935 // area is larger than the table)
936 if (table.rowAtPoint(r.getLocation()) == -1)
938 if (getFirstVisibleRowIndex() == -1)
939 return -1;
940 else
941 return table.getModel().getRowCount() - 1;
943 return table.rowAtPoint(r.getLocation());
947 * A helper method for the key bindings. Used because the actions
948 * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
950 * Selects the next (previous if SHIFT pressed) column for TAB, or row for
951 * ENTER from within the currently selected cells.
953 * @param firstModel the ListSelectionModel for columns (TAB) or
954 * rows (ENTER)
955 * @param firstMin the first selected index in firstModel
956 * @param firstMax the last selected index in firstModel
957 * @param secondModel the ListSelectionModel for rows (TAB) or
958 * columns (ENTER)
959 * @param secondMin the first selected index in secondModel
960 * @param secondMax the last selected index in secondModel
961 * @param reverse true if shift was held for the event
962 * @param eventIsTab true if TAB was pressed, false if ENTER pressed
964 void advanceMultipleSelection (ListSelectionModel firstModel, int firstMin,
965 int firstMax, ListSelectionModel secondModel,
966 int secondMin, int secondMax, boolean reverse,
967 boolean eventIsTab)
969 // If eventIsTab, all the "firsts" correspond to columns, otherwise, to rows
970 // "seconds" correspond to the opposite
971 int firstLead = firstModel.getLeadSelectionIndex();
972 int secondLead = secondModel.getLeadSelectionIndex();
973 int numFirsts = eventIsTab ?
974 table.getModel().getColumnCount() : table.getModel().getRowCount();
975 int numSeconds = eventIsTab ?
976 table.getModel().getRowCount() : table.getModel().getColumnCount();
978 // check if we have to wrap the "firsts" around, going to the other side
979 if ((firstLead == firstMax && !reverse) ||
980 (reverse && firstLead == firstMin))
982 firstModel.addSelectionInterval(reverse ? firstMax : firstMin,
983 reverse ? firstMax : firstMin);
985 // check if we have to wrap the "seconds"
986 if ((secondLead == secondMax && !reverse) ||
987 (reverse && secondLead == secondMin))
988 secondModel.addSelectionInterval(reverse ? secondMax : secondMin,
989 reverse ? secondMax : secondMin);
991 // if we're not wrapping the seconds, we have to find out where we
992 // are within the secondModel and advance to the next cell (or
993 // go back to the previous cell if reverse == true)
994 else
996 int[] secondsSelected;
997 if (eventIsTab && table.getRowSelectionAllowed() ||
998 !eventIsTab && table.getColumnSelectionAllowed())
999 secondsSelected = eventIsTab ?
1000 table.getSelectedRows() : table.getSelectedColumns();
1001 else
1003 // if row selection is not allowed, then the entire column gets
1004 // selected when you click on it, so consider ALL rows selected
1005 secondsSelected = new int[numSeconds];
1006 for (int i = 0; i < numSeconds; i++)
1007 secondsSelected[i] = i;
1010 // and now find the "next" index within the model
1011 int secondIndex = reverse ? secondsSelected.length - 1 : 0;
1012 if (!reverse)
1013 while (secondsSelected[secondIndex] <= secondLead)
1014 secondIndex++;
1015 else
1016 while (secondsSelected[secondIndex] >= secondLead)
1017 secondIndex--;
1019 // and select it - updating the lead selection index
1020 secondModel.addSelectionInterval(secondsSelected[secondIndex],
1021 secondsSelected[secondIndex]);
1024 // We didn't have to wrap the firsts, so just find the "next" first
1025 // and select it, we don't have to change "seconds"
1026 else
1028 int[] firstsSelected;
1029 if (eventIsTab && table.getColumnSelectionAllowed() ||
1030 !eventIsTab && table.getRowSelectionAllowed())
1031 firstsSelected = eventIsTab ?
1032 table.getSelectedColumns() : table.getSelectedRows();
1033 else
1035 // if selection not allowed, consider ALL firsts to be selected
1036 firstsSelected = new int[numFirsts];
1037 for (int i = 0; i < numFirsts; i++)
1038 firstsSelected[i] = i;
1040 int firstIndex = reverse ? firstsSelected.length - 1 : 0;
1041 if (!reverse)
1042 while (firstsSelected[firstIndex] <= firstLead)
1043 firstIndex++;
1044 else
1045 while (firstsSelected[firstIndex] >= firstLead)
1046 firstIndex--;
1047 firstModel.addSelectionInterval(firstsSelected[firstIndex],
1048 firstsSelected[firstIndex]);
1049 secondModel.addSelectionInterval(secondLead, secondLead);
1053 /**
1054 * A helper method for the key bindings. Used because the actions
1055 * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar.
1057 * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER)
1058 * in the table, changing the current selection. All cells in the table
1059 * are eligible, not just the ones that are currently selected.
1060 * @param firstModel the ListSelectionModel for columns (TAB) or rows
1061 * (ENTER)
1062 * @param firstMax the last index in firstModel
1063 * @param secondModel the ListSelectionModel for rows (TAB) or columns
1064 * (ENTER)
1065 * @param secondMax the last index in secondModel
1066 * @param reverse true if SHIFT was pressed for the event
1069 void advanceSingleSelection (ListSelectionModel firstModel, int firstMax,
1070 ListSelectionModel secondModel, int secondMax,
1071 boolean reverse)
1073 // for TABs, "first" corresponds to columns and "seconds" to rows.
1074 // the opposite is true for ENTERs
1075 int firstLead = firstModel.getLeadSelectionIndex();
1076 int secondLead = secondModel.getLeadSelectionIndex();
1078 // if we are going backwards subtract 2 because we later add 1
1079 // for a net change of -1
1080 if (reverse && (firstLead == 0))
1082 // check if we have to wrap around
1083 if (secondLead == 0)
1084 secondLead += secondMax + 1;
1085 secondLead -= 2;
1088 // do we have to wrap the "seconds"?
1089 if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax))
1090 secondModel.setSelectionInterval((secondLead + 1)%(secondMax + 1),
1091 (secondLead + 1)%(secondMax + 1));
1092 // if not, just reselect the current lead
1093 else
1094 secondModel.setSelectionInterval(secondLead, secondLead);
1096 // if we are going backwards, subtract 2 because we add 1 later
1097 // for net change of -1
1098 if (reverse)
1100 // check for wraparound
1101 if (firstLead == 0)
1102 firstLead += firstMax + 1;
1103 firstLead -= 2;
1105 // select the next "first"
1106 firstModel.setSelectionInterval ((firstLead + 1)%(firstMax + 1),
1107 (firstLead + 1)%(firstMax + 1));
1111 protected void installListeners()
1113 if (focusListener == null)
1114 focusListener = createFocusListener();
1115 table.addFocusListener(focusListener);
1116 if (keyListener == null)
1117 keyListener = createKeyListener();
1118 table.addKeyListener(keyListener);
1119 if (mouseInputListener == null)
1120 mouseInputListener = createMouseInputListener();
1121 table.addMouseListener(mouseInputListener);
1122 table.addMouseMotionListener(mouseInputListener);
1123 if (propertyChangeListener == null)
1124 propertyChangeListener = new PropertyChangeHandler();
1125 table.addPropertyChangeListener(propertyChangeListener);
1128 protected void uninstallDefaults()
1130 // TODO: this method used to do the following which is not
1131 // quite right (at least it breaks apps that run fine with the
1132 // JDK):
1134 // table.setFont(null);
1135 // table.setGridColor(null);
1136 // table.setForeground(null);
1137 // table.setBackground(null);
1138 // table.setSelectionForeground(null);
1139 // table.setSelectionBackground(null);
1141 // This would leave the component in a corrupt state, which is
1142 // not acceptable. A possible solution would be to have component
1143 // level defaults installed, that get overridden by the UI defaults
1144 // and get restored in this method. I am not quite sure about this
1145 // though. / Roman Kennke
1148 protected void uninstallKeyboardActions()
1150 // TODO: Implement this properly.
1153 protected void uninstallListeners()
1155 table.removeFocusListener(focusListener);
1156 table.removeKeyListener(keyListener);
1157 table.removeMouseListener(mouseInputListener);
1158 table.removeMouseMotionListener(mouseInputListener);
1159 table.removePropertyChangeListener(propertyChangeListener);
1160 propertyChangeListener = null;
1163 public void installUI(JComponent comp)
1165 table = (JTable)comp;
1166 installDefaults();
1167 installKeyboardActions();
1168 installListeners();
1171 public void uninstallUI(JComponent c)
1173 uninstallListeners();
1174 uninstallKeyboardActions();
1175 uninstallDefaults();
1179 * Paints a single cell in the table.
1181 * @param g The graphics context to paint in
1182 * @param row The row number to paint
1183 * @param col The column number to paint
1184 * @param bounds The bounds of the cell to paint, assuming a coordinate
1185 * system beginning at <code>(0,0)</code> in the upper left corner of the
1186 * table
1187 * @param rend A cell renderer to paint with
1188 * @param data The data to provide to the cell renderer
1189 * @param rowLead The lead selection for the rows of the table.
1190 * @param colLead The lead selection for the columns of the table.
1192 void paintCell(Graphics g, int row, int col, Rectangle bounds,
1193 TableCellRenderer rend, TableModel data,
1194 int rowLead, int colLead)
1196 Component comp = table.prepareRenderer(rend, row, col);
1197 rendererPane.paintComponent(g, comp, table, bounds);
1199 // FIXME: this is manual painting of the Caret, why doesn't the
1200 // JTextField take care of this itself?
1201 if (comp instanceof JTextField)
1203 Rectangle oldClip = g.getClipBounds();
1204 g.translate(bounds.x, bounds.y);
1205 g.clipRect(0, 0, bounds.width, bounds.height);
1206 ((JTextField)comp).getCaret().paint(g);
1207 g.translate(-bounds.x, -bounds.y);
1208 g.setClip(oldClip);
1212 public void paint(Graphics gfx, JComponent ignored)
1214 int ncols = table.getColumnCount();
1215 int nrows = table.getRowCount();
1216 if (nrows == 0 || ncols == 0)
1217 return;
1219 Rectangle clip = gfx.getClipBounds();
1220 TableColumnModel cols = table.getColumnModel();
1222 int height = table.getRowHeight();
1223 int x0 = 0, y0 = 0;
1224 int x = x0;
1225 int y = y0;
1227 Dimension gap = table.getIntercellSpacing();
1228 int ymax = clip.y + clip.height;
1229 int xmax = clip.x + clip.width;
1231 // paint the cell contents
1232 for (int c = 0; c < ncols && x < xmax; ++c)
1234 y = y0;
1235 TableColumn col = cols.getColumn(c);
1236 int width = col.getWidth();
1237 int halfGapWidth = gap.width / 2;
1238 int halfGapHeight = gap.height / 2;
1239 for (int r = 0; r < nrows && y < ymax; ++r)
1241 Rectangle bounds = new Rectangle(x + halfGapWidth,
1242 y + halfGapHeight + 1,
1243 width - gap.width + 1,
1244 height - gap.height);
1245 if (bounds.intersects(clip))
1247 paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c),
1248 table.getModel(),
1249 table.getSelectionModel().getLeadSelectionIndex(),
1250 table.getColumnModel().getSelectionModel().getLeadSelectionIndex());
1252 y += height;
1254 x += width;
1257 // tighten up the x and y max bounds
1258 ymax = y;
1259 xmax = x;
1261 Color grid = table.getGridColor();
1263 // paint vertical grid lines
1264 if (grid != null && table.getShowVerticalLines())
1266 x = x0;
1267 Color save = gfx.getColor();
1268 gfx.setColor(grid);
1269 for (int c = 0; c < ncols && x < xmax; ++c)
1271 x += cols.getColumn(c).getWidth();
1272 gfx.drawLine(x, y0, x, ymax);
1274 gfx.setColor(save);
1277 // paint horizontal grid lines
1278 if (grid != null && table.getShowHorizontalLines())
1280 y = y0;
1281 Color save = gfx.getColor();
1282 gfx.setColor(grid);
1283 for (int r = 0; r < nrows && y < ymax; ++r)
1285 y += height;
1286 gfx.drawLine(x0, y, xmax, y);
1288 gfx.setColor(save);