1 /*
2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package javax.swing.plaf.basic;
27
28 import java.awt.*;
29 import java.awt.datatransfer.*;
30 import java.awt.dnd.*;
31 import java.awt.event.*;
32 import java.util.Enumeration;
33 import java.util.EventObject;
34 import java.util.Hashtable;
35 import java.util.TooManyListenersException;
36 import javax.swing.*;
37 import javax.swing.event.*;
38 import javax.swing.plaf.*;
39 import javax.swing.text.*;
40 import javax.swing.table.*;
41 import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag;
42 import sun.swing.SwingUtilities2;
43
44
45 import java.beans.PropertyChangeEvent;
46 import java.beans.PropertyChangeListener;
47
48 import sun.swing.DefaultLookup;
49 import sun.swing.UIAction;
50
51 /**
52 * BasicTableUI implementation
53 *
54 * @author Philip Milne
55 * @author Shannon Hickey (drag and drop)
56 */
57 public class BasicTableUI extends TableUI
58 {
59 private static final StringBuilder BASELINE_COMPONENT_KEY =
60 new StringBuilder("Table.baselineComponent");
61
62 //
63 // Instance Variables
64 //
65
66 // The JTable that is delegating the painting to this UI.
67 /**
68 * The instance of {@code JTable}.
69 */
70 protected JTable table;
71
72 /**
73 * The instance of {@code CellRendererPane}.
74 */
75 protected CellRendererPane rendererPane;
76
77 /**
78 * {@code KeyListener} that are attached to the {@code JTable}.
79 */
80 protected KeyListener keyListener;
81
82 /**
83 * {@code FocusListener} that are attached to the {@code JTable}.
84 */
85 protected FocusListener focusListener;
86
87 /**
88 * {@code MouseInputListener} that are attached to the {@code JTable}.
89 */
90 protected MouseInputListener mouseInputListener;
91
92 private Handler handler;
93
94 /**
95 * Local cache of Table's client property "Table.isFileList"
96 */
97 private boolean isFileList = false;
98
99 //
100 // Helper class for keyboard actions
101 //
102
103 private static class Actions extends UIAction {
104 private static final String CANCEL_EDITING = "cancel";
105 private static final String SELECT_ALL = "selectAll";
106 private static final String CLEAR_SELECTION = "clearSelection";
107 private static final String START_EDITING = "startEditing";
108
109 private static final String NEXT_ROW = "selectNextRow";
110 private static final String NEXT_ROW_CELL = "selectNextRowCell";
111 private static final String NEXT_ROW_EXTEND_SELECTION =
112 "selectNextRowExtendSelection";
113 private static final String NEXT_ROW_CHANGE_LEAD =
114 "selectNextRowChangeLead";
115 private static final String PREVIOUS_ROW = "selectPreviousRow";
116 private static final String PREVIOUS_ROW_CELL = "selectPreviousRowCell";
117 private static final String PREVIOUS_ROW_EXTEND_SELECTION =
118 "selectPreviousRowExtendSelection";
119 private static final String PREVIOUS_ROW_CHANGE_LEAD =
120 "selectPreviousRowChangeLead";
121
122 private static final String NEXT_COLUMN = "selectNextColumn";
123 private static final String NEXT_COLUMN_CELL = "selectNextColumnCell";
124 private static final String NEXT_COLUMN_EXTEND_SELECTION =
125 "selectNextColumnExtendSelection";
126 private static final String NEXT_COLUMN_CHANGE_LEAD =
127 "selectNextColumnChangeLead";
128 private static final String PREVIOUS_COLUMN = "selectPreviousColumn";
129 private static final String PREVIOUS_COLUMN_CELL =
130 "selectPreviousColumnCell";
131 private static final String PREVIOUS_COLUMN_EXTEND_SELECTION =
132 "selectPreviousColumnExtendSelection";
133 private static final String PREVIOUS_COLUMN_CHANGE_LEAD =
134 "selectPreviousColumnChangeLead";
135
136 private static final String SCROLL_LEFT_CHANGE_SELECTION =
137 "scrollLeftChangeSelection";
138 private static final String SCROLL_LEFT_EXTEND_SELECTION =
139 "scrollLeftExtendSelection";
140 private static final String SCROLL_RIGHT_CHANGE_SELECTION =
141 "scrollRightChangeSelection";
142 private static final String SCROLL_RIGHT_EXTEND_SELECTION =
143 "scrollRightExtendSelection";
144
145 private static final String SCROLL_UP_CHANGE_SELECTION =
146 "scrollUpChangeSelection";
147 private static final String SCROLL_UP_EXTEND_SELECTION =
148 "scrollUpExtendSelection";
149 private static final String SCROLL_DOWN_CHANGE_SELECTION =
150 "scrollDownChangeSelection";
151 private static final String SCROLL_DOWN_EXTEND_SELECTION =
152 "scrollDownExtendSelection";
153
154 private static final String FIRST_COLUMN =
155 "selectFirstColumn";
156 private static final String FIRST_COLUMN_EXTEND_SELECTION =
157 "selectFirstColumnExtendSelection";
158 private static final String LAST_COLUMN =
159 "selectLastColumn";
160 private static final String LAST_COLUMN_EXTEND_SELECTION =
161 "selectLastColumnExtendSelection";
162
163 private static final String FIRST_ROW =
164 "selectFirstRow";
165 private static final String FIRST_ROW_EXTEND_SELECTION =
166 "selectFirstRowExtendSelection";
167 private static final String LAST_ROW =
168 "selectLastRow";
169 private static final String LAST_ROW_EXTEND_SELECTION =
170 "selectLastRowExtendSelection";
171
172 // add the lead item to the selection without changing lead or anchor
173 private static final String ADD_TO_SELECTION = "addToSelection";
174
175 // toggle the selected state of the lead item and move the anchor to it
176 private static final String TOGGLE_AND_ANCHOR = "toggleAndAnchor";
177
178 // extend the selection to the lead item
179 private static final String EXTEND_TO = "extendTo";
180
181 // move the anchor to the lead and ensure only that item is selected
182 private static final String MOVE_SELECTION_TO = "moveSelectionTo";
183
184 // give focus to the JTableHeader, if one exists
185 private static final String FOCUS_HEADER = "focusHeader";
186
187 protected int dx;
188 protected int dy;
189 protected boolean extend;
190 protected boolean inSelection;
191
192 // horizontally, forwards always means right,
193 // regardless of component orientation
194 protected boolean forwards;
195 protected boolean vertically;
196 protected boolean toLimit;
197
198 protected int leadRow;
199 protected int leadColumn;
200
201 Actions(String name) {
202 super(name);
203 }
204
205 Actions(String name, int dx, int dy, boolean extend,
206 boolean inSelection) {
207 super(name);
208
209 // Actions spcifying true for "inSelection" are
210 // fairly sensitive to bad parameter values. They require
211 // that one of dx and dy be 0 and the other be -1 or 1.
212 // Bogus parameter values could cause an infinite loop.
213 // To prevent any problems we massage the params here
214 // and complain if we get something we can't deal with.
215 if (inSelection) {
216 this.inSelection = true;
217
218 // look at the sign of dx and dy only
219 dx = sign(dx);
220 dy = sign(dy);
221
222 // make sure one is zero, but not both
223 assert (dx == 0 || dy == 0) && !(dx == 0 && dy == 0);
224 }
225
226 this.dx = dx;
227 this.dy = dy;
228 this.extend = extend;
229 }
230
231 Actions(String name, boolean extend, boolean forwards,
232 boolean vertically, boolean toLimit) {
233 this(name, 0, 0, extend, false);
234 this.forwards = forwards;
235 this.vertically = vertically;
236 this.toLimit = toLimit;
237 }
238
239 private static int clipToRange(int i, int a, int b) {
240 return Math.min(Math.max(i, a), b-1);
241 }
242
243 private void moveWithinTableRange(JTable table, int dx, int dy) {
244 leadRow = clipToRange(leadRow+dy, 0, table.getRowCount());
245 leadColumn = clipToRange(leadColumn+dx, 0, table.getColumnCount());
246 }
247
248 private static int sign(int num) {
249 return (num < 0) ? -1 : ((num == 0) ? 0 : 1);
250 }
251
252 /**
253 * Called to move within the selected range of the given JTable.
254 * This method uses the table's notion of selection, which is
255 * important to allow the user to navigate between items visually
256 * selected on screen. This notion may or may not be the same as
257 * what could be determined by directly querying the selection models.
258 * It depends on certain table properties (such as whether or not
259 * row or column selection is allowed). When performing modifications,
260 * it is recommended that caution be taken in order to preserve
261 * the intent of this method, especially when deciding whether to
262 * query the selection models or interact with JTable directly.
263 */
264 private boolean moveWithinSelectedRange(JTable table, int dx, int dy,
265 ListSelectionModel rsm, ListSelectionModel csm) {
266
267 // Note: The Actions constructor ensures that only one of
268 // dx and dy is 0, and the other is either -1 or 1
269
270 // find out how many items the table is showing as selected
271 // and the range of items to navigate through
272 int totalCount;
273 int minX, maxX, minY, maxY;
274
275 boolean rs = table.getRowSelectionAllowed();
276 boolean cs = table.getColumnSelectionAllowed();
277
278 // both column and row selection
279 if (rs && cs) {
280 totalCount = table.getSelectedRowCount() * table.getSelectedColumnCount();
281 minX = csm.getMinSelectionIndex();
282 maxX = csm.getMaxSelectionIndex();
283 minY = rsm.getMinSelectionIndex();
284 maxY = rsm.getMaxSelectionIndex();
285 // row selection only
286 } else if (rs) {
287 totalCount = table.getSelectedRowCount();
288 minX = 0;
289 maxX = table.getColumnCount() - 1;
290 minY = rsm.getMinSelectionIndex();
291 maxY = rsm.getMaxSelectionIndex();
292 // column selection only
293 } else if (cs) {
294 totalCount = table.getSelectedColumnCount();
295 minX = csm.getMinSelectionIndex();
296 maxX = csm.getMaxSelectionIndex();
297 minY = 0;
298 maxY = table.getRowCount() - 1;
299 // no selection allowed
300 } else {
301 totalCount = 0;
302 // A bogus assignment to stop javac from complaining
303 // about unitialized values. In this case, these
304 // won't even be used.
305 minX = maxX = minY = maxY = 0;
306 }
307
308 // For some cases, there is no point in trying to stay within the
309 // selected area. Instead, move outside the selection, wrapping at
310 // the table boundaries. The cases are:
311 boolean stayInSelection;
312
313 // - nothing selected
314 if (totalCount == 0 ||
315 // - one item selected, and the lead is already selected
316 (totalCount == 1 && table.isCellSelected(leadRow, leadColumn))) {
317
318 stayInSelection = false;
319
320 maxX = table.getColumnCount() - 1;
321 maxY = table.getRowCount() - 1;
322
323 // the mins are calculated like this in case the max is -1
324 minX = Math.min(0, maxX);
325 minY = Math.min(0, maxY);
326 } else {
327 stayInSelection = true;
328 }
329
330 // the algorithm below isn't prepared to deal with -1 lead/anchor
331 // so massage appropriately here first
332 if (dy == 1 && leadColumn == -1) {
333 leadColumn = minX;
334 leadRow = -1;
335 } else if (dx == 1 && leadRow == -1) {
336 leadRow = minY;
337 leadColumn = -1;
338 } else if (dy == -1 && leadColumn == -1) {
339 leadColumn = maxX;
340 leadRow = maxY + 1;
341 } else if (dx == -1 && leadRow == -1) {
342 leadRow = maxY;
343 leadColumn = maxX + 1;
344 }
345
346 // In cases where the lead is not within the search range,
347 // we need to bring it within one cell for the search
348 // to work properly. Check these here.
349 leadRow = Math.min(Math.max(leadRow, minY - 1), maxY + 1);
350 leadColumn = Math.min(Math.max(leadColumn, minX - 1), maxX + 1);
351
352 // find the next position, possibly looping until it is selected
353 do {
354 calcNextPos(dx, minX, maxX, dy, minY, maxY);
355 } while (stayInSelection && !table.isCellSelected(leadRow, leadColumn));
356
357 return stayInSelection;
358 }
359
360 /**
361 * Find the next lead row and column based on the given
362 * dx/dy and max/min values.
363 */
364 private void calcNextPos(int dx, int minX, int maxX,
365 int dy, int minY, int maxY) {
366
367 if (dx != 0) {
368 leadColumn += dx;
369 if (leadColumn > maxX) {
370 leadColumn = minX;
371 leadRow++;
372 if (leadRow > maxY) {
373 leadRow = minY;
374 }
375 } else if (leadColumn < minX) {
376 leadColumn = maxX;
377 leadRow--;
378 if (leadRow < minY) {
379 leadRow = maxY;
380 }
381 }
382 } else {
383 leadRow += dy;
384 if (leadRow > maxY) {
385 leadRow = minY;
386 leadColumn++;
387 if (leadColumn > maxX) {
388 leadColumn = minX;
389 }
390 } else if (leadRow < minY) {
391 leadRow = maxY;
392 leadColumn--;
393 if (leadColumn < minX) {
394 leadColumn = maxX;
395 }
396 }
397 }
398 }
399
400 public void actionPerformed(ActionEvent e) {
401 String key = getName();
402 JTable table = (JTable)e.getSource();
403
404 ListSelectionModel rsm = table.getSelectionModel();
405 leadRow = getAdjustedLead(table, true, rsm);
406
407 ListSelectionModel csm = table.getColumnModel().getSelectionModel();
408 leadColumn = getAdjustedLead(table, false, csm);
409
410 if (key == SCROLL_LEFT_CHANGE_SELECTION || // Paging Actions
411 key == SCROLL_LEFT_EXTEND_SELECTION ||
412 key == SCROLL_RIGHT_CHANGE_SELECTION ||
413 key == SCROLL_RIGHT_EXTEND_SELECTION ||
414 key == SCROLL_UP_CHANGE_SELECTION ||
415 key == SCROLL_UP_EXTEND_SELECTION ||
416 key == SCROLL_DOWN_CHANGE_SELECTION ||
417 key == SCROLL_DOWN_EXTEND_SELECTION ||
418 key == FIRST_COLUMN ||
419 key == FIRST_COLUMN_EXTEND_SELECTION ||
420 key == FIRST_ROW ||
421 key == FIRST_ROW_EXTEND_SELECTION ||
422 key == LAST_COLUMN ||
423 key == LAST_COLUMN_EXTEND_SELECTION ||
424 key == LAST_ROW ||
425 key == LAST_ROW_EXTEND_SELECTION) {
426 if (toLimit) {
427 if (vertically) {
428 int rowCount = table.getRowCount();
429 this.dx = 0;
430 this.dy = forwards ? rowCount : -rowCount;
431 }
432 else {
433 int colCount = table.getColumnCount();
434 this.dx = forwards ? colCount : -colCount;
435 this.dy = 0;
436 }
437 }
438 else {
439 if (!(SwingUtilities.getUnwrappedParent(table).getParent() instanceof
440 JScrollPane)) {
441 return;
442 }
443
444 Dimension delta = table.getParent().getSize();
445
446 if (vertically) {
447 Rectangle r = table.getCellRect(leadRow, 0, true);
448 if (forwards) {
449 // scroll by at least one cell
450 r.y += Math.max(delta.height, r.height);
451 } else {
452 r.y -= delta.height;
453 }
454
455 this.dx = 0;
456 int newRow = table.rowAtPoint(r.getLocation());
457 if (newRow == -1 && forwards) {
458 newRow = table.getRowCount();
459 }
460 this.dy = newRow - leadRow;
461 }
462 else {
463 Rectangle r = table.getCellRect(0, leadColumn, true);
464
465 if (forwards) {
466 // scroll by at least one cell
467 r.x += Math.max(delta.width, r.width);
468 } else {
469 r.x -= delta.width;
470 }
471
472 int newColumn = table.columnAtPoint(r.getLocation());
473 if (newColumn == -1) {
474 boolean ltr = table.getComponentOrientation().isLeftToRight();
475
476 newColumn = forwards ? (ltr ? table.getColumnCount() : 0)
477 : (ltr ? 0 : table.getColumnCount());
478
479 }
480 this.dx = newColumn - leadColumn;
481 this.dy = 0;
482 }
483 }
484 }
485 if (key == NEXT_ROW || // Navigate Actions
486 key == NEXT_ROW_CELL ||
487 key == NEXT_ROW_EXTEND_SELECTION ||
488 key == NEXT_ROW_CHANGE_LEAD ||
489 key == NEXT_COLUMN ||
490 key == NEXT_COLUMN_CELL ||
491 key == NEXT_COLUMN_EXTEND_SELECTION ||
492 key == NEXT_COLUMN_CHANGE_LEAD ||
493 key == PREVIOUS_ROW ||
494 key == PREVIOUS_ROW_CELL ||
495 key == PREVIOUS_ROW_EXTEND_SELECTION ||
496 key == PREVIOUS_ROW_CHANGE_LEAD ||
497 key == PREVIOUS_COLUMN ||
498 key == PREVIOUS_COLUMN_CELL ||
499 key == PREVIOUS_COLUMN_EXTEND_SELECTION ||
500 key == PREVIOUS_COLUMN_CHANGE_LEAD ||
501 // Paging Actions.
502 key == SCROLL_LEFT_CHANGE_SELECTION ||
503 key == SCROLL_LEFT_EXTEND_SELECTION ||
504 key == SCROLL_RIGHT_CHANGE_SELECTION ||
505 key == SCROLL_RIGHT_EXTEND_SELECTION ||
506 key == SCROLL_UP_CHANGE_SELECTION ||
507 key == SCROLL_UP_EXTEND_SELECTION ||
508 key == SCROLL_DOWN_CHANGE_SELECTION ||
509 key == SCROLL_DOWN_EXTEND_SELECTION ||
510 key == FIRST_COLUMN ||
511 key == FIRST_COLUMN_EXTEND_SELECTION ||
512 key == FIRST_ROW ||
513 key == FIRST_ROW_EXTEND_SELECTION ||
514 key == LAST_COLUMN ||
515 key == LAST_COLUMN_EXTEND_SELECTION ||
516 key == LAST_ROW ||
517 key == LAST_ROW_EXTEND_SELECTION) {
518
519 if (table.isEditing() &&
520 !table.getCellEditor().stopCellEditing()) {
521 return;
522 }
523
524 // Unfortunately, this strategy introduces bugs because
525 // of the asynchronous nature of requestFocus() call below.
526 // Introducing a delay with invokeLater() makes this work
527 // in the typical case though race conditions then allow
528 // focus to disappear altogether. The right solution appears
529 // to be to fix requestFocus() so that it queues a request
530 // for the focus regardless of who owns the focus at the
531 // time the call to requestFocus() is made. The optimisation
532 // to ignore the call to requestFocus() when the component
533 // already has focus may ligitimately be made as the
534 // request focus event is dequeued, not before.
535
536 // boolean wasEditingWithFocus = table.isEditing() &&
537 // table.getEditorComponent().isFocusOwner();
538
539 boolean changeLead = false;
540 if (key == NEXT_ROW_CHANGE_LEAD || key == PREVIOUS_ROW_CHANGE_LEAD) {
541 changeLead = (rsm.getSelectionMode()
542 == ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
543 } else if (key == NEXT_COLUMN_CHANGE_LEAD || key == PREVIOUS_COLUMN_CHANGE_LEAD) {
544 changeLead = (csm.getSelectionMode()
545 == ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
546 }
547
548 if (changeLead) {
549 moveWithinTableRange(table, dx, dy);
550 if (dy != 0) {
551 // casting should be safe since the action is only enabled
552 // for DefaultListSelectionModel
553 ((DefaultListSelectionModel)rsm).moveLeadSelectionIndex(leadRow);
554 if (getAdjustedLead(table, false, csm) == -1
555 && table.getColumnCount() > 0) {
556
557 ((DefaultListSelectionModel)csm).moveLeadSelectionIndex(0);
558 }
559 } else {
560 // casting should be safe since the action is only enabled
561 // for DefaultListSelectionModel
562 ((DefaultListSelectionModel)csm).moveLeadSelectionIndex(leadColumn);
563 if (getAdjustedLead(table, true, rsm) == -1
564 && table.getRowCount() > 0) {
565
566 ((DefaultListSelectionModel)rsm).moveLeadSelectionIndex(0);
567 }
568 }
569
570 Rectangle cellRect = table.getCellRect(leadRow, leadColumn, false);
571 if (cellRect != null) {
572 table.scrollRectToVisible(cellRect);
573 }
574 } else if (!inSelection) {
575 moveWithinTableRange(table, dx, dy);
576 table.changeSelection(leadRow, leadColumn, false, extend);
577 }
578 else {
579 if (table.getRowCount() <= 0 || table.getColumnCount() <= 0) {
580 // bail - don't try to move selection on an empty table
581 return;
582 }
583
584 if (moveWithinSelectedRange(table, dx, dy, rsm, csm)) {
585 // this is the only way we have to set both the lead
586 // and the anchor without changing the selection
587 if (rsm.isSelectedIndex(leadRow)) {
588 rsm.addSelectionInterval(leadRow, leadRow);
589 } else {
590 rsm.removeSelectionInterval(leadRow, leadRow);
591 }
592
593 if (csm.isSelectedIndex(leadColumn)) {
594 csm.addSelectionInterval(leadColumn, leadColumn);
595 } else {
596 csm.removeSelectionInterval(leadColumn, leadColumn);
597 }
598
599 Rectangle cellRect = table.getCellRect(leadRow, leadColumn, false);
600 if (cellRect != null) {
601 table.scrollRectToVisible(cellRect);
602 }
603 }
604 else {
605 table.changeSelection(leadRow, leadColumn,
606 false, false);
607 }
608 }
609
610 /*
611 if (wasEditingWithFocus) {
612 table.editCellAt(leadRow, leadColumn);
613 final Component editorComp = table.getEditorComponent();
614 if (editorComp != null) {
615 SwingUtilities.invokeLater(new Runnable() {
616 public void run() {
617 editorComp.requestFocus();
618 }
619 });
620 }
621 }
622 */
623 } else if (key == CANCEL_EDITING) {
624 table.removeEditor();
625 } else if (key == SELECT_ALL) {
626 table.selectAll();
627 } else if (key == CLEAR_SELECTION) {
628 table.clearSelection();
629 } else if (key == START_EDITING) {
630 if (!table.hasFocus()) {
631 CellEditor cellEditor = table.getCellEditor();
632 if (cellEditor != null && !cellEditor.stopCellEditing()) {
633 return;
634 }
635 table.requestFocus();
636 return;
637 }
638 table.editCellAt(leadRow, leadColumn, e);
639 Component editorComp = table.getEditorComponent();
640 if (editorComp != null) {
641 editorComp.requestFocus();
642 }
643 } else if (key == ADD_TO_SELECTION) {
644 if (!table.isCellSelected(leadRow, leadColumn)) {
645 int oldAnchorRow = rsm.getAnchorSelectionIndex();
646 int oldAnchorColumn = csm.getAnchorSelectionIndex();
647 rsm.setValueIsAdjusting(true);
648 csm.setValueIsAdjusting(true);
649 table.changeSelection(leadRow, leadColumn, true, false);
650 rsm.setAnchorSelectionIndex(oldAnchorRow);
651 csm.setAnchorSelectionIndex(oldAnchorColumn);
652 rsm.setValueIsAdjusting(false);
653 csm.setValueIsAdjusting(false);
654 }
655 } else if (key == TOGGLE_AND_ANCHOR) {
656 table.changeSelection(leadRow, leadColumn, true, false);
657 } else if (key == EXTEND_TO) {
658 table.changeSelection(leadRow, leadColumn, false, true);
659 } else if (key == MOVE_SELECTION_TO) {
660 table.changeSelection(leadRow, leadColumn, false, false);
661 } else if (key == FOCUS_HEADER) {
662 JTableHeader th = table.getTableHeader();
663 if (th != null) {
664 //Set the header's selected column to match the table.
665 int col = table.getSelectedColumn();
666 if (col >= 0) {
667 TableHeaderUI thUI = th.getUI();
668 if (thUI instanceof BasicTableHeaderUI) {
669 ((BasicTableHeaderUI)thUI).selectColumn(col);
670 }
671 }
672
673 //Then give the header the focus.
674 th.requestFocusInWindow();
675 }
676 }
677 }
678
679 @Override
680 public boolean accept(Object sender) {
681 String key = getName();
682
683 if (sender instanceof JTable &&
684 Boolean.TRUE.equals(((JTable)sender).getClientProperty("Table.isFileList"))) {
685 if (key == NEXT_COLUMN ||
686 key == NEXT_COLUMN_CELL ||
687 key == NEXT_COLUMN_EXTEND_SELECTION ||
688 key == NEXT_COLUMN_CHANGE_LEAD ||
689 key == PREVIOUS_COLUMN ||
690 key == PREVIOUS_COLUMN_CELL ||
691 key == PREVIOUS_COLUMN_EXTEND_SELECTION ||
692 key == PREVIOUS_COLUMN_CHANGE_LEAD ||
693 key == SCROLL_LEFT_CHANGE_SELECTION ||
694 key == SCROLL_LEFT_EXTEND_SELECTION ||
695 key == SCROLL_RIGHT_CHANGE_SELECTION ||
696 key == SCROLL_RIGHT_EXTEND_SELECTION ||
697 key == FIRST_COLUMN ||
698 key == FIRST_COLUMN_EXTEND_SELECTION ||
699 key == LAST_COLUMN ||
700 key == LAST_COLUMN_EXTEND_SELECTION ||
701 key == NEXT_ROW_CELL ||
702 key == PREVIOUS_ROW_CELL) {
703
704 return false;
705 }
706 }
707
708 if (key == CANCEL_EDITING && sender instanceof JTable) {
709 return ((JTable)sender).isEditing();
710 } else if (key == NEXT_ROW_CHANGE_LEAD ||
711 key == PREVIOUS_ROW_CHANGE_LEAD) {
712 // discontinuous selection actions are only enabled for
713 // DefaultListSelectionModel
714 return sender != null &&
715 ((JTable)sender).getSelectionModel()
716 instanceof DefaultListSelectionModel;
717 } else if (key == NEXT_COLUMN_CHANGE_LEAD ||
718 key == PREVIOUS_COLUMN_CHANGE_LEAD) {
719 // discontinuous selection actions are only enabled for
720 // DefaultListSelectionModel
721 return sender != null &&
722 ((JTable)sender).getColumnModel().getSelectionModel()
723 instanceof DefaultListSelectionModel;
724 } else if (key == ADD_TO_SELECTION && sender instanceof JTable) {
725 // This action is typically bound to SPACE.
726 // If the table is already in an editing mode, SPACE should
727 // simply enter a space character into the table, and not
728 // select a cell. Likewise, if the lead cell is already selected
729 // then hitting SPACE should just enter a space character
730 // into the cell and begin editing. In both of these cases
731 // this action will be disabled.
732 JTable table = (JTable)sender;
733 int leadRow = getAdjustedLead(table, true);
734 int leadCol = getAdjustedLead(table, false);
735 return !(table.isEditing() || table.isCellSelected(leadRow, leadCol));
736 } else if (key == FOCUS_HEADER && sender instanceof JTable) {
737 JTable table = (JTable)sender;
738 return table.getTableHeader() != null;
739 }
740
741 return true;
742 }
743 }
744
745
746 //
747 // The Table's Key listener
748 //
749
750 /**
751 * This class should be treated as a "protected" inner class.
752 * Instantiate it only within subclasses of {@code BasicTableUI}.
753 * <p>As of Java 2 platform v1.3 this class is no longer used.
754 * Instead <code>JTable</code>
755 * overrides <code>processKeyBinding</code> to dispatch the event to
756 * the current <code>TableCellEditor</code>.
757 */
758 public class KeyHandler implements KeyListener {
759 // NOTE: This class exists only for backward compatibility. All
760 // its functionality has been moved into Handler. If you need to add
761 // new functionality add it to the Handler, but make sure this
762 // class calls into the Handler.
763 public void keyPressed(KeyEvent e) {
764 getHandler().keyPressed(e);
765 }
766
767 public void keyReleased(KeyEvent e) {
768 getHandler().keyReleased(e);
769 }
770
771 public void keyTyped(KeyEvent e) {
772 getHandler().keyTyped(e);
773 }
774 }
775
776 //
777 // The Table's focus listener
778 //
779
780 /**
781 * This class should be treated as a "protected" inner class.
782 * Instantiate it only within subclasses of {@code BasicTableUI}.
783 */
784 public class FocusHandler implements FocusListener {
785 // NOTE: This class exists only for backward compatibility. All
786 // its functionality has been moved into Handler. If you need to add
787 // new functionality add it to the Handler, but make sure this
788 // class calls into the Handler.
789 public void focusGained(FocusEvent e) {
790 getHandler().focusGained(e);
791 }
792
793 public void focusLost(FocusEvent e) {
794 getHandler().focusLost(e);
795 }
796 }
797
798 //
799 // The Table's mouse and mouse motion listeners
800 //
801
802 /**
803 * This class should be treated as a "protected" inner class.
804 * Instantiate it only within subclasses of BasicTableUI.
805 */
806 public class MouseInputHandler implements MouseInputListener {
807 // NOTE: This class exists only for backward compatibility. All
808 // its functionality has been moved into Handler. If you need to add
809 // new functionality add it to the Handler, but make sure this
810 // class calls into the Handler.
811 public void mouseClicked(MouseEvent e) {
812 getHandler().mouseClicked(e);
813 }
814
815 public void mousePressed(MouseEvent e) {
816 getHandler().mousePressed(e);
817 }
818
819 public void mouseReleased(MouseEvent e) {
820 getHandler().mouseReleased(e);
821 }
822
823 public void mouseEntered(MouseEvent e) {
824 getHandler().mouseEntered(e);
825 }
826
827 public void mouseExited(MouseEvent e) {
828 getHandler().mouseExited(e);
829 }
830
831 public void mouseMoved(MouseEvent e) {
832 getHandler().mouseMoved(e);
833 }
834
835 public void mouseDragged(MouseEvent e) {
836 getHandler().mouseDragged(e);
837 }
838 }
839
840 private class Handler implements FocusListener, MouseInputListener,
841 PropertyChangeListener, ListSelectionListener, ActionListener,
842 BeforeDrag {
843
844 // FocusListener
845 private void repaintLeadCell( ) {
846 int lr = getAdjustedLead(table, true);
847 int lc = getAdjustedLead(table, false);
848
849 if (lr < 0 || lc < 0) {
850 return;
851 }
852
853 Rectangle dirtyRect = table.getCellRect(lr, lc, false);
854 table.repaint(dirtyRect);
855 }
856
857 public void focusGained(FocusEvent e) {
858 repaintLeadCell();
859 }
860
861 public void focusLost(FocusEvent e) {
862 repaintLeadCell();
863 }
864
865
866 // KeyListener
867 public void keyPressed(KeyEvent e) { }
868
869 public void keyReleased(KeyEvent e) { }
870
871 public void keyTyped(KeyEvent e) {
872 KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyChar(),
873 e.getModifiers());
874
875 // We register all actions using ANCESTOR_OF_FOCUSED_COMPONENT
876 // which means that we might perform the appropriate action
877 // in the table and then forward it to the editor if the editor
878 // had focus. Make sure this doesn't happen by checking our
879 // InputMaps.
880 InputMap map = table.getInputMap(JComponent.WHEN_FOCUSED);
881 if (map != null && map.get(keyStroke) != null) {
882 return;
883 }
884 map = table.getInputMap(JComponent.
885 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
886 if (map != null && map.get(keyStroke) != null) {
887 return;
888 }
889
890 keyStroke = KeyStroke.getKeyStrokeForEvent(e);
891
892 // The AWT seems to generate an unconsumed \r event when
893 // ENTER (\n) is pressed.
894 if (e.getKeyChar() == '\r') {
895 return;
896 }
897
898 int leadRow = getAdjustedLead(table, true);
899 int leadColumn = getAdjustedLead(table, false);
900 if (leadRow != -1 && leadColumn != -1 && !table.isEditing()) {
901 if (!table.editCellAt(leadRow, leadColumn)) {
902 return;
903 }
904 }
905
906 // Forwarding events this way seems to put the component
907 // in a state where it believes it has focus. In reality
908 // the table retains focus - though it is difficult for
909 // a user to tell, since the caret is visible and flashing.
910
911 // Calling table.requestFocus() here, to get the focus back to
912 // the table, seems to have no effect.
913
914 Component editorComp = table.getEditorComponent();
915 if (table.isEditing() && editorComp != null) {
916 if (editorComp instanceof JComponent) {
917 JComponent component = (JComponent)editorComp;
918 map = component.getInputMap(JComponent.WHEN_FOCUSED);
919 Object binding = (map != null) ? map.get(keyStroke) : null;
920 if (binding == null) {
921 map = component.getInputMap(JComponent.
922 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
923 binding = (map != null) ? map.get(keyStroke) : null;
924 }
925 if (binding != null) {
926 ActionMap am = component.getActionMap();
927 Action action = (am != null) ? am.get(binding) : null;
928 if (action != null && SwingUtilities.
929 notifyAction(action, keyStroke, e, component,
930 e.getModifiers())) {
931 e.consume();
932 }
933 }
934 }
935 }
936 }
937
938
939 // MouseInputListener
940
941 // Component receiving mouse events during editing.
942 // May not be editorComponent.
943 private Component dispatchComponent;
944
945 public void mouseClicked(MouseEvent e) {}
946
947 private void setDispatchComponent(MouseEvent e) {
948 Component editorComponent = table.getEditorComponent();
949 Point p = e.getPoint();
950 Point p2 = SwingUtilities.convertPoint(table, p, editorComponent);
951 dispatchComponent =
952 SwingUtilities.getDeepestComponentAt(editorComponent,
953 p2.x, p2.y);
954 SwingUtilities2.setSkipClickCount(dispatchComponent,
955 e.getClickCount() - 1);
956 }
957
958 private boolean repostEvent(MouseEvent e) {
959 // Check for isEditing() in case another event has
960 // caused the editor to be removed. See bug #4306499.
961 if (dispatchComponent == null || !table.isEditing()) {
962 return false;
963 }
964 MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e,
965 dispatchComponent);
966 dispatchComponent.dispatchEvent(e2);
967 return true;
968 }
969
970 private void setValueIsAdjusting(boolean flag) {
971 table.getSelectionModel().setValueIsAdjusting(flag);
972 table.getColumnModel().getSelectionModel().
973 setValueIsAdjusting(flag);
974 }
975
976 // The row and column where the press occurred and the
977 // press event itself
978 private int pressedRow;
979 private int pressedCol;
980 private MouseEvent pressedEvent;
981
982 // Whether or not the mouse press (which is being considered as part
983 // of a drag sequence) also caused the selection change to be fully
984 // processed.
985 private boolean dragPressDidSelection;
986
987 // Set to true when a drag gesture has been fully recognized and DnD
988 // begins. Use this to ignore further mouse events which could be
989 // delivered if DnD is cancelled (via ESCAPE for example)
990 private boolean dragStarted;
991
992 // Whether or not we should start the editing timer on release
993 private boolean shouldStartTimer;
994
995 // To cache the return value of pointOutsidePrefSize since we use
996 // it multiple times.
997 private boolean outsidePrefSize;
998
999 // Used to delay the start of editing.
1000 private Timer timer = null;
1001
1002 private boolean canStartDrag() {
1003 if (pressedRow == -1 || pressedCol == -1) {
1004 return false;
1005 }
1006
1007 if (isFileList) {
1008 return !outsidePrefSize;
1009 }
1010
1011 // if this is a single selection table
1012 if ((table.getSelectionModel().getSelectionMode() ==
1013 ListSelectionModel.SINGLE_SELECTION) &&
1014 (table.getColumnModel().getSelectionModel().getSelectionMode() ==
1015 ListSelectionModel.SINGLE_SELECTION)) {
1016
1017 return true;
1018 }
1019
1020 return table.isCellSelected(pressedRow, pressedCol);
1021 }
1022
1023 public void mousePressed(MouseEvent e) {
1024 if (SwingUtilities2.shouldIgnore(e, table)) {
1025 return;
1026 }
1027
1028 if (table.isEditing() && !table.getCellEditor().stopCellEditing()) {
1029 Component editorComponent = table.getEditorComponent();
1030 if (editorComponent != null && !editorComponent.hasFocus()) {
1031 SwingUtilities2.compositeRequestFocus(editorComponent);
1032 }
1033 return;
1034 }
1035
1036 Point p = e.getPoint();
1037 pressedRow = table.rowAtPoint(p);
1038 pressedCol = table.columnAtPoint(p);
1039 outsidePrefSize = pointOutsidePrefSize(pressedRow, pressedCol, p);
1040
1041 if (isFileList) {
1042 shouldStartTimer =
1043 table.isCellSelected(pressedRow, pressedCol) &&
1044 !e.isShiftDown() &&
1045 !BasicGraphicsUtils.isMenuShortcutKeyDown(e) &&
1046 !outsidePrefSize;
1047 }
1048
1049 if (table.getDragEnabled()) {
1050 mousePressedDND(e);
1051 } else {
1052 SwingUtilities2.adjustFocus(table);
1053 if (!isFileList) {
1054 setValueIsAdjusting(true);
1055 }
1056 adjustSelection(e);
1057 }
1058 }
1059
1060 private void mousePressedDND(MouseEvent e) {
1061 pressedEvent = e;
1062 boolean grabFocus = true;
1063 dragStarted = false;
1064
1065 if (canStartDrag() && DragRecognitionSupport.mousePressed(e)) {
1066
1067 dragPressDidSelection = false;
1068
1069 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e) && isFileList) {
1070 // do nothing for control - will be handled on release
1071 // or when drag starts
1072 return;
1073 } else if (!e.isShiftDown() && table.isCellSelected(pressedRow, pressedCol)) {
1074 // clicking on something that's already selected
1075 // and need to make it the lead now
1076 table.getSelectionModel().addSelectionInterval(pressedRow,
1077 pressedRow);
1078 table.getColumnModel().getSelectionModel().
1079 addSelectionInterval(pressedCol, pressedCol);
1080
1081 return;
1082 }
1083
1084 dragPressDidSelection = true;
1085
1086 // could be a drag initiating event - don't grab focus
1087 grabFocus = false;
1088 } else if (!isFileList) {
1089 // When drag can't happen, mouse drags might change the selection in the table
1090 // so we want the isAdjusting flag to be set
1091 setValueIsAdjusting(true);
1092 }
1093
1094 if (grabFocus) {
1095 SwingUtilities2.adjustFocus(table);
1096 }
1097
1098 adjustSelection(e);
1099 }
1100
1101 private void adjustSelection(MouseEvent e) {
1102 // Fix for 4835633
1103 if (outsidePrefSize) {
1104 // If shift is down in multi-select, we should just return.
1105 // For single select or non-shift-click, clear the selection
1106 if (e.getID() == MouseEvent.MOUSE_PRESSED &&
1107 (!e.isShiftDown() ||
1108 table.getSelectionModel().getSelectionMode() ==
1109 ListSelectionModel.SINGLE_SELECTION)) {
1110 table.clearSelection();
1111 TableCellEditor tce = table.getCellEditor();
1112 if (tce != null) {
1113 tce.stopCellEditing();
1114 }
1115 }
1116 return;
1117 }
1118 // The autoscroller can generate drag events outside the
1119 // table's range.
1120 if ((pressedCol == -1) || (pressedRow == -1)) {
1121 return;
1122 }
1123
1124 boolean dragEnabled = table.getDragEnabled();
1125
1126 if (!dragEnabled && !isFileList && table.editCellAt(pressedRow, pressedCol, e)) {
1127 setDispatchComponent(e);
1128 repostEvent(e);
1129 }
1130
1131 CellEditor editor = table.getCellEditor();
1132 if (dragEnabled || editor == null || editor.shouldSelectCell(e)) {
1133 table.changeSelection(pressedRow, pressedCol,
1134 BasicGraphicsUtils.isMenuShortcutKeyDown(e),
1135 e.isShiftDown());
1136 }
1137 }
1138
1139 public void valueChanged(ListSelectionEvent e) {
1140 if (timer != null) {
1141 timer.stop();
1142 timer = null;
1143 }
1144 }
1145
1146 public void actionPerformed(ActionEvent ae) {
1147 table.editCellAt(pressedRow, pressedCol, null);
1148 Component editorComponent = table.getEditorComponent();
1149 if (editorComponent != null && !editorComponent.hasFocus()) {
1150 SwingUtilities2.compositeRequestFocus(editorComponent);
1151 }
1152 return;
1153 }
1154
1155 private void maybeStartTimer() {
1156 if (!shouldStartTimer) {
1157 return;
1158 }
1159
1160 if (timer == null) {
1161 timer = new Timer(1200, this);
1162 timer.setRepeats(false);
1163 }
1164
1165 timer.start();
1166 }
1167
1168 public void mouseReleased(MouseEvent e) {
1169 if (SwingUtilities2.shouldIgnore(e, table)) {
1170 return;
1171 }
1172
1173 if (table.getDragEnabled()) {
1174 mouseReleasedDND(e);
1175 } else {
1176 if (isFileList) {
1177 maybeStartTimer();
1178 }
1179 }
1180
1181 pressedEvent = null;
1182 repostEvent(e);
1183 dispatchComponent = null;
1184 setValueIsAdjusting(false);
1185 }
1186
1187 private void mouseReleasedDND(MouseEvent e) {
1188 MouseEvent me = DragRecognitionSupport.mouseReleased(e);
1189 if (me != null) {
1190 SwingUtilities2.adjustFocus(table);
1191 if (!dragPressDidSelection) {
1192 adjustSelection(me);
1193 }
1194 }
1195
1196 if (!dragStarted) {
1197 if (isFileList) {
1198 maybeStartTimer();
1199 return;
1200 }
1201
1202 Point p = e.getPoint();
1203
1204 if (pressedEvent != null &&
1205 table.rowAtPoint(p) == pressedRow &&
1206 table.columnAtPoint(p) == pressedCol &&
1207 table.editCellAt(pressedRow, pressedCol, pressedEvent)) {
1208
1209 setDispatchComponent(pressedEvent);
1210 repostEvent(pressedEvent);
1211
1212 // This may appear completely odd, but must be done for backward
1213 // compatibility reasons. Developers have been known to rely on
1214 // a call to shouldSelectCell after editing has begun.
1215 CellEditor ce = table.getCellEditor();
1216 if (ce != null) {
1217 ce.shouldSelectCell(pressedEvent);
1218 }
1219 }
1220 }
1221 }
1222
1223 public void mouseEntered(MouseEvent e) {}
1224
1225 public void mouseExited(MouseEvent e) {}
1226
1227 public void mouseMoved(MouseEvent e) {}
1228
1229 public void dragStarting(MouseEvent me) {
1230 dragStarted = true;
1231
1232 if (BasicGraphicsUtils.isMenuShortcutKeyDown(me) && isFileList) {
1233 table.getSelectionModel().addSelectionInterval(pressedRow,
1234 pressedRow);
1235 table.getColumnModel().getSelectionModel().
1236 addSelectionInterval(pressedCol, pressedCol);
1237 }
1238
1239 pressedEvent = null;
1240 }
1241
1242 public void mouseDragged(MouseEvent e) {
1243 if (SwingUtilities2.shouldIgnore(e, table)) {
1244 return;
1245 }
1246
1247 if (table.getDragEnabled() &&
1248 (DragRecognitionSupport.mouseDragged(e, this) || dragStarted)) {
1249
1250 return;
1251 }
1252
1253 repostEvent(e);
1254
1255 // Check isFileList:
1256 // Until we support drag-selection, dragging should not change
1257 // the selection (act like single-select).
1258 if (isFileList || table.isEditing()) {
1259 return;
1260 }
1261
1262 Point p = e.getPoint();
1263 int row = table.rowAtPoint(p);
1264 int column = table.columnAtPoint(p);
1265 // The autoscroller can generate drag events outside the
1266 // table's range.
1267 if ((column == -1) || (row == -1)) {
1268 return;
1269 }
1270
1271 table.changeSelection(row, column,
1272 BasicGraphicsUtils.isMenuShortcutKeyDown(e), true);
1273 }
1274
1275
1276 // PropertyChangeListener
1277 public void propertyChange(PropertyChangeEvent event) {
1278 String changeName = event.getPropertyName();
1279
1280 if ("componentOrientation" == changeName) {
1281 InputMap inputMap = getInputMap(
1282 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1283
1284 SwingUtilities.replaceUIInputMap(table,
1285 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1286 inputMap);
1287
1288 JTableHeader header = table.getTableHeader();
1289 if (header != null) {
1290 header.setComponentOrientation(
1291 (ComponentOrientation)event.getNewValue());
1292 }
1293 } else if ("dropLocation" == changeName) {
1294 JTable.DropLocation oldValue = (JTable.DropLocation)event.getOldValue();
1295 repaintDropLocation(oldValue);
1296 repaintDropLocation(table.getDropLocation());
1297 } else if ("Table.isFileList" == changeName) {
1298 isFileList = Boolean.TRUE.equals(table.getClientProperty("Table.isFileList"));
1299 table.revalidate();
1300 table.repaint();
1301 if (isFileList) {
1302 table.getSelectionModel().addListSelectionListener(getHandler());
1303 } else {
1304 table.getSelectionModel().removeListSelectionListener(getHandler());
1305 timer = null;
1306 }
1307 } else if ("selectionModel" == changeName) {
1308 if (isFileList) {
1309 ListSelectionModel old = (ListSelectionModel)event.getOldValue();
1310 old.removeListSelectionListener(getHandler());
1311 table.getSelectionModel().addListSelectionListener(getHandler());
1312 }
1313 }
1314 }
1315
1316 private void repaintDropLocation(JTable.DropLocation loc) {
1317 if (loc == null) {
1318 return;
1319 }
1320
1321 if (!loc.isInsertRow() && !loc.isInsertColumn()) {
1322 Rectangle rect = table.getCellRect(loc.getRow(), loc.getColumn(), false);
1323 if(table.getClientProperty("Table.showWholeRowAsDropTarget") == Boolean.TRUE) {
1324 // used by WindowsFileChooserUI in Details view
1325
1326 rect.x = 0;
1327 rect.width = table.getWidth();
1328 }
1329 if (rect != null) {
1330 table.repaint(rect);
1331 }
1332 return;
1333 }
1334
1335 if (loc.isInsertRow()) {
1336 Rectangle rect = extendRect(getHDropLineRect(loc), true);
1337 if (rect != null) {
1338 table.repaint(rect);
1339 }
1340 }
1341
1342 if (loc.isInsertColumn()) {
1343 Rectangle rect = extendRect(getVDropLineRect(loc), false);
1344 if (rect != null) {
1345 table.repaint(rect);
1346 }
1347 }
1348 }
1349 }
1350
1351
1352 /*
1353 * Returns true if the given point is outside the preferredSize of the
1354 * item at the given row of the table. (Column must be 0).
1355 * Returns false if the "Table.isFileList" client property is not set.
1356 */
1357 private boolean pointOutsidePrefSize(int row, int column, Point p) {
1358 if (!isFileList) {
1359 return false;
1360 }
1361
1362 return SwingUtilities2.pointOutsidePrefSize(table, row, column, p);
1363 }
1364
1365 //
1366 // Factory methods for the Listeners
1367 //
1368
1369 private Handler getHandler() {
1370 if (handler == null) {
1371 handler = new Handler();
1372 }
1373 return handler;
1374 }
1375
1376 /**
1377 * Creates the key listener for handling keyboard navigation in the {@code JTable}.
1378 *
1379 * @return the key listener for handling keyboard navigation in the {@code JTable}
1380 */
1381 protected KeyListener createKeyListener() {
1382 return null;
1383 }
1384
1385 /**
1386 * Creates the focus listener for handling keyboard navigation in the {@code JTable}.
1387 *
1388 * @return the focus listener for handling keyboard navigation in the {@code JTable}
1389 */
1390 protected FocusListener createFocusListener() {
1391 return getHandler();
1392 }
1393
1394 /**
1395 * Creates the mouse listener for the {@code JTable}.
1396 *
1397 * @return the mouse listener for the {@code JTable}
1398 */
1399 protected MouseInputListener createMouseInputListener() {
1400 return getHandler();
1401 }
1402
1403 //
1404 // The installation/uninstall procedures and support
1405 //
1406
1407 /**
1408 * Returns a new instance of {@code BasicTableUI}.
1409 *
1410 * @param c a component
1411 * @return a new instance of {@code BasicTableUI}
1412 */
1413 public static ComponentUI createUI(JComponent c) {
1414 return new BasicTableUI();
1415 }
1416
1417 // Installation
1418
1419 public void installUI(JComponent c) {
1420 table = (JTable)c;
1421
1422 rendererPane = new CellRendererPane();
1423 table.add(rendererPane);
1424 installDefaults();
1425 installDefaults2();
1426 installListeners();
1427 installKeyboardActions();
1428 }
1429
1430 /**
1431 * Initialize JTable properties, e.g. font, foreground, and background.
1432 * The font, foreground, and background properties are only set if their
1433 * current value is either null or a UIResource, other properties are set
1434 * if the current value is null.
1435 *
1436 * @see #installUI
1437 */
1438 protected void installDefaults() {
1439 LookAndFeel.installColorsAndFont(table, "Table.background",
1440 "Table.foreground", "Table.font");
1441 // JTable's original row height is 16. To correctly display the
1442 // contents on Linux we should have set it to 18, Windows 19 and
1443 // Solaris 20. As these values vary so much it's too hard to
1444 // be backward compatable and try to update the row height, we're
1445 // therefor NOT going to adjust the row height based on font. If the
1446 // developer changes the font, it's there responsability to update
1447 // the row height.
1448
1449 LookAndFeel.installProperty(table, "opaque", Boolean.TRUE);
1450
1451 Color sbg = table.getSelectionBackground();
1452 if (sbg == null || sbg instanceof UIResource) {
1453 sbg = UIManager.getColor("Table.selectionBackground");
1454 table.setSelectionBackground(sbg != null ? sbg : UIManager.getColor("textHighlight"));
1455 }
1456
1457 Color sfg = table.getSelectionForeground();
1458 if (sfg == null || sfg instanceof UIResource) {
1459 sfg = UIManager.getColor("Table.selectionForeground");
1460 table.setSelectionForeground(sfg != null ? sfg : UIManager.getColor("textHighlightText"));
1461 }
1462
1463 Color gridColor = table.getGridColor();
1464 if (gridColor == null || gridColor instanceof UIResource) {
1465 gridColor = UIManager.getColor("Table.gridColor");
1466 table.setGridColor(gridColor != null ? gridColor : Color.GRAY);
1467 }
1468
1469 // install the scrollpane border
1470 Container parent = SwingUtilities.getUnwrappedParent(table); // should be viewport
1471 if (parent != null) {
1472 parent = parent.getParent(); // should be the scrollpane
1473 if (parent != null && parent instanceof JScrollPane) {
1474 LookAndFeel.installBorder((JScrollPane)parent, "Table.scrollPaneBorder");
1475 }
1476 }
1477
1478 isFileList = Boolean.TRUE.equals(table.getClientProperty("Table.isFileList"));
1479 }
1480
1481 private void installDefaults2() {
1482 TransferHandler th = table.getTransferHandler();
1483 if (th == null || th instanceof UIResource) {
1484 table.setTransferHandler(defaultTransferHandler);
1485 // default TransferHandler doesn't support drop
1486 // so we don't want drop handling
1487 if (table.getDropTarget() instanceof UIResource) {
1488 table.setDropTarget(null);
1489 }
1490 }
1491 }
1492
1493 /**
1494 * Attaches listeners to the JTable.
1495 */
1496 protected void installListeners() {
1497 focusListener = createFocusListener();
1498 keyListener = createKeyListener();
1499 mouseInputListener = createMouseInputListener();
1500
1501 table.addFocusListener(focusListener);
1502 table.addKeyListener(keyListener);
1503 table.addMouseListener(mouseInputListener);
1504 table.addMouseMotionListener(mouseInputListener);
1505 table.addPropertyChangeListener(getHandler());
1506 if (isFileList) {
1507 table.getSelectionModel().addListSelectionListener(getHandler());
1508 }
1509 }
1510
1511 /**
1512 * Register all keyboard actions on the JTable.
1513 */
1514 protected void installKeyboardActions() {
1515 LazyActionMap.installLazyActionMap(table, BasicTableUI.class,
1516 "Table.actionMap");
1517
1518 InputMap inputMap = getInputMap(JComponent.
1519 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1520 SwingUtilities.replaceUIInputMap(table,
1521 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1522 inputMap);
1523 }
1524
1525 InputMap getInputMap(int condition) {
1526 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
1527 InputMap keyMap =
1528 (InputMap)DefaultLookup.get(table, this,
1529 "Table.ancestorInputMap");
1530 InputMap rtlKeyMap;
1531
1532 if (table.getComponentOrientation().isLeftToRight() ||
1533 ((rtlKeyMap = (InputMap)DefaultLookup.get(table, this,
1534 "Table.ancestorInputMap.RightToLeft")) == null)) {
1535 return keyMap;
1536 } else {
1537 rtlKeyMap.setParent(keyMap);
1538 return rtlKeyMap;
1539 }
1540 }
1541 return null;
1542 }
1543
1544 static void loadActionMap(LazyActionMap map) {
1545 // IMPORTANT: There is a very close coupling between the parameters
1546 // passed to the Actions constructor. Only certain parameter
1547 // combinations are supported. For example, the following Action would
1548 // not work as expected:
1549 // new Actions(Actions.NEXT_ROW_CELL, 1, 4, false, true)
1550 // Actions which move within the selection only (having a true
1551 // inSelection parameter) require that one of dx or dy be
1552 // zero and the other be -1 or 1. The point of this warning is
1553 // that you should be very careful about making sure a particular
1554 // combination of parameters is supported before changing or
1555 // adding anything here.
1556
1557 map.put(new Actions(Actions.NEXT_COLUMN, 1, 0,
1558 false, false));
1559 map.put(new Actions(Actions.NEXT_COLUMN_CHANGE_LEAD, 1, 0,
1560 false, false));
1561 map.put(new Actions(Actions.PREVIOUS_COLUMN, -1, 0,
1562 false, false));
1563 map.put(new Actions(Actions.PREVIOUS_COLUMN_CHANGE_LEAD, -1, 0,
1564 false, false));
1565 map.put(new Actions(Actions.NEXT_ROW, 0, 1,
1566 false, false));
1567 map.put(new Actions(Actions.NEXT_ROW_CHANGE_LEAD, 0, 1,
1568 false, false));
1569 map.put(new Actions(Actions.PREVIOUS_ROW, 0, -1,
1570 false, false));
1571 map.put(new Actions(Actions.PREVIOUS_ROW_CHANGE_LEAD, 0, -1,
1572 false, false));
1573 map.put(new Actions(Actions.NEXT_COLUMN_EXTEND_SELECTION,
1574 1, 0, true, false));
1575 map.put(new Actions(Actions.PREVIOUS_COLUMN_EXTEND_SELECTION,
1576 -1, 0, true, false));
1577 map.put(new Actions(Actions.NEXT_ROW_EXTEND_SELECTION,
1578 0, 1, true, false));
1579 map.put(new Actions(Actions.PREVIOUS_ROW_EXTEND_SELECTION,
1580 0, -1, true, false));
1581 map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION,
1582 false, false, true, false));
1583 map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION,
1584 false, true, true, false));
1585 map.put(new Actions(Actions.FIRST_COLUMN,
1586 false, false, false, true));
1587 map.put(new Actions(Actions.LAST_COLUMN,
1588 false, true, false, true));
1589
1590 map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION,
1591 true, false, true, false));
1592 map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION,
1593 true, true, true, false));
1594 map.put(new Actions(Actions.FIRST_COLUMN_EXTEND_SELECTION,
1595 true, false, false, true));
1596 map.put(new Actions(Actions.LAST_COLUMN_EXTEND_SELECTION,
1597 true, true, false, true));
1598
1599 map.put(new Actions(Actions.FIRST_ROW, false, false, true, true));
1600 map.put(new Actions(Actions.LAST_ROW, false, true, true, true));
1601
1602 map.put(new Actions(Actions.FIRST_ROW_EXTEND_SELECTION,
1603 true, false, true, true));
1604 map.put(new Actions(Actions.LAST_ROW_EXTEND_SELECTION,
1605 true, true, true, true));
1606
1607 map.put(new Actions(Actions.NEXT_COLUMN_CELL,
1608 1, 0, false, true));
1609 map.put(new Actions(Actions.PREVIOUS_COLUMN_CELL,
1610 -1, 0, false, true));
1611 map.put(new Actions(Actions.NEXT_ROW_CELL, 0, 1, false, true));
1612 map.put(new Actions(Actions.PREVIOUS_ROW_CELL,
1613 0, -1, false, true));
1614
1615 map.put(new Actions(Actions.SELECT_ALL));
1616 map.put(new Actions(Actions.CLEAR_SELECTION));
1617 map.put(new Actions(Actions.CANCEL_EDITING));
1618 map.put(new Actions(Actions.START_EDITING));
1619
1620 map.put(TransferHandler.getCutAction().getValue(Action.NAME),
1621 TransferHandler.getCutAction());
1622 map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
1623 TransferHandler.getCopyAction());
1624 map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
1625 TransferHandler.getPasteAction());
1626
1627 map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_SELECTION,
1628 false, false, false, false));
1629 map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_SELECTION,
1630 false, true, false, false));
1631 map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION,
1632 true, false, false, false));
1633 map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION,
1634 true, true, false, false));
1635
1636 map.put(new Actions(Actions.ADD_TO_SELECTION));
1637 map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
1638 map.put(new Actions(Actions.EXTEND_TO));
1639 map.put(new Actions(Actions.MOVE_SELECTION_TO));
1640 map.put(new Actions(Actions.FOCUS_HEADER));
1641 }
1642
1643 // Uninstallation
1644
1645 public void uninstallUI(JComponent c) {
1646 uninstallDefaults();
1647 uninstallListeners();
1648 uninstallKeyboardActions();
1649
1650 table.remove(rendererPane);
1651 rendererPane = null;
1652 table = null;
1653 }
1654
1655 /**
1656 * Uninstalls default properties.
1657 */
1658 protected void uninstallDefaults() {
1659 if (table.getTransferHandler() instanceof UIResource) {
1660 table.setTransferHandler(null);
1661 }
1662 }
1663
1664 /**
1665 * Unregisters listeners.
1666 */
1667 protected void uninstallListeners() {
1668 table.removeFocusListener(focusListener);
1669 table.removeKeyListener(keyListener);
1670 table.removeMouseListener(mouseInputListener);
1671 table.removeMouseMotionListener(mouseInputListener);
1672 table.removePropertyChangeListener(getHandler());
1673 if (isFileList) {
1674 table.getSelectionModel().removeListSelectionListener(getHandler());
1675 }
1676
1677 focusListener = null;
1678 keyListener = null;
1679 mouseInputListener = null;
1680 handler = null;
1681 }
1682
1683 /**
1684 * Unregisters keyboard actions.
1685 */
1686 protected void uninstallKeyboardActions() {
1687 SwingUtilities.replaceUIInputMap(table, JComponent.
1688 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1689 SwingUtilities.replaceUIActionMap(table, null);
1690 }
1691
1692 /**
1693 * Returns the baseline.
1694 *
1695 * @throws NullPointerException {@inheritDoc}
1696 * @throws IllegalArgumentException {@inheritDoc}
1697 * @see javax.swing.JComponent#getBaseline(int, int)
1698 * @since 1.6
1699 */
1700 public int getBaseline(JComponent c, int width, int height) {
1701 super.getBaseline(c, width, height);
1702 UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
1703 Component renderer = (Component)lafDefaults.get(
1704 BASELINE_COMPONENT_KEY);
1705 if (renderer == null) {
1706 DefaultTableCellRenderer tcr = new DefaultTableCellRenderer();
1707 renderer = tcr.getTableCellRendererComponent(
1708 table, "a", false, false, -1, -1);
1709 lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
1710 }
1711 renderer.setFont(table.getFont());
1712 int rowMargin = table.getRowMargin();
1713 return renderer.getBaseline(Integer.MAX_VALUE, table.getRowHeight() -
1714 rowMargin) + rowMargin / 2;
1715 }
1716
1717 /**
1718 * Returns an enum indicating how the baseline of the component
1719 * changes as the size changes.
1720 *
1721 * @throws NullPointerException {@inheritDoc}
1722 * @see javax.swing.JComponent#getBaseline(int, int)
1723 * @since 1.6
1724 */
1725 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
1726 JComponent c) {
1727 super.getBaselineResizeBehavior(c);
1728 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
1729 }
1730
1731 //
1732 // Size Methods
1733 //
1734
1735 private Dimension createTableSize(long width) {
1736 int height = 0;
1737 int rowCount = table.getRowCount();
1738 if (rowCount > 0 && table.getColumnCount() > 0) {
1739 Rectangle r = table.getCellRect(rowCount-1, 0, true);
1740 height = r.y + r.height;
1741 }
1742 // Width is always positive. The call to abs() is a workaround for
1743 // a bug in the 1.1.6 JIT on Windows.
1744 long tmp = Math.abs(width);
1745 if (tmp > Integer.MAX_VALUE) {
1746 tmp = Integer.MAX_VALUE;
1747 }
1748 return new Dimension((int)tmp, height);
1749 }
1750
1751 /**
1752 * Return the minimum size of the table. The minimum height is the
1753 * row height times the number of rows.
1754 * The minimum width is the sum of the minimum widths of each column.
1755 */
1756 public Dimension getMinimumSize(JComponent c) {
1757 long width = 0;
1758 Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns();
1759 while (enumeration.hasMoreElements()) {
1760 TableColumn aColumn = enumeration.nextElement();
1761 width = width + aColumn.getMinWidth();
1762 }
1763 return createTableSize(width);
1764 }
1765
1766 /**
1767 * Return the preferred size of the table. The preferred height is the
1768 * row height times the number of rows.
1769 * The preferred width is the sum of the preferred widths of each column.
1770 */
1771 public Dimension getPreferredSize(JComponent c) {
1772 long width = 0;
1773 Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns();
1774 while (enumeration.hasMoreElements()) {
1775 TableColumn aColumn = enumeration.nextElement();
1776 width = width + aColumn.getPreferredWidth();
1777 }
1778 return createTableSize(width);
1779 }
1780
1781 /**
1782 * Return the maximum size of the table. The maximum height is the
1783 * row heighttimes the number of rows.
1784 * The maximum width is the sum of the maximum widths of each column.
1785 */
1786 public Dimension getMaximumSize(JComponent c) {
1787 long width = 0;
1788 Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns();
1789 while (enumeration.hasMoreElements()) {
1790 TableColumn aColumn = enumeration.nextElement();
1791 width = width + aColumn.getMaxWidth();
1792 }
1793 return createTableSize(width);
1794 }
1795
1796 //
1797 // Paint methods and support
1798 //
1799
1800 /** Paint a representation of the <code>table</code> instance
1801 * that was set in installUI().
1802 */
1803 public void paint(Graphics g, JComponent c) {
1804 Rectangle clip = g.getClipBounds();
1805
1806 Rectangle bounds = table.getBounds();
1807 // account for the fact that the graphics has already been translated
1808 // into the table's bounds
1809 bounds.x = bounds.y = 0;
1810
1811 if (table.getRowCount() <= 0 || table.getColumnCount() <= 0 ||
1812 // this check prevents us from painting the entire table
1813 // when the clip doesn't intersect our bounds at all
1814 !bounds.intersects(clip)) {
1815
1816 paintDropLines(g);
1817 return;
1818 }
1819
1820 boolean ltr = table.getComponentOrientation().isLeftToRight();
1821
1822 // compute the visible part of table which needs to be painted
1823 Rectangle visibleBounds = clip.intersection(bounds);
1824 Point upperLeft = visibleBounds.getLocation();
1825 Point lowerRight = new Point(visibleBounds.x + visibleBounds.width - 1,
1826 visibleBounds.y + visibleBounds.height - 1);
1827
1828 int rMin = table.rowAtPoint(upperLeft);
1829 int rMax = table.rowAtPoint(lowerRight);
1830 // This should never happen (as long as our bounds intersect the clip,
1831 // which is why we bail above if that is the case).
1832 if (rMin == -1) {
1833 rMin = 0;
1834 }
1835 // If the table does not have enough rows to fill the view we'll get -1.
1836 // (We could also get -1 if our bounds don't intersect the clip,
1837 // which is why we bail above if that is the case).
1838 // Replace this with the index of the last row.
1839 if (rMax == -1) {
1840 rMax = table.getRowCount()-1;
1841 }
1842
1843 int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight);
1844 int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);
1845 // This should never happen.
1846 if (cMin == -1) {
1847 cMin = 0;
1848 }
1849 // If the table does not have enough columns to fill the view we'll get -1.
1850 // Replace this with the index of the last column.
1851 if (cMax == -1) {
1852 cMax = table.getColumnCount()-1;
1853 }
1854
1855 Container comp = SwingUtilities.getUnwrappedParent(table);
1856 if (comp != null) {
1857 comp = comp.getParent();
1858 }
1859
1860 if (comp != null && !(comp instanceof JViewport) && !(comp instanceof JScrollPane)) {
1861 // We did rMax-1 to paint the same number of rows that are drawn on console
1862 // otherwise 1 extra row is printed per page than that are displayed
1863 // when there is no scrollPane and we do printing of table
1864 // but not when rmax is already pointing to index of last row
1865 if (rMax != (table.getRowCount() - 1)) {
1866 rMax = rMax - 1;
1867 }
1868 }
1869
1870 // Paint the grid.
1871 paintGrid(g, rMin, rMax, cMin, cMax);
1872
1873 // Paint the cells.
1874 paintCells(g, rMin, rMax, cMin, cMax);
1875
1876 paintDropLines(g);
1877 }
1878
1879 private void paintDropLines(Graphics g) {
1880 JTable.DropLocation loc = table.getDropLocation();
1881 if (loc == null) {
1882 return;
1883 }
1884
1885 Color color = UIManager.getColor("Table.dropLineColor");
1886 Color shortColor = UIManager.getColor("Table.dropLineShortColor");
1887 if (color == null && shortColor == null) {
1888 return;
1889 }
1890
1891 Rectangle rect;
1892
1893 rect = getHDropLineRect(loc);
1894 if (rect != null) {
1895 int x = rect.x;
1896 int w = rect.width;
1897 if (color != null) {
1898 extendRect(rect, true);
1899 g.setColor(color);
1900 g.fillRect(rect.x, rect.y, rect.width, rect.height);
1901 }
1902 if (!loc.isInsertColumn() && shortColor != null) {
1903 g.setColor(shortColor);
1904 g.fillRect(x, rect.y, w, rect.height);
1905 }
1906 }
1907
1908 rect = getVDropLineRect(loc);
1909 if (rect != null) {
1910 int y = rect.y;
1911 int h = rect.height;
1912 if (color != null) {
1913 extendRect(rect, false);
1914 g.setColor(color);
1915 g.fillRect(rect.x, rect.y, rect.width, rect.height);
1916 }
1917 if (!loc.isInsertRow() && shortColor != null) {
1918 g.setColor(shortColor);
1919 g.fillRect(rect.x, y, rect.width, h);
1920 }
1921 }
1922 }
1923
1924 private Rectangle getHDropLineRect(JTable.DropLocation loc) {
1925 if (!loc.isInsertRow()) {
1926 return null;
1927 }
1928
1929 int row = loc.getRow();
1930 int col = loc.getColumn();
1931 if (col >= table.getColumnCount()) {
1932 col--;
1933 }
1934
1935 Rectangle rect = table.getCellRect(row, col, true);
1936
1937 if (row >= table.getRowCount()) {
1938 row--;
1939 Rectangle prevRect = table.getCellRect(row, col, true);
1940 rect.y = prevRect.y + prevRect.height;
1941 }
1942
1943 if (rect.y == 0) {
1944 rect.y = -1;
1945 } else {
1946 rect.y -= 2;
1947 }
1948
1949 rect.height = 3;
1950
1951 return rect;
1952 }
1953
1954 private Rectangle getVDropLineRect(JTable.DropLocation loc) {
1955 if (!loc.isInsertColumn()) {
1956 return null;
1957 }
1958
1959 boolean ltr = table.getComponentOrientation().isLeftToRight();
1960 int col = loc.getColumn();
1961 Rectangle rect = table.getCellRect(loc.getRow(), col, true);
1962
1963 if (col >= table.getColumnCount()) {
1964 col--;
1965 rect = table.getCellRect(loc.getRow(), col, true);
1966 if (ltr) {
1967 rect.x = rect.x + rect.width;
1968 }
1969 } else if (!ltr) {
1970 rect.x = rect.x + rect.width;
1971 }
1972
1973 if (rect.x == 0) {
1974 rect.x = -1;
1975 } else {
1976 rect.x -= 2;
1977 }
1978
1979 rect.width = 3;
1980
1981 return rect;
1982 }
1983
1984 private Rectangle extendRect(Rectangle rect, boolean horizontal) {
1985 if (rect == null) {
1986 return rect;
1987 }
1988
1989 if (horizontal) {
1990 rect.x = 0;
1991 rect.width = table.getWidth();
1992 } else {
1993 rect.y = 0;
1994
1995 if (table.getRowCount() != 0) {
1996 Rectangle lastRect = table.getCellRect(table.getRowCount() - 1, 0, true);
1997 rect.height = lastRect.y + lastRect.height;
1998 } else {
1999 rect.height = table.getHeight();
2000 }
2001 }
2002
2003 return rect;
2004 }
2005
2006 /*
2007 * Paints the grid lines within <I>aRect</I>, using the grid
2008 * color set with <I>setGridColor</I>. Paints vertical lines
2009 * if <code>getShowVerticalLines()</code> returns true and paints
2010 * horizontal lines if <code>getShowHorizontalLines()</code>
2011 * returns true.
2012 */
2013 private void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) {
2014 g.setColor(table.getGridColor());
2015
2016 Rectangle minCell = table.getCellRect(rMin, cMin, true);
2017 Rectangle maxCell = table.getCellRect(rMax, cMax, true);
2018 Rectangle damagedArea = minCell.union( maxCell );
2019
2020 if (table.getShowHorizontalLines()) {
2021 int tableWidth = damagedArea.x + damagedArea.width;
2022 int y = damagedArea.y;
2023 for (int row = rMin; row <= rMax; row++) {
2024 y += table.getRowHeight(row);
2025 g.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1);
2026 }
2027 }
2028 if (table.getShowVerticalLines()) {
2029 TableColumnModel cm = table.getColumnModel();
2030 int tableHeight = damagedArea.y + damagedArea.height;
2031 int x;
2032 if (table.getComponentOrientation().isLeftToRight()) {
2033 x = damagedArea.x;
2034 for (int column = cMin; column <= cMax; column++) {
2035 int w = cm.getColumn(column).getWidth();
2036 x += w;
2037 g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
2038 }
2039 } else {
2040 x = damagedArea.x;
2041 for (int column = cMax; column >= cMin; column--) {
2042 int w = cm.getColumn(column).getWidth();
2043 x += w;
2044 g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
2045 }
2046 }
2047 }
2048 }
2049
2050 private int viewIndexForColumn(TableColumn aColumn) {
2051 TableColumnModel cm = table.getColumnModel();
2052 for (int column = 0; column < cm.getColumnCount(); column++) {
2053 if (cm.getColumn(column) == aColumn) {
2054 return column;
2055 }
2056 }
2057 return -1;
2058 }
2059
2060 private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
2061 boolean cellWidthHack = table.getClientProperty(
2062 "BasicTableUI.cellWidthHack") == Boolean.TRUE;
2063 // used by WindowsFileTableUI to remove borders between cells event when a row is selected
2064
2065 JTableHeader header = table.getTableHeader();
2066 TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
2067
2068 TableColumnModel cm = table.getColumnModel();
2069 int columnMargin = cm.getColumnMargin();
2070
2071 Rectangle cellRect;
2072 TableColumn aColumn;
2073 int columnWidth;
2074 if (table.getComponentOrientation().isLeftToRight()) {
2075 for(int row = rMin; row <= rMax; row++) {
2076 cellRect = table.getCellRect(row, cMin, false);
2077 for(int column = cMin; column <= cMax; column++) {
2078 aColumn = cm.getColumn(column);
2079 columnWidth = aColumn.getWidth();
2080 cellRect.width = columnWidth - columnMargin;
2081 if(cellWidthHack)
2082 cellRect.width += 1;
2083 if (aColumn != draggedColumn) {
2084 paintCell(g, cellRect, row, column);
2085 }
2086 cellRect.x += columnWidth;
2087 }
2088 }
2089 } else {
2090 for(int row = rMin; row <= rMax; row++) {
2091 cellRect = table.getCellRect(row, cMin, false);
2092 aColumn = cm.getColumn(cMin);
2093 if (aColumn != draggedColumn) {
2094 columnWidth = aColumn.getWidth();
2095 cellRect.width = columnWidth - columnMargin;
2096 paintCell(g, cellRect, row, cMin);
2097 }
2098 for(int column = cMin+1; column <= cMax; column++) {
2099 aColumn = cm.getColumn(column);
2100 columnWidth = aColumn.getWidth();
2101 cellRect.width = columnWidth - columnMargin;
2102 cellRect.x -= columnWidth;
2103 if (aColumn != draggedColumn) {
2104 paintCell(g, cellRect, row, column);
2105 }
2106 }
2107 }
2108 }
2109
2110 // Paint the dragged column if we are dragging.
2111 if (draggedColumn != null) {
2112 paintDraggedArea(g, rMin, rMax, draggedColumn, header.getDraggedDistance());
2113 }
2114
2115 // Remove any renderers that may be left in the rendererPane.
2116 rendererPane.removeAll();
2117 }
2118
2119 private void paintDraggedArea(Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) {
2120 int draggedColumnIndex = viewIndexForColumn(draggedColumn);
2121
2122 Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
2123 Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
2124
2125 Rectangle vacatedColumnRect = minCell.union(maxCell);
2126
2127 // Paint a gray well in place of the moving column.
2128 g.setColor(table.getParent().getBackground());
2129 g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
2130 vacatedColumnRect.width, vacatedColumnRect.height);
2131
2132 // Move to the where the cell has been dragged.
2133 vacatedColumnRect.x += distance;
2134
2135 // Fill the background.
2136 g.setColor(table.getBackground());
2137 g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
2138 vacatedColumnRect.width, vacatedColumnRect.height);
2139
2140 // Paint the vertical grid lines if necessary.
2141 if (table.getShowVerticalLines()) {
2142 g.setColor(table.getGridColor());
2143 int x1 = vacatedColumnRect.x;
2144 int y1 = vacatedColumnRect.y;
2145 int x2 = x1 + vacatedColumnRect.width - 1;
2146 int y2 = y1 + vacatedColumnRect.height - 1;
2147 // Left
2148 g.drawLine(x1-1, y1, x1-1, y2);
2149 // Right
2150 g.drawLine(x2, y1, x2, y2);
2151 }
2152
2153 for(int row = rMin; row <= rMax; row++) {
2154 // Render the cell value
2155 Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
2156 r.x += distance;
2157 paintCell(g, r, row, draggedColumnIndex);
2158
2159 // Paint the (lower) horizontal grid line if necessary.
2160 if (table.getShowHorizontalLines()) {
2161 g.setColor(table.getGridColor());
2162 Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
2163 rcr.x += distance;
2164 int x1 = rcr.x;
2165 int y1 = rcr.y;
2166 int x2 = x1 + rcr.width - 1;
2167 int y2 = y1 + rcr.height - 1;
2168 g.drawLine(x1, y2, x2, y2);
2169 }
2170 }
2171 }
2172
2173 private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
2174 if (table.isEditing() && table.getEditingRow()==row &&
2175 table.getEditingColumn()==column) {
2176 Component component = table.getEditorComponent();
2177 component.setBounds(cellRect);
2178 component.validate();
2179 }
2180 else {
2181 TableCellRenderer renderer = table.getCellRenderer(row, column);
2182 Component component = table.prepareRenderer(renderer, row, column);
2183 rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
2184 cellRect.width, cellRect.height, true);
2185 }
2186 }
2187
2188 private static int getAdjustedLead(JTable table,
2189 boolean row,
2190 ListSelectionModel model) {
2191
2192 int index = model.getLeadSelectionIndex();
2193 int compare = row ? table.getRowCount() : table.getColumnCount();
2194 return index < compare ? index : -1;
2195 }
2196
2197 private static int getAdjustedLead(JTable table, boolean row) {
2198 return row ? getAdjustedLead(table, row, table.getSelectionModel())
2199 : getAdjustedLead(table, row, table.getColumnModel().getSelectionModel());
2200 }
2201
2202
2203 private static final TransferHandler defaultTransferHandler = new TableTransferHandler();
2204
2205 @SuppressWarnings("serial") // JDK-implementation class
2206 static class TableTransferHandler extends TransferHandler implements UIResource {
2207
2208 /**
2209 * Create a Transferable to use as the source for a data transfer.
2210 *
2211 * @param c The component holding the data to be transfered. This
2212 * argument is provided to enable sharing of TransferHandlers by
2213 * multiple components.
2214 * @return The representation of the data to be transfered.
2215 *
2216 */
2217 protected Transferable createTransferable(JComponent c) {
2218 if (c instanceof JTable) {
2219 JTable table = (JTable) c;
2220 int[] rows;
2221 int[] cols;
2222
2223 if (!table.getRowSelectionAllowed() && !table.getColumnSelectionAllowed()) {
2224 return null;
2225 }
2226
2227 if (!table.getRowSelectionAllowed()) {
2228 int rowCount = table.getRowCount();
2229
2230 rows = new int[rowCount];
2231 for (int counter = 0; counter < rowCount; counter++) {
2232 rows[counter] = counter;
2233 }
2234 } else {
2235 rows = table.getSelectedRows();
2236 }
2237
2238 if (!table.getColumnSelectionAllowed()) {
2239 int colCount = table.getColumnCount();
2240
2241 cols = new int[colCount];
2242 for (int counter = 0; counter < colCount; counter++) {
2243 cols[counter] = counter;
2244 }
2245 } else {
2246 cols = table.getSelectedColumns();
2247 }
2248
2249 if (rows == null || cols == null || rows.length == 0 || cols.length == 0) {
2250 return null;
2251 }
2252
2253 StringBuilder plainStr = new StringBuilder();
2254 StringBuilder htmlStr = new StringBuilder();
2255
2256 htmlStr.append("<html>\n<body>\n<table>\n");
2257
2258 for (int row = 0; row < rows.length; row++) {
2259 htmlStr.append("<tr>\n");
2260 for (int col = 0; col < cols.length; col++) {
2261 Object obj = table.getValueAt(rows[row], cols[col]);
2262 String val = ((obj == null) ? "" : obj.toString());
2263 plainStr.append(val).append('\t');
2264 htmlStr.append(" <td>").append(val).append("</td>\n");
2265 }
2266 // we want a newline at the end of each line and not a tab
2267 plainStr.deleteCharAt(plainStr.length() - 1).append('\n');
2268 htmlStr.append("</tr>\n");
2269 }
2270
2271 // remove the last newline
2272 plainStr.deleteCharAt(plainStr.length() - 1);
2273 htmlStr.append("</table>\n</body>\n</html>");
2274
2275 return new BasicTransferable(plainStr.toString(), htmlStr.toString());
2276 }
2277
2278 return null;
2279 }
2280
2281 public int getSourceActions(JComponent c) {
2282 return COPY;
2283 }
2284
2285 }
2286 } // End of Class BasicTableUI