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
.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. */
98 /** The action bound to KeyStrokes. */
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
167 private void updateSelection(boolean controlPressed
)
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
);
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().
187 if (lo_col
!= -1 && hi_col
!= -1)
189 if (controlPressed
&& colModel
.getSelectionMode() !=
190 ListSelectionModel
.SINGLE_SELECTION
)
191 colModel
.addSelectionInterval(lo_col
, hi_col
);
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
));
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())
270 * Listens for changes to the model property of the JTable and adjusts some
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
295 if (model
.getRowCount() > 0)
297 rowSel
.setAnchorSelectionIndex(0);
298 rowSel
.setLeadSelectionIndex(0);
302 rowSel
.setAnchorSelectionIndex(-1);
303 rowSel
.setLeadSelectionIndex(-1);
305 if (model
.getColumnCount() > 0)
307 colSel
.setAnchorSelectionIndex(0);
308 colSel
.setLeadSelectionIndex(0);
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,
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)
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,
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)
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()),
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
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
438 private static class ActionListenerProxy
439 extends AbstractAction
441 ActionListener target
;
442 String bindingCommandName
;
444 public ActionListenerProxy(ActionListener li
,
448 bindingCommandName
= cmd
;
451 public void actionPerformed(ActionEvent e
)
453 ActionEvent derivedEvent
= new ActionEvent(e
.getSource(),
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
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"))
556 if (rowLead
== getFirstVisibleRowIndex())
558 (0, rowLead
- (getLastVisibleRowIndex() -
559 getFirstVisibleRowIndex() + 1));
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"))
575 if (colLead
== getLastVisibleColumnIndex())
577 (colMax
, colLead
+ (getLastVisibleColumnIndex() -
578 getFirstVisibleColumnIndex() + 1));
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"))
594 if (colLead
== getFirstVisibleColumnIndex())
596 (0, colLead
- (getLastVisibleColumnIndex() -
597 getFirstVisibleColumnIndex() + 1));
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);
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
633 if (!table
.isCellSelected(rowLead
, colLead
))
635 rowModel
.addSelectionInterval(rowModel
.getMinSelectionIndex(),
636 rowModel
.getMinSelectionIndex());
637 colModel
.addSelectionInterval(colModel
.getMinSelectionIndex(),
638 colModel
.getMinSelectionIndex());
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
,
658 ("selectPreviousColumnCell")));
660 advanceSingleSelection(rowModel
, rowMax
, colModel
, colMax
,
662 ("selectPreviousRowCell")));
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
,
686 ("selectPreviousColumnCell")), true);
689 advanceMultipleSelection(rowModel
, rowMinSelected
, rowMaxSelected
,
690 colModel
, colMinSelected
, colMaxSelected
,
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"))
703 if (colLead
== getFirstVisibleColumnIndex())
705 (0, colLead
- (getLastVisibleColumnIndex() -
706 getFirstVisibleColumnIndex() + 1));
708 target
= getFirstVisibleColumnIndex();
710 colModel
.setLeadSelectionIndex(target
);
711 rowModel
.setLeadSelectionIndex(rowLead
);
713 else if (command
.equals("scrollDownChangeSelection"))
716 if (rowLead
== getLastVisibleRowIndex())
718 (rowMax
, rowLead
+ (getLastVisibleRowIndex() -
719 getFirstVisibleRowIndex() + 1));
721 target
= getLastVisibleRowIndex();
723 rowModel
.setSelectionInterval(target
, target
);
724 colModel
.setSelectionInterval(colLead
, colLead
);
726 else if (command
.equals("scrollRightExtendSelection"))
729 if (colLead
== getLastVisibleColumnIndex())
731 (colMax
, colLead
+ (getLastVisibleColumnIndex() -
732 getFirstVisibleColumnIndex() + 1));
734 target
= getLastVisibleColumnIndex();
736 colModel
.setLeadSelectionIndex(target
);
737 rowModel
.setLeadSelectionIndex(rowLead
);
739 else if (command
.equals("selectAll"))
743 else if (command
.equals("selectLastRowExtendSelection"))
745 rowModel
.setLeadSelectionIndex(rowMax
);
746 colModel
.setLeadSelectionIndex(colLead
);
748 else if (command
.equals("scrollDownExtendSelection"))
751 if (rowLead
== getLastVisibleRowIndex())
753 (rowMax
, rowLead
+ (getLastVisibleRowIndex() -
754 getFirstVisibleRowIndex() + 1));
756 target
= getLastVisibleRowIndex();
758 rowModel
.setLeadSelectionIndex(target
);
759 colModel
.setLeadSelectionIndex(colLead
);
761 else if (command
.equals("scrollUpChangeSelection"))
764 if (rowLead
== getFirstVisibleRowIndex())
766 (0, rowLead
- (getLastVisibleRowIndex() -
767 getFirstVisibleRowIndex() + 1));
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
);
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
);
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
));
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));
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(),
839 colModel
.setSelectionInterval(colModel
.getAnchorSelectionIndex(),
842 else if (command
.equals("toggleAndAnchor"))
844 if (rowModel
.isSelectedIndex(rowLead
))
845 rowModel
.removeSelectionInterval(rowLead
, rowLead
);
847 rowModel
.addSelectionInterval(rowLead
, rowLead
);
849 if (colModel
.isSelectedIndex(colLead
))
850 colModel
.removeSelectionInterval(colLead
, colLead
);
852 colModel
.addSelectionInterval(colLead
, colLead
);
854 rowModel
.setAnchorSelectionIndex(rowLead
);
855 colModel
.setAnchorSelectionIndex(colLead
);
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));
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)
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
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
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
,
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)
996 int[] secondsSelected
;
997 if (eventIsTab
&& table
.getRowSelectionAllowed() ||
998 !eventIsTab
&& table
.getColumnSelectionAllowed())
999 secondsSelected
= eventIsTab ?
1000 table
.getSelectedRows() : table
.getSelectedColumns();
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;
1013 while (secondsSelected
[secondIndex
] <= secondLead
)
1016 while (secondsSelected
[secondIndex
] >= secondLead
)
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"
1028 int[] firstsSelected
;
1029 if (eventIsTab
&& table
.getColumnSelectionAllowed() ||
1030 !eventIsTab
&& table
.getRowSelectionAllowed())
1031 firstsSelected
= eventIsTab ?
1032 table
.getSelectedColumns() : table
.getSelectedRows();
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;
1042 while (firstsSelected
[firstIndex
] <= firstLead
)
1045 while (firstsSelected
[firstIndex
] >= firstLead
)
1047 firstModel
.addSelectionInterval(firstsSelected
[firstIndex
],
1048 firstsSelected
[firstIndex
]);
1049 secondModel
.addSelectionInterval(secondLead
, secondLead
);
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
1062 * @param firstMax the last index in firstModel
1063 * @param secondModel the ListSelectionModel for rows (TAB) or columns
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
,
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;
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
1094 secondModel
.setSelectionInterval(secondLead
, secondLead
);
1096 // if we are going backwards, subtract 2 because we add 1 later
1097 // for net change of -1
1100 // check for wraparound
1102 firstLead
+= firstMax
+ 1;
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
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
;
1167 installKeyboardActions();
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
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 boolean rowSelAllowed
= table
.getRowSelectionAllowed();
1197 boolean colSelAllowed
= table
.getColumnSelectionAllowed();
1198 boolean isSel
= false;
1199 if (rowSelAllowed
&& colSelAllowed
|| !rowSelAllowed
&& !colSelAllowed
)
1200 isSel
= table
.isCellSelected(row
, col
);
1202 isSel
= table
.isRowSelected(row
) && table
.getRowSelectionAllowed()
1203 || table
.isColumnSelected(col
) && table
.getColumnSelectionAllowed();
1205 // Determine the focused cell. The focused cell is the cell at the
1206 // leadSelectionIndices of the row and column selection model.
1207 ListSelectionModel rowSel
= table
.getSelectionModel();
1208 ListSelectionModel colSel
= table
.getColumnModel().getSelectionModel();
1209 boolean hasFocus
= table
.hasFocus() && table
.isEnabled()
1210 && rowSel
.getLeadSelectionIndex() == row
1211 && colSel
.getLeadSelectionIndex() == col
;
1213 Component comp
= rend
.getTableCellRendererComponent(table
,
1214 data
.getValueAt(row
, col
),
1215 isSel
, hasFocus
, row
, col
);
1217 rendererPane
.paintComponent(g
, comp
, table
, bounds
);
1219 // FIXME: this is manual painting of the Caret, why doesn't the
1220 // JTextField take care of this itself?
1221 if (comp
instanceof JTextField
)
1223 Rectangle oldClip
= g
.getClipBounds();
1224 g
.translate(bounds
.x
, bounds
.y
);
1225 g
.clipRect(0, 0, bounds
.width
, bounds
.height
);
1226 ((JTextField
)comp
).getCaret().paint(g
);
1227 g
.translate(-bounds
.x
, -bounds
.y
);
1232 public void paint(Graphics gfx
, JComponent ignored
)
1234 int ncols
= table
.getColumnCount();
1235 int nrows
= table
.getRowCount();
1236 if (nrows
== 0 || ncols
== 0)
1239 Rectangle clip
= gfx
.getClipBounds();
1240 TableColumnModel cols
= table
.getColumnModel();
1242 int height
= table
.getRowHeight();
1247 Dimension gap
= table
.getIntercellSpacing();
1248 int ymax
= clip
.y
+ clip
.height
;
1249 int xmax
= clip
.x
+ clip
.width
;
1251 // paint the cell contents
1252 for (int c
= 0; c
< ncols
&& x
< xmax
; ++c
)
1255 TableColumn col
= cols
.getColumn(c
);
1256 int width
= col
.getWidth();
1257 int halfGapWidth
= gap
.width
/ 2;
1258 int halfGapHeight
= gap
.height
/ 2;
1259 for (int r
= 0; r
< nrows
&& y
< ymax
; ++r
)
1261 Rectangle bounds
= new Rectangle(x
+ halfGapWidth
,
1262 y
+ halfGapHeight
+ 1,
1263 width
- gap
.width
+ 1,
1264 height
- gap
.height
);
1265 if (bounds
.intersects(clip
))
1267 paintCell(gfx
, r
, c
, bounds
, table
.getCellRenderer(r
, c
),
1269 table
.getSelectionModel().getLeadSelectionIndex(),
1270 table
.getColumnModel().getSelectionModel().getLeadSelectionIndex());
1277 // tighten up the x and y max bounds
1281 Color grid
= table
.getGridColor();
1283 // paint vertical grid lines
1284 if (grid
!= null && table
.getShowVerticalLines())
1287 Color save
= gfx
.getColor();
1289 boolean paintedLine
= false;
1290 for (int c
= 0; c
< ncols
&& x
< xmax
; ++c
)
1292 x
+= cols
.getColumn(c
).getWidth();
1293 gfx
.drawLine(x
, y0
, x
, ymax
);
1299 // paint horizontal grid lines
1300 if (grid
!= null && table
.getShowHorizontalLines())
1303 Color save
= gfx
.getColor();
1305 boolean paintedLine
= false;
1306 for (int r
= 0; r
< nrows
&& y
< ymax
; ++r
)
1309 gfx
.drawLine(x0
, y
, xmax
, y
);