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 (rect != null) {
1324 table.repaint(rect);
1325 }
1326 return;
1327 }
1328
1329 if (loc.isInsertRow()) {
1330 Rectangle rect = extendRect(getHDropLineRect(loc), true);
1331 if (rect != null) {
1332 table.repaint(rect);
1333 }
1334 }
1335
1336 if (loc.isInsertColumn()) {
1337 Rectangle rect = extendRect(getVDropLineRect(loc), false);
1338 if (rect != null) {
1339 table.repaint(rect);
1340 }
1341 }
1342 }
1343 }
1344
1345
1346 /*
1347 * Returns true if the given point is outside the preferredSize of the
1348 * item at the given row of the table. (Column must be 0).
1349 * Returns false if the "Table.isFileList" client property is not set.
1350 */
1351 private boolean pointOutsidePrefSize(int row, int column, Point p) {
1352 if (!isFileList) {
1353 return false;
1354 }
1355
1356 return SwingUtilities2.pointOutsidePrefSize(table, row, column, p);
1357 }
1358
1359 //
1360 // Factory methods for the Listeners
1361 //
1362
1363 private Handler getHandler() {
1364 if (handler == null) {
1365 handler = new Handler();
1366 }
1367 return handler;
1368 }
1369
1370 /**
1371 * Creates the key listener for handling keyboard navigation in the {@code JTable}.
1372 *
1373 * @return the key listener for handling keyboard navigation in the {@code JTable}
1374 */
1375 protected KeyListener createKeyListener() {
1376 return null;
1377 }
1378
1379 /**
1380 * Creates the focus listener for handling keyboard navigation in the {@code JTable}.
1381 *
1382 * @return the focus listener for handling keyboard navigation in the {@code JTable}
1383 */
1384 protected FocusListener createFocusListener() {
1385 return getHandler();
1386 }
1387
1388 /**
1389 * Creates the mouse listener for the {@code JTable}.
1390 *
1391 * @return the mouse listener for the {@code JTable}
1392 */
1393 protected MouseInputListener createMouseInputListener() {
1394 return getHandler();
1395 }
1396
1397 //
1398 // The installation/uninstall procedures and support
1399 //
1400
1401 /**
1402 * Returns a new instance of {@code BasicTableUI}.
1403 *
1404 * @param c a component
1405 * @return a new instance of {@code BasicTableUI}
1406 */
1407 public static ComponentUI createUI(JComponent c) {
1408 return new BasicTableUI();
1409 }
1410
1411 // Installation
1412
1413 public void installUI(JComponent c) {
1414 table = (JTable)c;
1415
1416 rendererPane = new CellRendererPane();
1417 table.add(rendererPane);
1418 installDefaults();
1419 installDefaults2();
1420 installListeners();
1421 installKeyboardActions();
1422 }
1423
1424 /**
1425 * Initialize JTable properties, e.g. font, foreground, and background.
1426 * The font, foreground, and background properties are only set if their
1427 * current value is either null or a UIResource, other properties are set
1428 * if the current value is null.
1429 *
1430 * @see #installUI
1431 */
1432 protected void installDefaults() {
1433 LookAndFeel.installColorsAndFont(table, "Table.background",
1434 "Table.foreground", "Table.font");
1435 // JTable's original row height is 16. To correctly display the
1436 // contents on Linux we should have set it to 18, Windows 19 and
1437 // Solaris 20. As these values vary so much it's too hard to
1438 // be backward compatable and try to update the row height, we're
1439 // therefor NOT going to adjust the row height based on font. If the
1440 // developer changes the font, it's there responsability to update
1441 // the row height.
1442
1443 LookAndFeel.installProperty(table, "opaque", Boolean.TRUE);
1444
1445 Color sbg = table.getSelectionBackground();
1446 if (sbg == null || sbg instanceof UIResource) {
1447 sbg = UIManager.getColor("Table.selectionBackground");
1448 table.setSelectionBackground(sbg != null ? sbg : UIManager.getColor("textHighlight"));
1449 }
1450
1451 Color sfg = table.getSelectionForeground();
1452 if (sfg == null || sfg instanceof UIResource) {
1453 sfg = UIManager.getColor("Table.selectionForeground");
1454 table.setSelectionForeground(sfg != null ? sfg : UIManager.getColor("textHighlightText"));
1455 }
1456
1457 Color gridColor = table.getGridColor();
1458 if (gridColor == null || gridColor instanceof UIResource) {
1459 gridColor = UIManager.getColor("Table.gridColor");
1460 table.setGridColor(gridColor != null ? gridColor : Color.GRAY);
1461 }
1462
1463 // install the scrollpane border
1464 Container parent = SwingUtilities.getUnwrappedParent(table); // should be viewport
1465 if (parent != null) {
1466 parent = parent.getParent(); // should be the scrollpane
1467 if (parent != null && parent instanceof JScrollPane) {
1468 LookAndFeel.installBorder((JScrollPane)parent, "Table.scrollPaneBorder");
1469 }
1470 }
1471
1472 isFileList = Boolean.TRUE.equals(table.getClientProperty("Table.isFileList"));
1473 }
1474
1475 private void installDefaults2() {
1476 TransferHandler th = table.getTransferHandler();
1477 if (th == null || th instanceof UIResource) {
1478 table.setTransferHandler(defaultTransferHandler);
1479 // default TransferHandler doesn't support drop
1480 // so we don't want drop handling
1481 if (table.getDropTarget() instanceof UIResource) {
1482 table.setDropTarget(null);
1483 }
1484 }
1485 }
1486
1487 /**
1488 * Attaches listeners to the JTable.
1489 */
1490 protected void installListeners() {
1491 focusListener = createFocusListener();
1492 keyListener = createKeyListener();
1493 mouseInputListener = createMouseInputListener();
1494
1495 table.addFocusListener(focusListener);
1496 table.addKeyListener(keyListener);
1497 table.addMouseListener(mouseInputListener);
1498 table.addMouseMotionListener(mouseInputListener);
1499 table.addPropertyChangeListener(getHandler());
1500 if (isFileList) {
1501 table.getSelectionModel().addListSelectionListener(getHandler());
1502 }
1503 }
1504
1505 /**
1506 * Register all keyboard actions on the JTable.
1507 */
1508 protected void installKeyboardActions() {
1509 LazyActionMap.installLazyActionMap(table, BasicTableUI.class,
1510 "Table.actionMap");
1511
1512 InputMap inputMap = getInputMap(JComponent.
1513 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
1514 SwingUtilities.replaceUIInputMap(table,
1515 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
1516 inputMap);
1517 }
1518
1519 InputMap getInputMap(int condition) {
1520 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
1521 InputMap keyMap =
1522 (InputMap)DefaultLookup.get(table, this,
1523 "Table.ancestorInputMap");
1524 InputMap rtlKeyMap;
1525
1526 if (table.getComponentOrientation().isLeftToRight() ||
1527 ((rtlKeyMap = (InputMap)DefaultLookup.get(table, this,
1528 "Table.ancestorInputMap.RightToLeft")) == null)) {
1529 return keyMap;
1530 } else {
1531 rtlKeyMap.setParent(keyMap);
1532 return rtlKeyMap;
1533 }
1534 }
1535 return null;
1536 }
1537
1538 static void loadActionMap(LazyActionMap map) {
1539 // IMPORTANT: There is a very close coupling between the parameters
1540 // passed to the Actions constructor. Only certain parameter
1541 // combinations are supported. For example, the following Action would
1542 // not work as expected:
1543 // new Actions(Actions.NEXT_ROW_CELL, 1, 4, false, true)
1544 // Actions which move within the selection only (having a true
1545 // inSelection parameter) require that one of dx or dy be
1546 // zero and the other be -1 or 1. The point of this warning is
1547 // that you should be very careful about making sure a particular
1548 // combination of parameters is supported before changing or
1549 // adding anything here.
1550
1551 map.put(new Actions(Actions.NEXT_COLUMN, 1, 0,
1552 false, false));
1553 map.put(new Actions(Actions.NEXT_COLUMN_CHANGE_LEAD, 1, 0,
1554 false, false));
1555 map.put(new Actions(Actions.PREVIOUS_COLUMN, -1, 0,
1556 false, false));
1557 map.put(new Actions(Actions.PREVIOUS_COLUMN_CHANGE_LEAD, -1, 0,
1558 false, false));
1559 map.put(new Actions(Actions.NEXT_ROW, 0, 1,
1560 false, false));
1561 map.put(new Actions(Actions.NEXT_ROW_CHANGE_LEAD, 0, 1,
1562 false, false));
1563 map.put(new Actions(Actions.PREVIOUS_ROW, 0, -1,
1564 false, false));
1565 map.put(new Actions(Actions.PREVIOUS_ROW_CHANGE_LEAD, 0, -1,
1566 false, false));
1567 map.put(new Actions(Actions.NEXT_COLUMN_EXTEND_SELECTION,
1568 1, 0, true, false));
1569 map.put(new Actions(Actions.PREVIOUS_COLUMN_EXTEND_SELECTION,
1570 -1, 0, true, false));
1571 map.put(new Actions(Actions.NEXT_ROW_EXTEND_SELECTION,
1572 0, 1, true, false));
1573 map.put(new Actions(Actions.PREVIOUS_ROW_EXTEND_SELECTION,
1574 0, -1, true, false));
1575 map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION,
1576 false, false, true, false));
1577 map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION,
1578 false, true, true, false));
1579 map.put(new Actions(Actions.FIRST_COLUMN,
1580 false, false, false, true));
1581 map.put(new Actions(Actions.LAST_COLUMN,
1582 false, true, false, true));
1583
1584 map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION,
1585 true, false, true, false));
1586 map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION,
1587 true, true, true, false));
1588 map.put(new Actions(Actions.FIRST_COLUMN_EXTEND_SELECTION,
1589 true, false, false, true));
1590 map.put(new Actions(Actions.LAST_COLUMN_EXTEND_SELECTION,
1591 true, true, false, true));
1592
1593 map.put(new Actions(Actions.FIRST_ROW, false, false, true, true));
1594 map.put(new Actions(Actions.LAST_ROW, false, true, true, true));
1595
1596 map.put(new Actions(Actions.FIRST_ROW_EXTEND_SELECTION,
1597 true, false, true, true));
1598 map.put(new Actions(Actions.LAST_ROW_EXTEND_SELECTION,
1599 true, true, true, true));
1600
1601 map.put(new Actions(Actions.NEXT_COLUMN_CELL,
1602 1, 0, false, true));
1603 map.put(new Actions(Actions.PREVIOUS_COLUMN_CELL,
1604 -1, 0, false, true));
1605 map.put(new Actions(Actions.NEXT_ROW_CELL, 0, 1, false, true));
1606 map.put(new Actions(Actions.PREVIOUS_ROW_CELL,
1607 0, -1, false, true));
1608
1609 map.put(new Actions(Actions.SELECT_ALL));
1610 map.put(new Actions(Actions.CLEAR_SELECTION));
1611 map.put(new Actions(Actions.CANCEL_EDITING));
1612 map.put(new Actions(Actions.START_EDITING));
1613
1614 map.put(TransferHandler.getCutAction().getValue(Action.NAME),
1615 TransferHandler.getCutAction());
1616 map.put(TransferHandler.getCopyAction().getValue(Action.NAME),
1617 TransferHandler.getCopyAction());
1618 map.put(TransferHandler.getPasteAction().getValue(Action.NAME),
1619 TransferHandler.getPasteAction());
1620
1621 map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_SELECTION,
1622 false, false, false, false));
1623 map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_SELECTION,
1624 false, true, false, false));
1625 map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION,
1626 true, false, false, false));
1627 map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION,
1628 true, true, false, false));
1629
1630 map.put(new Actions(Actions.ADD_TO_SELECTION));
1631 map.put(new Actions(Actions.TOGGLE_AND_ANCHOR));
1632 map.put(new Actions(Actions.EXTEND_TO));
1633 map.put(new Actions(Actions.MOVE_SELECTION_TO));
1634 map.put(new Actions(Actions.FOCUS_HEADER));
1635 }
1636
1637 // Uninstallation
1638
1639 public void uninstallUI(JComponent c) {
1640 uninstallDefaults();
1641 uninstallListeners();
1642 uninstallKeyboardActions();
1643
1644 table.remove(rendererPane);
1645 rendererPane = null;
1646 table = null;
1647 }
1648
1649 /**
1650 * Uninstalls default properties.
1651 */
1652 protected void uninstallDefaults() {
1653 if (table.getTransferHandler() instanceof UIResource) {
1654 table.setTransferHandler(null);
1655 }
1656 }
1657
1658 /**
1659 * Unregisters listeners.
1660 */
1661 protected void uninstallListeners() {
1662 table.removeFocusListener(focusListener);
1663 table.removeKeyListener(keyListener);
1664 table.removeMouseListener(mouseInputListener);
1665 table.removeMouseMotionListener(mouseInputListener);
1666 table.removePropertyChangeListener(getHandler());
1667 if (isFileList) {
1668 table.getSelectionModel().removeListSelectionListener(getHandler());
1669 }
1670
1671 focusListener = null;
1672 keyListener = null;
1673 mouseInputListener = null;
1674 handler = null;
1675 }
1676
1677 /**
1678 * Unregisters keyboard actions.
1679 */
1680 protected void uninstallKeyboardActions() {
1681 SwingUtilities.replaceUIInputMap(table, JComponent.
1682 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
1683 SwingUtilities.replaceUIActionMap(table, null);
1684 }
1685
1686 /**
1687 * Returns the baseline.
1688 *
1689 * @throws NullPointerException {@inheritDoc}
1690 * @throws IllegalArgumentException {@inheritDoc}
1691 * @see javax.swing.JComponent#getBaseline(int, int)
1692 * @since 1.6
1693 */
1694 public int getBaseline(JComponent c, int width, int height) {
1695 super.getBaseline(c, width, height);
1696 UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults();
1697 Component renderer = (Component)lafDefaults.get(
1698 BASELINE_COMPONENT_KEY);
1699 if (renderer == null) {
1700 DefaultTableCellRenderer tcr = new DefaultTableCellRenderer();
1701 renderer = tcr.getTableCellRendererComponent(
1702 table, "a", false, false, -1, -1);
1703 lafDefaults.put(BASELINE_COMPONENT_KEY, renderer);
1704 }
1705 renderer.setFont(table.getFont());
1706 int rowMargin = table.getRowMargin();
1707 return renderer.getBaseline(Integer.MAX_VALUE, table.getRowHeight() -
1708 rowMargin) + rowMargin / 2;
1709 }
1710
1711 /**
1712 * Returns an enum indicating how the baseline of the component
1713 * changes as the size changes.
1714 *
1715 * @throws NullPointerException {@inheritDoc}
1716 * @see javax.swing.JComponent#getBaseline(int, int)
1717 * @since 1.6
1718 */
1719 public Component.BaselineResizeBehavior getBaselineResizeBehavior(
1720 JComponent c) {
1721 super.getBaselineResizeBehavior(c);
1722 return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
1723 }
1724
1725 //
1726 // Size Methods
1727 //
1728
1729 private Dimension createTableSize(long width) {
1730 int height = 0;
1731 int rowCount = table.getRowCount();
1732 if (rowCount > 0 && table.getColumnCount() > 0) {
1733 Rectangle r = table.getCellRect(rowCount-1, 0, true);
1734 height = r.y + r.height;
1735 }
1736 // Width is always positive. The call to abs() is a workaround for
1737 // a bug in the 1.1.6 JIT on Windows.
1738 long tmp = Math.abs(width);
1739 if (tmp > Integer.MAX_VALUE) {
1740 tmp = Integer.MAX_VALUE;
1741 }
1742 return new Dimension((int)tmp, height);
1743 }
1744
1745 /**
1746 * Return the minimum size of the table. The minimum height is the
1747 * row height times the number of rows.
1748 * The minimum width is the sum of the minimum widths of each column.
1749 */
1750 public Dimension getMinimumSize(JComponent c) {
1751 long width = 0;
1752 Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns();
1753 while (enumeration.hasMoreElements()) {
1754 TableColumn aColumn = enumeration.nextElement();
1755 width = width + aColumn.getMinWidth();
1756 }
1757 return createTableSize(width);
1758 }
1759
1760 /**
1761 * Return the preferred size of the table. The preferred height is the
1762 * row height times the number of rows.
1763 * The preferred width is the sum of the preferred widths of each column.
1764 */
1765 public Dimension getPreferredSize(JComponent c) {
1766 long width = 0;
1767 Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns();
1768 while (enumeration.hasMoreElements()) {
1769 TableColumn aColumn = enumeration.nextElement();
1770 width = width + aColumn.getPreferredWidth();
1771 }
1772 return createTableSize(width);
1773 }
1774
1775 /**
1776 * Return the maximum size of the table. The maximum height is the
1777 * row heighttimes the number of rows.
1778 * The maximum width is the sum of the maximum widths of each column.
1779 */
1780 public Dimension getMaximumSize(JComponent c) {
1781 long width = 0;
1782 Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns();
1783 while (enumeration.hasMoreElements()) {
1784 TableColumn aColumn = enumeration.nextElement();
1785 width = width + aColumn.getMaxWidth();
1786 }
1787 return createTableSize(width);
1788 }
1789
1790 //
1791 // Paint methods and support
1792 //
1793
1794 /** Paint a representation of the <code>table</code> instance
1795 * that was set in installUI().
1796 */
1797 public void paint(Graphics g, JComponent c) {
1798 Rectangle clip = g.getClipBounds();
1799
1800 Rectangle bounds = table.getBounds();
1801 // account for the fact that the graphics has already been translated
1802 // into the table's bounds
1803 bounds.x = bounds.y = 0;
1804
1805 if (table.getRowCount() <= 0 || table.getColumnCount() <= 0 ||
1806 // this check prevents us from painting the entire table
1807 // when the clip doesn't intersect our bounds at all
1808 !bounds.intersects(clip)) {
1809
1810 paintDropLines(g);
1811 return;
1812 }
1813
1814 boolean ltr = table.getComponentOrientation().isLeftToRight();
1815
1816 // compute the visible part of table which needs to be painted
1817 Rectangle visibleBounds = clip.intersection(bounds);
1818 Point upperLeft = visibleBounds.getLocation();
1819 Point lowerRight = new Point(visibleBounds.x + visibleBounds.width - 1,
1820 visibleBounds.y + visibleBounds.height - 1);
1821
1822 int rMin = table.rowAtPoint(upperLeft);
1823 int rMax = table.rowAtPoint(lowerRight);
1824 // This should never happen (as long as our bounds intersect the clip,
1825 // which is why we bail above if that is the case).
1826 if (rMin == -1) {
1827 rMin = 0;
1828 }
1829 // If the table does not have enough rows to fill the view we'll get -1.
1830 // (We could also get -1 if our bounds don't intersect the clip,
1831 // which is why we bail above if that is the case).
1832 // Replace this with the index of the last row.
1833 if (rMax == -1) {
1834 rMax = table.getRowCount()-1;
1835 }
1836
1837 int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight);
1838 int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);
1839 // This should never happen.
1840 if (cMin == -1) {
1841 cMin = 0;
1842 }
1843 // If the table does not have enough columns to fill the view we'll get -1.
1844 // Replace this with the index of the last column.
1845 if (cMax == -1) {
1846 cMax = table.getColumnCount()-1;
1847 }
1848
1849 Container comp = SwingUtilities.getUnwrappedParent(table);
1850 if (comp != null) {
1851 comp = comp.getParent();
1852 }
1853
1854 if (comp != null && !(comp instanceof JViewport) && !(comp instanceof JScrollPane)) {
1855 // We did rMax-1 to paint the same number of rows that are drawn on console
1856 // otherwise 1 extra row is printed per page than that are displayed
1857 // when there is no scrollPane and we do printing of table
1858 // but not when rmax is already pointing to index of last row
1859 if (rMax != (table.getRowCount() - 1)) {
1860 rMax = rMax - 1;
1861 }
1862 }
1863
1864 // Paint the grid.
1865 paintGrid(g, rMin, rMax, cMin, cMax);
1866
1867 // Paint the cells.
1868 paintCells(g, rMin, rMax, cMin, cMax);
1869
1870 paintDropLines(g);
1871 }
1872
1873 private void paintDropLines(Graphics g) {
1874 JTable.DropLocation loc = table.getDropLocation();
1875 if (loc == null) {
1876 return;
1877 }
1878
1879 Color color = UIManager.getColor("Table.dropLineColor");
1880 Color shortColor = UIManager.getColor("Table.dropLineShortColor");
1881 if (color == null && shortColor == null) {
1882 return;
1883 }
1884
1885 Rectangle rect;
1886
1887 rect = getHDropLineRect(loc);
1888 if (rect != null) {
1889 int x = rect.x;
1890 int w = rect.width;
1891 if (color != null) {
1892 extendRect(rect, true);
1893 g.setColor(color);
1894 g.fillRect(rect.x, rect.y, rect.width, rect.height);
1895 }
1896 if (!loc.isInsertColumn() && shortColor != null) {
1897 g.setColor(shortColor);
1898 g.fillRect(x, rect.y, w, rect.height);
1899 }
1900 }
1901
1902 rect = getVDropLineRect(loc);
1903 if (rect != null) {
1904 int y = rect.y;
1905 int h = rect.height;
1906 if (color != null) {
1907 extendRect(rect, false);
1908 g.setColor(color);
1909 g.fillRect(rect.x, rect.y, rect.width, rect.height);
1910 }
1911 if (!loc.isInsertRow() && shortColor != null) {
1912 g.setColor(shortColor);
1913 g.fillRect(rect.x, y, rect.width, h);
1914 }
1915 }
1916 }
1917
1918 private Rectangle getHDropLineRect(JTable.DropLocation loc) {
1919 if (!loc.isInsertRow()) {
1920 return null;
1921 }
1922
1923 int row = loc.getRow();
1924 int col = loc.getColumn();
1925 if (col >= table.getColumnCount()) {
1926 col--;
1927 }
1928
1929 Rectangle rect = table.getCellRect(row, col, true);
1930
1931 if (row >= table.getRowCount()) {
1932 row--;
1933 Rectangle prevRect = table.getCellRect(row, col, true);
1934 rect.y = prevRect.y + prevRect.height;
1935 }
1936
1937 if (rect.y == 0) {
1938 rect.y = -1;
1939 } else {
1940 rect.y -= 2;
1941 }
1942
1943 rect.height = 3;
1944
1945 return rect;
1946 }
1947
1948 private Rectangle getVDropLineRect(JTable.DropLocation loc) {
1949 if (!loc.isInsertColumn()) {
1950 return null;
1951 }
1952
1953 boolean ltr = table.getComponentOrientation().isLeftToRight();
1954 int col = loc.getColumn();
1955 Rectangle rect = table.getCellRect(loc.getRow(), col, true);
1956
1957 if (col >= table.getColumnCount()) {
1958 col--;
1959 rect = table.getCellRect(loc.getRow(), col, true);
1960 if (ltr) {
1961 rect.x = rect.x + rect.width;
1962 }
1963 } else if (!ltr) {
1964 rect.x = rect.x + rect.width;
1965 }
1966
1967 if (rect.x == 0) {
1968 rect.x = -1;
1969 } else {
1970 rect.x -= 2;
1971 }
1972
1973 rect.width = 3;
1974
1975 return rect;
1976 }
1977
1978 private Rectangle extendRect(Rectangle rect, boolean horizontal) {
1979 if (rect == null) {
1980 return rect;
1981 }
1982
1983 if (horizontal) {
1984 rect.x = 0;
1985 rect.width = table.getWidth();
1986 } else {
1987 rect.y = 0;
1988
1989 if (table.getRowCount() != 0) {
1990 Rectangle lastRect = table.getCellRect(table.getRowCount() - 1, 0, true);
1991 rect.height = lastRect.y + lastRect.height;
1992 } else {
1993 rect.height = table.getHeight();
1994 }
1995 }
1996
1997 return rect;
1998 }
1999
2000 /*
2001 * Paints the grid lines within <I>aRect</I>, using the grid
2002 * color set with <I>setGridColor</I>. Paints vertical lines
2003 * if <code>getShowVerticalLines()</code> returns true and paints
2004 * horizontal lines if <code>getShowHorizontalLines()</code>
2005 * returns true.
2006 */
2007 private void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) {
2008 g.setColor(table.getGridColor());
2009
2010 Rectangle minCell = table.getCellRect(rMin, cMin, true);
2011 Rectangle maxCell = table.getCellRect(rMax, cMax, true);
2012 Rectangle damagedArea = minCell.union( maxCell );
2013
2014 if (table.getShowHorizontalLines()) {
2015 int tableWidth = damagedArea.x + damagedArea.width;
2016 int y = damagedArea.y;
2017 for (int row = rMin; row <= rMax; row++) {
2018 y += table.getRowHeight(row);
2019 g.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1);
2020 }
2021 }
2022 if (table.getShowVerticalLines()) {
2023 TableColumnModel cm = table.getColumnModel();
2024 int tableHeight = damagedArea.y + damagedArea.height;
2025 int x;
2026 if (table.getComponentOrientation().isLeftToRight()) {
2027 x = damagedArea.x;
2028 for (int column = cMin; column <= cMax; column++) {
2029 int w = cm.getColumn(column).getWidth();
2030 x += w;
2031 g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
2032 }
2033 } else {
2034 x = damagedArea.x;
2035 for (int column = cMax; column >= cMin; column--) {
2036 int w = cm.getColumn(column).getWidth();
2037 x += w;
2038 g.drawLine(x - 1, 0, x - 1, tableHeight - 1);
2039 }
2040 }
2041 }
2042 }
2043
2044 private int viewIndexForColumn(TableColumn aColumn) {
2045 TableColumnModel cm = table.getColumnModel();
2046 for (int column = 0; column < cm.getColumnCount(); column++) {
2047 if (cm.getColumn(column) == aColumn) {
2048 return column;
2049 }
2050 }
2051 return -1;
2052 }
2053
2054 private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
2055 JTableHeader header = table.getTableHeader();
2056 TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
2057
2058 TableColumnModel cm = table.getColumnModel();
2059 int columnMargin = cm.getColumnMargin();
2060
2061 Rectangle cellRect;
2062 TableColumn aColumn;
2063 int columnWidth;
2064 if (table.getComponentOrientation().isLeftToRight()) {
2065 for(int row = rMin; row <= rMax; row++) {
2066 cellRect = table.getCellRect(row, cMin, false);
2067 for(int column = cMin; column <= cMax; column++) {
2068 aColumn = cm.getColumn(column);
2069 columnWidth = aColumn.getWidth();
2070 cellRect.width = columnWidth - columnMargin;
2071 if (aColumn != draggedColumn) {
2072 paintCell(g, cellRect, row, column);
2073 }
2074 cellRect.x += columnWidth;
2075 }
2076 }
2077 } else {
2078 for(int row = rMin; row <= rMax; row++) {
2079 cellRect = table.getCellRect(row, cMin, false);
2080 aColumn = cm.getColumn(cMin);
2081 if (aColumn != draggedColumn) {
2082 columnWidth = aColumn.getWidth();
2083 cellRect.width = columnWidth - columnMargin;
2084 paintCell(g, cellRect, row, cMin);
2085 }
2086 for(int column = cMin+1; column <= cMax; column++) {
2087 aColumn = cm.getColumn(column);
2088 columnWidth = aColumn.getWidth();
2089 cellRect.width = columnWidth - columnMargin;
2090 cellRect.x -= columnWidth;
2091 if (aColumn != draggedColumn) {
2092 paintCell(g, cellRect, row, column);
2093 }
2094 }
2095 }
2096 }
2097
2098 // Paint the dragged column if we are dragging.
2099 if (draggedColumn != null) {
2100 paintDraggedArea(g, rMin, rMax, draggedColumn, header.getDraggedDistance());
2101 }
2102
2103 // Remove any renderers that may be left in the rendererPane.
2104 rendererPane.removeAll();
2105 }
2106
2107 private void paintDraggedArea(Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) {
2108 int draggedColumnIndex = viewIndexForColumn(draggedColumn);
2109
2110 Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
2111 Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
2112
2113 Rectangle vacatedColumnRect = minCell.union(maxCell);
2114
2115 // Paint a gray well in place of the moving column.
2116 g.setColor(table.getParent().getBackground());
2117 g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
2118 vacatedColumnRect.width, vacatedColumnRect.height);
2119
2120 // Move to the where the cell has been dragged.
2121 vacatedColumnRect.x += distance;
2122
2123 // Fill the background.
2124 g.setColor(table.getBackground());
2125 g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
2126 vacatedColumnRect.width, vacatedColumnRect.height);
2127
2128 // Paint the vertical grid lines if necessary.
2129 if (table.getShowVerticalLines()) {
2130 g.setColor(table.getGridColor());
2131 int x1 = vacatedColumnRect.x;
2132 int y1 = vacatedColumnRect.y;
2133 int x2 = x1 + vacatedColumnRect.width - 1;
2134 int y2 = y1 + vacatedColumnRect.height - 1;
2135 // Left
2136 g.drawLine(x1-1, y1, x1-1, y2);
2137 // Right
2138 g.drawLine(x2, y1, x2, y2);
2139 }
2140
2141 for(int row = rMin; row <= rMax; row++) {
2142 // Render the cell value
2143 Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
2144 r.x += distance;
2145 paintCell(g, r, row, draggedColumnIndex);
2146
2147 // Paint the (lower) horizontal grid line if necessary.
2148 if (table.getShowHorizontalLines()) {
2149 g.setColor(table.getGridColor());
2150 Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
2151 rcr.x += distance;
2152 int x1 = rcr.x;
2153 int y1 = rcr.y;
2154 int x2 = x1 + rcr.width - 1;
2155 int y2 = y1 + rcr.height - 1;
2156 g.drawLine(x1, y2, x2, y2);
2157 }
2158 }
2159 }
2160
2161 private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
2162 if (table.isEditing() && table.getEditingRow()==row &&
2163 table.getEditingColumn()==column) {
2164 Component component = table.getEditorComponent();
2165 component.setBounds(cellRect);
2166 component.validate();
2167 }
2168 else {
2169 TableCellRenderer renderer = table.getCellRenderer(row, column);
2170 Component component = table.prepareRenderer(renderer, row, column);
2171 rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
2172 cellRect.width, cellRect.height, true);
2173 }
2174 }
2175
2176 private static int getAdjustedLead(JTable table,
2177 boolean row,
2178 ListSelectionModel model) {
2179
2180 int index = model.getLeadSelectionIndex();
2181 int compare = row ? table.getRowCount() : table.getColumnCount();
2182 return index < compare ? index : -1;
2183 }
2184
2185 private static int getAdjustedLead(JTable table, boolean row) {
2186 return row ? getAdjustedLead(table, row, table.getSelectionModel())
2187 : getAdjustedLead(table, row, table.getColumnModel().getSelectionModel());
2188 }
2189
2190
2191 private static final TransferHandler defaultTransferHandler = new TableTransferHandler();
2192
2193 @SuppressWarnings("serial") // JDK-implementation class
2194 static class TableTransferHandler extends TransferHandler implements UIResource {
2195
2196 /**
2197 * Create a Transferable to use as the source for a data transfer.
2198 *
2199 * @param c The component holding the data to be transfered. This
2200 * argument is provided to enable sharing of TransferHandlers by
2201 * multiple components.
2202 * @return The representation of the data to be transfered.
2203 *
2204 */
2205 protected Transferable createTransferable(JComponent c) {
2206 if (c instanceof JTable) {
2207 JTable table = (JTable) c;
2208 int[] rows;
2209 int[] cols;
2210
2211 if (!table.getRowSelectionAllowed() && !table.getColumnSelectionAllowed()) {
2212 return null;
2213 }
2214
2215 if (!table.getRowSelectionAllowed()) {
2216 int rowCount = table.getRowCount();
2217
2218 rows = new int[rowCount];
2219 for (int counter = 0; counter < rowCount; counter++) {
2220 rows[counter] = counter;
2221 }
2222 } else {
2223 rows = table.getSelectedRows();
2224 }
2225
2226 if (!table.getColumnSelectionAllowed()) {
2227 int colCount = table.getColumnCount();
2228
2229 cols = new int[colCount];
2230 for (int counter = 0; counter < colCount; counter++) {
2231 cols[counter] = counter;
2232 }
2233 } else {
2234 cols = table.getSelectedColumns();
2235 }
2236
2237 if (rows == null || cols == null || rows.length == 0 || cols.length == 0) {
2238 return null;
2239 }
2240
2241 StringBuilder plainStr = new StringBuilder();
2242 StringBuilder htmlStr = new StringBuilder();
2243
2244 htmlStr.append("<html>\n<body>\n<table>\n");
2245
2246 for (int row = 0; row < rows.length; row++) {
2247 htmlStr.append("<tr>\n");
2248 for (int col = 0; col < cols.length; col++) {
2249 Object obj = table.getValueAt(rows[row], cols[col]);
2250 String val = ((obj == null) ? "" : obj.toString());
2251 plainStr.append(val).append('\t');
2252 htmlStr.append(" <td>").append(val).append("</td>\n");
2253 }
2254 // we want a newline at the end of each line and not a tab
2255 plainStr.deleteCharAt(plainStr.length() - 1).append('\n');
2256 htmlStr.append("</tr>\n");
2257 }
2258
2259 // remove the last newline
2260 plainStr.deleteCharAt(plainStr.length() - 1);
2261 htmlStr.append("</table>\n</body>\n</html>");
2262
2263 return new BasicTransferable(plainStr.toString(), htmlStr.toString());
2264 }
2265
2266 return null;
2267 }
2268
2269 public int getSourceActions(JComponent c) {
2270 return COPY;
2271 }
2272
2273 }
2274 } // End of Class BasicTableUI