BasicTableUI.java revision 11099:678faa7d1a6a
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 26package javax.swing.plaf.basic; 27 28import java.awt.*; 29import java.awt.datatransfer.*; 30import java.awt.dnd.*; 31import java.awt.event.*; 32import java.util.Enumeration; 33import java.util.EventObject; 34import java.util.Hashtable; 35import java.util.TooManyListenersException; 36import javax.swing.*; 37import javax.swing.event.*; 38import javax.swing.plaf.*; 39import javax.swing.text.*; 40import javax.swing.table.*; 41import javax.swing.plaf.basic.DragRecognitionSupport.BeforeDrag; 42import sun.swing.SwingUtilities2; 43 44 45import java.beans.PropertyChangeEvent; 46import java.beans.PropertyChangeListener; 47 48import sun.swing.DefaultLookup; 49import sun.swing.UIAction; 50 51/** 52 * BasicTableUI implementation 53 * 54 * @author Philip Milne 55 * @author Shannon Hickey (drag and drop) 56 */ 57public 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 public boolean isEnabled(Object sender) { 680 String key = getName(); 681 682 if (sender instanceof JTable && 683 Boolean.TRUE.equals(((JTable)sender).getClientProperty("Table.isFileList"))) { 684 if (key == NEXT_COLUMN || 685 key == NEXT_COLUMN_CELL || 686 key == NEXT_COLUMN_EXTEND_SELECTION || 687 key == NEXT_COLUMN_CHANGE_LEAD || 688 key == PREVIOUS_COLUMN || 689 key == PREVIOUS_COLUMN_CELL || 690 key == PREVIOUS_COLUMN_EXTEND_SELECTION || 691 key == PREVIOUS_COLUMN_CHANGE_LEAD || 692 key == SCROLL_LEFT_CHANGE_SELECTION || 693 key == SCROLL_LEFT_EXTEND_SELECTION || 694 key == SCROLL_RIGHT_CHANGE_SELECTION || 695 key == SCROLL_RIGHT_EXTEND_SELECTION || 696 key == FIRST_COLUMN || 697 key == FIRST_COLUMN_EXTEND_SELECTION || 698 key == LAST_COLUMN || 699 key == LAST_COLUMN_EXTEND_SELECTION || 700 key == NEXT_ROW_CELL || 701 key == PREVIOUS_ROW_CELL) { 702 703 return false; 704 } 705 } 706 707 if (key == CANCEL_EDITING && sender instanceof JTable) { 708 return ((JTable)sender).isEditing(); 709 } else if (key == NEXT_ROW_CHANGE_LEAD || 710 key == PREVIOUS_ROW_CHANGE_LEAD) { 711 // discontinuous selection actions are only enabled for 712 // DefaultListSelectionModel 713 return sender != null && 714 ((JTable)sender).getSelectionModel() 715 instanceof DefaultListSelectionModel; 716 } else if (key == NEXT_COLUMN_CHANGE_LEAD || 717 key == PREVIOUS_COLUMN_CHANGE_LEAD) { 718 // discontinuous selection actions are only enabled for 719 // DefaultListSelectionModel 720 return sender != null && 721 ((JTable)sender).getColumnModel().getSelectionModel() 722 instanceof DefaultListSelectionModel; 723 } else if (key == ADD_TO_SELECTION && sender instanceof JTable) { 724 // This action is typically bound to SPACE. 725 // If the table is already in an editing mode, SPACE should 726 // simply enter a space character into the table, and not 727 // select a cell. Likewise, if the lead cell is already selected 728 // then hitting SPACE should just enter a space character 729 // into the cell and begin editing. In both of these cases 730 // this action will be disabled. 731 JTable table = (JTable)sender; 732 int leadRow = getAdjustedLead(table, true); 733 int leadCol = getAdjustedLead(table, false); 734 return !(table.isEditing() || table.isCellSelected(leadRow, leadCol)); 735 } else if (key == FOCUS_HEADER && sender instanceof JTable) { 736 JTable table = (JTable)sender; 737 return table.getTableHeader() != null; 738 } 739 740 return true; 741 } 742 } 743 744 745// 746// The Table's Key listener 747// 748 749 /** 750 * This class should be treated as a "protected" inner class. 751 * Instantiate it only within subclasses of {@code BasicTableUI}. 752 * <p>As of Java 2 platform v1.3 this class is no longer used. 753 * Instead <code>JTable</code> 754 * overrides <code>processKeyBinding</code> to dispatch the event to 755 * the current <code>TableCellEditor</code>. 756 */ 757 public class KeyHandler implements KeyListener { 758 // NOTE: This class exists only for backward compatibility. All 759 // its functionality has been moved into Handler. If you need to add 760 // new functionality add it to the Handler, but make sure this 761 // class calls into the Handler. 762 public void keyPressed(KeyEvent e) { 763 getHandler().keyPressed(e); 764 } 765 766 public void keyReleased(KeyEvent e) { 767 getHandler().keyReleased(e); 768 } 769 770 public void keyTyped(KeyEvent e) { 771 getHandler().keyTyped(e); 772 } 773 } 774 775// 776// The Table's focus listener 777// 778 779 /** 780 * This class should be treated as a "protected" inner class. 781 * Instantiate it only within subclasses of {@code BasicTableUI}. 782 */ 783 public class FocusHandler implements FocusListener { 784 // NOTE: This class exists only for backward compatibility. All 785 // its functionality has been moved into Handler. If you need to add 786 // new functionality add it to the Handler, but make sure this 787 // class calls into the Handler. 788 public void focusGained(FocusEvent e) { 789 getHandler().focusGained(e); 790 } 791 792 public void focusLost(FocusEvent e) { 793 getHandler().focusLost(e); 794 } 795 } 796 797// 798// The Table's mouse and mouse motion listeners 799// 800 801 /** 802 * This class should be treated as a "protected" inner class. 803 * Instantiate it only within subclasses of BasicTableUI. 804 */ 805 public class MouseInputHandler implements MouseInputListener { 806 // NOTE: This class exists only for backward compatibility. All 807 // its functionality has been moved into Handler. If you need to add 808 // new functionality add it to the Handler, but make sure this 809 // class calls into the Handler. 810 public void mouseClicked(MouseEvent e) { 811 getHandler().mouseClicked(e); 812 } 813 814 public void mousePressed(MouseEvent e) { 815 getHandler().mousePressed(e); 816 } 817 818 public void mouseReleased(MouseEvent e) { 819 getHandler().mouseReleased(e); 820 } 821 822 public void mouseEntered(MouseEvent e) { 823 getHandler().mouseEntered(e); 824 } 825 826 public void mouseExited(MouseEvent e) { 827 getHandler().mouseExited(e); 828 } 829 830 public void mouseMoved(MouseEvent e) { 831 getHandler().mouseMoved(e); 832 } 833 834 public void mouseDragged(MouseEvent e) { 835 getHandler().mouseDragged(e); 836 } 837 } 838 839 private class Handler implements FocusListener, MouseInputListener, 840 PropertyChangeListener, ListSelectionListener, ActionListener, 841 BeforeDrag { 842 843 // FocusListener 844 private void repaintLeadCell( ) { 845 int lr = getAdjustedLead(table, true); 846 int lc = getAdjustedLead(table, false); 847 848 if (lr < 0 || lc < 0) { 849 return; 850 } 851 852 Rectangle dirtyRect = table.getCellRect(lr, lc, false); 853 table.repaint(dirtyRect); 854 } 855 856 public void focusGained(FocusEvent e) { 857 repaintLeadCell(); 858 } 859 860 public void focusLost(FocusEvent e) { 861 repaintLeadCell(); 862 } 863 864 865 // KeyListener 866 public void keyPressed(KeyEvent e) { } 867 868 public void keyReleased(KeyEvent e) { } 869 870 public void keyTyped(KeyEvent e) { 871 KeyStroke keyStroke = KeyStroke.getKeyStroke(e.getKeyChar(), 872 e.getModifiers()); 873 874 // We register all actions using ANCESTOR_OF_FOCUSED_COMPONENT 875 // which means that we might perform the appropriate action 876 // in the table and then forward it to the editor if the editor 877 // had focus. Make sure this doesn't happen by checking our 878 // InputMaps. 879 InputMap map = table.getInputMap(JComponent.WHEN_FOCUSED); 880 if (map != null && map.get(keyStroke) != null) { 881 return; 882 } 883 map = table.getInputMap(JComponent. 884 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 885 if (map != null && map.get(keyStroke) != null) { 886 return; 887 } 888 889 keyStroke = KeyStroke.getKeyStrokeForEvent(e); 890 891 // The AWT seems to generate an unconsumed \r event when 892 // ENTER (\n) is pressed. 893 if (e.getKeyChar() == '\r') { 894 return; 895 } 896 897 int leadRow = getAdjustedLead(table, true); 898 int leadColumn = getAdjustedLead(table, false); 899 if (leadRow != -1 && leadColumn != -1 && !table.isEditing()) { 900 if (!table.editCellAt(leadRow, leadColumn)) { 901 return; 902 } 903 } 904 905 // Forwarding events this way seems to put the component 906 // in a state where it believes it has focus. In reality 907 // the table retains focus - though it is difficult for 908 // a user to tell, since the caret is visible and flashing. 909 910 // Calling table.requestFocus() here, to get the focus back to 911 // the table, seems to have no effect. 912 913 Component editorComp = table.getEditorComponent(); 914 if (table.isEditing() && editorComp != null) { 915 if (editorComp instanceof JComponent) { 916 JComponent component = (JComponent)editorComp; 917 map = component.getInputMap(JComponent.WHEN_FOCUSED); 918 Object binding = (map != null) ? map.get(keyStroke) : null; 919 if (binding == null) { 920 map = component.getInputMap(JComponent. 921 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 922 binding = (map != null) ? map.get(keyStroke) : null; 923 } 924 if (binding != null) { 925 ActionMap am = component.getActionMap(); 926 Action action = (am != null) ? am.get(binding) : null; 927 if (action != null && SwingUtilities. 928 notifyAction(action, keyStroke, e, component, 929 e.getModifiers())) { 930 e.consume(); 931 } 932 } 933 } 934 } 935 } 936 937 938 // MouseInputListener 939 940 // Component receiving mouse events during editing. 941 // May not be editorComponent. 942 private Component dispatchComponent; 943 944 public void mouseClicked(MouseEvent e) {} 945 946 private void setDispatchComponent(MouseEvent e) { 947 Component editorComponent = table.getEditorComponent(); 948 Point p = e.getPoint(); 949 Point p2 = SwingUtilities.convertPoint(table, p, editorComponent); 950 dispatchComponent = 951 SwingUtilities.getDeepestComponentAt(editorComponent, 952 p2.x, p2.y); 953 SwingUtilities2.setSkipClickCount(dispatchComponent, 954 e.getClickCount() - 1); 955 } 956 957 private boolean repostEvent(MouseEvent e) { 958 // Check for isEditing() in case another event has 959 // caused the editor to be removed. See bug #4306499. 960 if (dispatchComponent == null || !table.isEditing()) { 961 return false; 962 } 963 MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e, 964 dispatchComponent); 965 dispatchComponent.dispatchEvent(e2); 966 return true; 967 } 968 969 private void setValueIsAdjusting(boolean flag) { 970 table.getSelectionModel().setValueIsAdjusting(flag); 971 table.getColumnModel().getSelectionModel(). 972 setValueIsAdjusting(flag); 973 } 974 975 // The row and column where the press occurred and the 976 // press event itself 977 private int pressedRow; 978 private int pressedCol; 979 private MouseEvent pressedEvent; 980 981 // Whether or not the mouse press (which is being considered as part 982 // of a drag sequence) also caused the selection change to be fully 983 // processed. 984 private boolean dragPressDidSelection; 985 986 // Set to true when a drag gesture has been fully recognized and DnD 987 // begins. Use this to ignore further mouse events which could be 988 // delivered if DnD is cancelled (via ESCAPE for example) 989 private boolean dragStarted; 990 991 // Whether or not we should start the editing timer on release 992 private boolean shouldStartTimer; 993 994 // To cache the return value of pointOutsidePrefSize since we use 995 // it multiple times. 996 private boolean outsidePrefSize; 997 998 // Used to delay the start of editing. 999 private Timer timer = null; 1000 1001 private boolean canStartDrag() { 1002 if (pressedRow == -1 || pressedCol == -1) { 1003 return false; 1004 } 1005 1006 if (isFileList) { 1007 return !outsidePrefSize; 1008 } 1009 1010 // if this is a single selection table 1011 if ((table.getSelectionModel().getSelectionMode() == 1012 ListSelectionModel.SINGLE_SELECTION) && 1013 (table.getColumnModel().getSelectionModel().getSelectionMode() == 1014 ListSelectionModel.SINGLE_SELECTION)) { 1015 1016 return true; 1017 } 1018 1019 return table.isCellSelected(pressedRow, pressedCol); 1020 } 1021 1022 public void mousePressed(MouseEvent e) { 1023 if (SwingUtilities2.shouldIgnore(e, table)) { 1024 return; 1025 } 1026 1027 if (table.isEditing() && !table.getCellEditor().stopCellEditing()) { 1028 Component editorComponent = table.getEditorComponent(); 1029 if (editorComponent != null && !editorComponent.hasFocus()) { 1030 SwingUtilities2.compositeRequestFocus(editorComponent); 1031 } 1032 return; 1033 } 1034 1035 Point p = e.getPoint(); 1036 pressedRow = table.rowAtPoint(p); 1037 pressedCol = table.columnAtPoint(p); 1038 outsidePrefSize = pointOutsidePrefSize(pressedRow, pressedCol, p); 1039 1040 if (isFileList) { 1041 shouldStartTimer = 1042 table.isCellSelected(pressedRow, pressedCol) && 1043 !e.isShiftDown() && 1044 !BasicGraphicsUtils.isMenuShortcutKeyDown(e) && 1045 !outsidePrefSize; 1046 } 1047 1048 if (table.getDragEnabled()) { 1049 mousePressedDND(e); 1050 } else { 1051 SwingUtilities2.adjustFocus(table); 1052 if (!isFileList) { 1053 setValueIsAdjusting(true); 1054 } 1055 adjustSelection(e); 1056 } 1057 } 1058 1059 private void mousePressedDND(MouseEvent e) { 1060 pressedEvent = e; 1061 boolean grabFocus = true; 1062 dragStarted = false; 1063 1064 if (canStartDrag() && DragRecognitionSupport.mousePressed(e)) { 1065 1066 dragPressDidSelection = false; 1067 1068 if (BasicGraphicsUtils.isMenuShortcutKeyDown(e) && isFileList) { 1069 // do nothing for control - will be handled on release 1070 // or when drag starts 1071 return; 1072 } else if (!e.isShiftDown() && table.isCellSelected(pressedRow, pressedCol)) { 1073 // clicking on something that's already selected 1074 // and need to make it the lead now 1075 table.getSelectionModel().addSelectionInterval(pressedRow, 1076 pressedRow); 1077 table.getColumnModel().getSelectionModel(). 1078 addSelectionInterval(pressedCol, pressedCol); 1079 1080 return; 1081 } 1082 1083 dragPressDidSelection = true; 1084 1085 // could be a drag initiating event - don't grab focus 1086 grabFocus = false; 1087 } else if (!isFileList) { 1088 // When drag can't happen, mouse drags might change the selection in the table 1089 // so we want the isAdjusting flag to be set 1090 setValueIsAdjusting(true); 1091 } 1092 1093 if (grabFocus) { 1094 SwingUtilities2.adjustFocus(table); 1095 } 1096 1097 adjustSelection(e); 1098 } 1099 1100 private void adjustSelection(MouseEvent e) { 1101 // Fix for 4835633 1102 if (outsidePrefSize) { 1103 // If shift is down in multi-select, we should just return. 1104 // For single select or non-shift-click, clear the selection 1105 if (e.getID() == MouseEvent.MOUSE_PRESSED && 1106 (!e.isShiftDown() || 1107 table.getSelectionModel().getSelectionMode() == 1108 ListSelectionModel.SINGLE_SELECTION)) { 1109 table.clearSelection(); 1110 TableCellEditor tce = table.getCellEditor(); 1111 if (tce != null) { 1112 tce.stopCellEditing(); 1113 } 1114 } 1115 return; 1116 } 1117 // The autoscroller can generate drag events outside the 1118 // table's range. 1119 if ((pressedCol == -1) || (pressedRow == -1)) { 1120 return; 1121 } 1122 1123 boolean dragEnabled = table.getDragEnabled(); 1124 1125 if (!dragEnabled && !isFileList && table.editCellAt(pressedRow, pressedCol, e)) { 1126 setDispatchComponent(e); 1127 repostEvent(e); 1128 } 1129 1130 CellEditor editor = table.getCellEditor(); 1131 if (dragEnabled || editor == null || editor.shouldSelectCell(e)) { 1132 table.changeSelection(pressedRow, pressedCol, 1133 BasicGraphicsUtils.isMenuShortcutKeyDown(e), 1134 e.isShiftDown()); 1135 } 1136 } 1137 1138 public void valueChanged(ListSelectionEvent e) { 1139 if (timer != null) { 1140 timer.stop(); 1141 timer = null; 1142 } 1143 } 1144 1145 public void actionPerformed(ActionEvent ae) { 1146 table.editCellAt(pressedRow, pressedCol, null); 1147 Component editorComponent = table.getEditorComponent(); 1148 if (editorComponent != null && !editorComponent.hasFocus()) { 1149 SwingUtilities2.compositeRequestFocus(editorComponent); 1150 } 1151 return; 1152 } 1153 1154 private void maybeStartTimer() { 1155 if (!shouldStartTimer) { 1156 return; 1157 } 1158 1159 if (timer == null) { 1160 timer = new Timer(1200, this); 1161 timer.setRepeats(false); 1162 } 1163 1164 timer.start(); 1165 } 1166 1167 public void mouseReleased(MouseEvent e) { 1168 if (SwingUtilities2.shouldIgnore(e, table)) { 1169 return; 1170 } 1171 1172 if (table.getDragEnabled()) { 1173 mouseReleasedDND(e); 1174 } else { 1175 if (isFileList) { 1176 maybeStartTimer(); 1177 } 1178 } 1179 1180 pressedEvent = null; 1181 repostEvent(e); 1182 dispatchComponent = null; 1183 setValueIsAdjusting(false); 1184 } 1185 1186 private void mouseReleasedDND(MouseEvent e) { 1187 MouseEvent me = DragRecognitionSupport.mouseReleased(e); 1188 if (me != null) { 1189 SwingUtilities2.adjustFocus(table); 1190 if (!dragPressDidSelection) { 1191 adjustSelection(me); 1192 } 1193 } 1194 1195 if (!dragStarted) { 1196 if (isFileList) { 1197 maybeStartTimer(); 1198 return; 1199 } 1200 1201 Point p = e.getPoint(); 1202 1203 if (pressedEvent != null && 1204 table.rowAtPoint(p) == pressedRow && 1205 table.columnAtPoint(p) == pressedCol && 1206 table.editCellAt(pressedRow, pressedCol, pressedEvent)) { 1207 1208 setDispatchComponent(pressedEvent); 1209 repostEvent(pressedEvent); 1210 1211 // This may appear completely odd, but must be done for backward 1212 // compatibility reasons. Developers have been known to rely on 1213 // a call to shouldSelectCell after editing has begun. 1214 CellEditor ce = table.getCellEditor(); 1215 if (ce != null) { 1216 ce.shouldSelectCell(pressedEvent); 1217 } 1218 } 1219 } 1220 } 1221 1222 public void mouseEntered(MouseEvent e) {} 1223 1224 public void mouseExited(MouseEvent e) {} 1225 1226 public void mouseMoved(MouseEvent e) {} 1227 1228 public void dragStarting(MouseEvent me) { 1229 dragStarted = true; 1230 1231 if (BasicGraphicsUtils.isMenuShortcutKeyDown(me) && isFileList) { 1232 table.getSelectionModel().addSelectionInterval(pressedRow, 1233 pressedRow); 1234 table.getColumnModel().getSelectionModel(). 1235 addSelectionInterval(pressedCol, pressedCol); 1236 } 1237 1238 pressedEvent = null; 1239 } 1240 1241 public void mouseDragged(MouseEvent e) { 1242 if (SwingUtilities2.shouldIgnore(e, table)) { 1243 return; 1244 } 1245 1246 if (table.getDragEnabled() && 1247 (DragRecognitionSupport.mouseDragged(e, this) || dragStarted)) { 1248 1249 return; 1250 } 1251 1252 repostEvent(e); 1253 1254 // Check isFileList: 1255 // Until we support drag-selection, dragging should not change 1256 // the selection (act like single-select). 1257 if (isFileList || table.isEditing()) { 1258 return; 1259 } 1260 1261 Point p = e.getPoint(); 1262 int row = table.rowAtPoint(p); 1263 int column = table.columnAtPoint(p); 1264 // The autoscroller can generate drag events outside the 1265 // table's range. 1266 if ((column == -1) || (row == -1)) { 1267 return; 1268 } 1269 1270 table.changeSelection(row, column, 1271 BasicGraphicsUtils.isMenuShortcutKeyDown(e), true); 1272 } 1273 1274 1275 // PropertyChangeListener 1276 public void propertyChange(PropertyChangeEvent event) { 1277 String changeName = event.getPropertyName(); 1278 1279 if ("componentOrientation" == changeName) { 1280 InputMap inputMap = getInputMap( 1281 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 1282 1283 SwingUtilities.replaceUIInputMap(table, 1284 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 1285 inputMap); 1286 1287 JTableHeader header = table.getTableHeader(); 1288 if (header != null) { 1289 header.setComponentOrientation( 1290 (ComponentOrientation)event.getNewValue()); 1291 } 1292 } else if ("dropLocation" == changeName) { 1293 JTable.DropLocation oldValue = (JTable.DropLocation)event.getOldValue(); 1294 repaintDropLocation(oldValue); 1295 repaintDropLocation(table.getDropLocation()); 1296 } else if ("Table.isFileList" == changeName) { 1297 isFileList = Boolean.TRUE.equals(table.getClientProperty("Table.isFileList")); 1298 table.revalidate(); 1299 table.repaint(); 1300 if (isFileList) { 1301 table.getSelectionModel().addListSelectionListener(getHandler()); 1302 } else { 1303 table.getSelectionModel().removeListSelectionListener(getHandler()); 1304 timer = null; 1305 } 1306 } else if ("selectionModel" == changeName) { 1307 if (isFileList) { 1308 ListSelectionModel old = (ListSelectionModel)event.getOldValue(); 1309 old.removeListSelectionListener(getHandler()); 1310 table.getSelectionModel().addListSelectionListener(getHandler()); 1311 } 1312 } 1313 } 1314 1315 private void repaintDropLocation(JTable.DropLocation loc) { 1316 if (loc == null) { 1317 return; 1318 } 1319 1320 if (!loc.isInsertRow() && !loc.isInsertColumn()) { 1321 Rectangle rect = table.getCellRect(loc.getRow(), loc.getColumn(), false); 1322 if (rect != null) { 1323 table.repaint(rect); 1324 } 1325 return; 1326 } 1327 1328 if (loc.isInsertRow()) { 1329 Rectangle rect = extendRect(getHDropLineRect(loc), true); 1330 if (rect != null) { 1331 table.repaint(rect); 1332 } 1333 } 1334 1335 if (loc.isInsertColumn()) { 1336 Rectangle rect = extendRect(getVDropLineRect(loc), false); 1337 if (rect != null) { 1338 table.repaint(rect); 1339 } 1340 } 1341 } 1342 } 1343 1344 1345 /* 1346 * Returns true if the given point is outside the preferredSize of the 1347 * item at the given row of the table. (Column must be 0). 1348 * Returns false if the "Table.isFileList" client property is not set. 1349 */ 1350 private boolean pointOutsidePrefSize(int row, int column, Point p) { 1351 if (!isFileList) { 1352 return false; 1353 } 1354 1355 return SwingUtilities2.pointOutsidePrefSize(table, row, column, p); 1356 } 1357 1358// 1359// Factory methods for the Listeners 1360// 1361 1362 private Handler getHandler() { 1363 if (handler == null) { 1364 handler = new Handler(); 1365 } 1366 return handler; 1367 } 1368 1369 /** 1370 * Creates the key listener for handling keyboard navigation in the {@code JTable}. 1371 * 1372 * @return the key listener for handling keyboard navigation in the {@code JTable} 1373 */ 1374 protected KeyListener createKeyListener() { 1375 return null; 1376 } 1377 1378 /** 1379 * Creates the focus listener for handling keyboard navigation in the {@code JTable}. 1380 * 1381 * @return the focus listener for handling keyboard navigation in the {@code JTable} 1382 */ 1383 protected FocusListener createFocusListener() { 1384 return getHandler(); 1385 } 1386 1387 /** 1388 * Creates the mouse listener for the {@code JTable}. 1389 * 1390 * @return the mouse listener for the {@code JTable} 1391 */ 1392 protected MouseInputListener createMouseInputListener() { 1393 return getHandler(); 1394 } 1395 1396// 1397// The installation/uninstall procedures and support 1398// 1399 1400 /** 1401 * Returns a new instance of {@code BasicTableUI}. 1402 * 1403 * @param c a component 1404 * @return a new instance of {@code BasicTableUI} 1405 */ 1406 public static ComponentUI createUI(JComponent c) { 1407 return new BasicTableUI(); 1408 } 1409 1410// Installation 1411 1412 public void installUI(JComponent c) { 1413 table = (JTable)c; 1414 1415 rendererPane = new CellRendererPane(); 1416 table.add(rendererPane); 1417 installDefaults(); 1418 installDefaults2(); 1419 installListeners(); 1420 installKeyboardActions(); 1421 } 1422 1423 /** 1424 * Initialize JTable properties, e.g. font, foreground, and background. 1425 * The font, foreground, and background properties are only set if their 1426 * current value is either null or a UIResource, other properties are set 1427 * if the current value is null. 1428 * 1429 * @see #installUI 1430 */ 1431 protected void installDefaults() { 1432 LookAndFeel.installColorsAndFont(table, "Table.background", 1433 "Table.foreground", "Table.font"); 1434 // JTable's original row height is 16. To correctly display the 1435 // contents on Linux we should have set it to 18, Windows 19 and 1436 // Solaris 20. As these values vary so much it's too hard to 1437 // be backward compatable and try to update the row height, we're 1438 // therefor NOT going to adjust the row height based on font. If the 1439 // developer changes the font, it's there responsability to update 1440 // the row height. 1441 1442 LookAndFeel.installProperty(table, "opaque", Boolean.TRUE); 1443 1444 Color sbg = table.getSelectionBackground(); 1445 if (sbg == null || sbg instanceof UIResource) { 1446 sbg = UIManager.getColor("Table.selectionBackground"); 1447 table.setSelectionBackground(sbg != null ? sbg : UIManager.getColor("textHighlight")); 1448 } 1449 1450 Color sfg = table.getSelectionForeground(); 1451 if (sfg == null || sfg instanceof UIResource) { 1452 sfg = UIManager.getColor("Table.selectionForeground"); 1453 table.setSelectionForeground(sfg != null ? sfg : UIManager.getColor("textHighlightText")); 1454 } 1455 1456 Color gridColor = table.getGridColor(); 1457 if (gridColor == null || gridColor instanceof UIResource) { 1458 gridColor = UIManager.getColor("Table.gridColor"); 1459 table.setGridColor(gridColor != null ? gridColor : Color.GRAY); 1460 } 1461 1462 // install the scrollpane border 1463 Container parent = SwingUtilities.getUnwrappedParent(table); // should be viewport 1464 if (parent != null) { 1465 parent = parent.getParent(); // should be the scrollpane 1466 if (parent != null && parent instanceof JScrollPane) { 1467 LookAndFeel.installBorder((JScrollPane)parent, "Table.scrollPaneBorder"); 1468 } 1469 } 1470 1471 isFileList = Boolean.TRUE.equals(table.getClientProperty("Table.isFileList")); 1472 } 1473 1474 private void installDefaults2() { 1475 TransferHandler th = table.getTransferHandler(); 1476 if (th == null || th instanceof UIResource) { 1477 table.setTransferHandler(defaultTransferHandler); 1478 // default TransferHandler doesn't support drop 1479 // so we don't want drop handling 1480 if (table.getDropTarget() instanceof UIResource) { 1481 table.setDropTarget(null); 1482 } 1483 } 1484 } 1485 1486 /** 1487 * Attaches listeners to the JTable. 1488 */ 1489 protected void installListeners() { 1490 focusListener = createFocusListener(); 1491 keyListener = createKeyListener(); 1492 mouseInputListener = createMouseInputListener(); 1493 1494 table.addFocusListener(focusListener); 1495 table.addKeyListener(keyListener); 1496 table.addMouseListener(mouseInputListener); 1497 table.addMouseMotionListener(mouseInputListener); 1498 table.addPropertyChangeListener(getHandler()); 1499 if (isFileList) { 1500 table.getSelectionModel().addListSelectionListener(getHandler()); 1501 } 1502 } 1503 1504 /** 1505 * Register all keyboard actions on the JTable. 1506 */ 1507 protected void installKeyboardActions() { 1508 LazyActionMap.installLazyActionMap(table, BasicTableUI.class, 1509 "Table.actionMap"); 1510 1511 InputMap inputMap = getInputMap(JComponent. 1512 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); 1513 SwingUtilities.replaceUIInputMap(table, 1514 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 1515 inputMap); 1516 } 1517 1518 InputMap getInputMap(int condition) { 1519 if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) { 1520 InputMap keyMap = 1521 (InputMap)DefaultLookup.get(table, this, 1522 "Table.ancestorInputMap"); 1523 InputMap rtlKeyMap; 1524 1525 if (table.getComponentOrientation().isLeftToRight() || 1526 ((rtlKeyMap = (InputMap)DefaultLookup.get(table, this, 1527 "Table.ancestorInputMap.RightToLeft")) == null)) { 1528 return keyMap; 1529 } else { 1530 rtlKeyMap.setParent(keyMap); 1531 return rtlKeyMap; 1532 } 1533 } 1534 return null; 1535 } 1536 1537 static void loadActionMap(LazyActionMap map) { 1538 // IMPORTANT: There is a very close coupling between the parameters 1539 // passed to the Actions constructor. Only certain parameter 1540 // combinations are supported. For example, the following Action would 1541 // not work as expected: 1542 // new Actions(Actions.NEXT_ROW_CELL, 1, 4, false, true) 1543 // Actions which move within the selection only (having a true 1544 // inSelection parameter) require that one of dx or dy be 1545 // zero and the other be -1 or 1. The point of this warning is 1546 // that you should be very careful about making sure a particular 1547 // combination of parameters is supported before changing or 1548 // adding anything here. 1549 1550 map.put(new Actions(Actions.NEXT_COLUMN, 1, 0, 1551 false, false)); 1552 map.put(new Actions(Actions.NEXT_COLUMN_CHANGE_LEAD, 1, 0, 1553 false, false)); 1554 map.put(new Actions(Actions.PREVIOUS_COLUMN, -1, 0, 1555 false, false)); 1556 map.put(new Actions(Actions.PREVIOUS_COLUMN_CHANGE_LEAD, -1, 0, 1557 false, false)); 1558 map.put(new Actions(Actions.NEXT_ROW, 0, 1, 1559 false, false)); 1560 map.put(new Actions(Actions.NEXT_ROW_CHANGE_LEAD, 0, 1, 1561 false, false)); 1562 map.put(new Actions(Actions.PREVIOUS_ROW, 0, -1, 1563 false, false)); 1564 map.put(new Actions(Actions.PREVIOUS_ROW_CHANGE_LEAD, 0, -1, 1565 false, false)); 1566 map.put(new Actions(Actions.NEXT_COLUMN_EXTEND_SELECTION, 1567 1, 0, true, false)); 1568 map.put(new Actions(Actions.PREVIOUS_COLUMN_EXTEND_SELECTION, 1569 -1, 0, true, false)); 1570 map.put(new Actions(Actions.NEXT_ROW_EXTEND_SELECTION, 1571 0, 1, true, false)); 1572 map.put(new Actions(Actions.PREVIOUS_ROW_EXTEND_SELECTION, 1573 0, -1, true, false)); 1574 map.put(new Actions(Actions.SCROLL_UP_CHANGE_SELECTION, 1575 false, false, true, false)); 1576 map.put(new Actions(Actions.SCROLL_DOWN_CHANGE_SELECTION, 1577 false, true, true, false)); 1578 map.put(new Actions(Actions.FIRST_COLUMN, 1579 false, false, false, true)); 1580 map.put(new Actions(Actions.LAST_COLUMN, 1581 false, true, false, true)); 1582 1583 map.put(new Actions(Actions.SCROLL_UP_EXTEND_SELECTION, 1584 true, false, true, false)); 1585 map.put(new Actions(Actions.SCROLL_DOWN_EXTEND_SELECTION, 1586 true, true, true, false)); 1587 map.put(new Actions(Actions.FIRST_COLUMN_EXTEND_SELECTION, 1588 true, false, false, true)); 1589 map.put(new Actions(Actions.LAST_COLUMN_EXTEND_SELECTION, 1590 true, true, false, true)); 1591 1592 map.put(new Actions(Actions.FIRST_ROW, false, false, true, true)); 1593 map.put(new Actions(Actions.LAST_ROW, false, true, true, true)); 1594 1595 map.put(new Actions(Actions.FIRST_ROW_EXTEND_SELECTION, 1596 true, false, true, true)); 1597 map.put(new Actions(Actions.LAST_ROW_EXTEND_SELECTION, 1598 true, true, true, true)); 1599 1600 map.put(new Actions(Actions.NEXT_COLUMN_CELL, 1601 1, 0, false, true)); 1602 map.put(new Actions(Actions.PREVIOUS_COLUMN_CELL, 1603 -1, 0, false, true)); 1604 map.put(new Actions(Actions.NEXT_ROW_CELL, 0, 1, false, true)); 1605 map.put(new Actions(Actions.PREVIOUS_ROW_CELL, 1606 0, -1, false, true)); 1607 1608 map.put(new Actions(Actions.SELECT_ALL)); 1609 map.put(new Actions(Actions.CLEAR_SELECTION)); 1610 map.put(new Actions(Actions.CANCEL_EDITING)); 1611 map.put(new Actions(Actions.START_EDITING)); 1612 1613 map.put(TransferHandler.getCutAction().getValue(Action.NAME), 1614 TransferHandler.getCutAction()); 1615 map.put(TransferHandler.getCopyAction().getValue(Action.NAME), 1616 TransferHandler.getCopyAction()); 1617 map.put(TransferHandler.getPasteAction().getValue(Action.NAME), 1618 TransferHandler.getPasteAction()); 1619 1620 map.put(new Actions(Actions.SCROLL_LEFT_CHANGE_SELECTION, 1621 false, false, false, false)); 1622 map.put(new Actions(Actions.SCROLL_RIGHT_CHANGE_SELECTION, 1623 false, true, false, false)); 1624 map.put(new Actions(Actions.SCROLL_LEFT_EXTEND_SELECTION, 1625 true, false, false, false)); 1626 map.put(new Actions(Actions.SCROLL_RIGHT_EXTEND_SELECTION, 1627 true, true, false, false)); 1628 1629 map.put(new Actions(Actions.ADD_TO_SELECTION)); 1630 map.put(new Actions(Actions.TOGGLE_AND_ANCHOR)); 1631 map.put(new Actions(Actions.EXTEND_TO)); 1632 map.put(new Actions(Actions.MOVE_SELECTION_TO)); 1633 map.put(new Actions(Actions.FOCUS_HEADER)); 1634 } 1635 1636// Uninstallation 1637 1638 public void uninstallUI(JComponent c) { 1639 uninstallDefaults(); 1640 uninstallListeners(); 1641 uninstallKeyboardActions(); 1642 1643 table.remove(rendererPane); 1644 rendererPane = null; 1645 table = null; 1646 } 1647 1648 /** 1649 * Uninstalls default properties. 1650 */ 1651 protected void uninstallDefaults() { 1652 if (table.getTransferHandler() instanceof UIResource) { 1653 table.setTransferHandler(null); 1654 } 1655 } 1656 1657 /** 1658 * Unregisters listeners. 1659 */ 1660 protected void uninstallListeners() { 1661 table.removeFocusListener(focusListener); 1662 table.removeKeyListener(keyListener); 1663 table.removeMouseListener(mouseInputListener); 1664 table.removeMouseMotionListener(mouseInputListener); 1665 table.removePropertyChangeListener(getHandler()); 1666 if (isFileList) { 1667 table.getSelectionModel().removeListSelectionListener(getHandler()); 1668 } 1669 1670 focusListener = null; 1671 keyListener = null; 1672 mouseInputListener = null; 1673 handler = null; 1674 } 1675 1676 /** 1677 * Unregisters keyboard actions. 1678 */ 1679 protected void uninstallKeyboardActions() { 1680 SwingUtilities.replaceUIInputMap(table, JComponent. 1681 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); 1682 SwingUtilities.replaceUIActionMap(table, null); 1683 } 1684 1685 /** 1686 * Returns the baseline. 1687 * 1688 * @throws NullPointerException {@inheritDoc} 1689 * @throws IllegalArgumentException {@inheritDoc} 1690 * @see javax.swing.JComponent#getBaseline(int, int) 1691 * @since 1.6 1692 */ 1693 public int getBaseline(JComponent c, int width, int height) { 1694 super.getBaseline(c, width, height); 1695 UIDefaults lafDefaults = UIManager.getLookAndFeelDefaults(); 1696 Component renderer = (Component)lafDefaults.get( 1697 BASELINE_COMPONENT_KEY); 1698 if (renderer == null) { 1699 DefaultTableCellRenderer tcr = new DefaultTableCellRenderer(); 1700 renderer = tcr.getTableCellRendererComponent( 1701 table, "a", false, false, -1, -1); 1702 lafDefaults.put(BASELINE_COMPONENT_KEY, renderer); 1703 } 1704 renderer.setFont(table.getFont()); 1705 int rowMargin = table.getRowMargin(); 1706 return renderer.getBaseline(Integer.MAX_VALUE, table.getRowHeight() - 1707 rowMargin) + rowMargin / 2; 1708 } 1709 1710 /** 1711 * Returns an enum indicating how the baseline of the component 1712 * changes as the size changes. 1713 * 1714 * @throws NullPointerException {@inheritDoc} 1715 * @see javax.swing.JComponent#getBaseline(int, int) 1716 * @since 1.6 1717 */ 1718 public Component.BaselineResizeBehavior getBaselineResizeBehavior( 1719 JComponent c) { 1720 super.getBaselineResizeBehavior(c); 1721 return Component.BaselineResizeBehavior.CONSTANT_ASCENT; 1722 } 1723 1724// 1725// Size Methods 1726// 1727 1728 private Dimension createTableSize(long width) { 1729 int height = 0; 1730 int rowCount = table.getRowCount(); 1731 if (rowCount > 0 && table.getColumnCount() > 0) { 1732 Rectangle r = table.getCellRect(rowCount-1, 0, true); 1733 height = r.y + r.height; 1734 } 1735 // Width is always positive. The call to abs() is a workaround for 1736 // a bug in the 1.1.6 JIT on Windows. 1737 long tmp = Math.abs(width); 1738 if (tmp > Integer.MAX_VALUE) { 1739 tmp = Integer.MAX_VALUE; 1740 } 1741 return new Dimension((int)tmp, height); 1742 } 1743 1744 /** 1745 * Return the minimum size of the table. The minimum height is the 1746 * row height times the number of rows. 1747 * The minimum width is the sum of the minimum widths of each column. 1748 */ 1749 public Dimension getMinimumSize(JComponent c) { 1750 long width = 0; 1751 Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns(); 1752 while (enumeration.hasMoreElements()) { 1753 TableColumn aColumn = enumeration.nextElement(); 1754 width = width + aColumn.getMinWidth(); 1755 } 1756 return createTableSize(width); 1757 } 1758 1759 /** 1760 * Return the preferred size of the table. The preferred height is the 1761 * row height times the number of rows. 1762 * The preferred width is the sum of the preferred widths of each column. 1763 */ 1764 public Dimension getPreferredSize(JComponent c) { 1765 long width = 0; 1766 Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns(); 1767 while (enumeration.hasMoreElements()) { 1768 TableColumn aColumn = enumeration.nextElement(); 1769 width = width + aColumn.getPreferredWidth(); 1770 } 1771 return createTableSize(width); 1772 } 1773 1774 /** 1775 * Return the maximum size of the table. The maximum height is the 1776 * row heighttimes the number of rows. 1777 * The maximum width is the sum of the maximum widths of each column. 1778 */ 1779 public Dimension getMaximumSize(JComponent c) { 1780 long width = 0; 1781 Enumeration<TableColumn> enumeration = table.getColumnModel().getColumns(); 1782 while (enumeration.hasMoreElements()) { 1783 TableColumn aColumn = enumeration.nextElement(); 1784 width = width + aColumn.getMaxWidth(); 1785 } 1786 return createTableSize(width); 1787 } 1788 1789// 1790// Paint methods and support 1791// 1792 1793 /** Paint a representation of the <code>table</code> instance 1794 * that was set in installUI(). 1795 */ 1796 public void paint(Graphics g, JComponent c) { 1797 Rectangle clip = g.getClipBounds(); 1798 1799 Rectangle bounds = table.getBounds(); 1800 // account for the fact that the graphics has already been translated 1801 // into the table's bounds 1802 bounds.x = bounds.y = 0; 1803 1804 if (table.getRowCount() <= 0 || table.getColumnCount() <= 0 || 1805 // this check prevents us from painting the entire table 1806 // when the clip doesn't intersect our bounds at all 1807 !bounds.intersects(clip)) { 1808 1809 paintDropLines(g); 1810 return; 1811 } 1812 1813 boolean ltr = table.getComponentOrientation().isLeftToRight(); 1814 1815 Point upperLeft = clip.getLocation(); 1816 Point lowerRight = new Point(clip.x + clip.width - 1, 1817 clip.y + clip.height - 1); 1818 1819 int rMin = table.rowAtPoint(upperLeft); 1820 int rMax = table.rowAtPoint(lowerRight); 1821 // This should never happen (as long as our bounds intersect the clip, 1822 // which is why we bail above if that is the case). 1823 if (rMin == -1) { 1824 rMin = 0; 1825 } 1826 // If the table does not have enough rows to fill the view we'll get -1. 1827 // (We could also get -1 if our bounds don't intersect the clip, 1828 // which is why we bail above if that is the case). 1829 // Replace this with the index of the last row. 1830 if (rMax == -1) { 1831 rMax = table.getRowCount()-1; 1832 } 1833 1834 int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight); 1835 int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft); 1836 // This should never happen. 1837 if (cMin == -1) { 1838 cMin = 0; 1839 } 1840 // If the table does not have enough columns to fill the view we'll get -1. 1841 // Replace this with the index of the last column. 1842 if (cMax == -1) { 1843 cMax = table.getColumnCount()-1; 1844 } 1845 1846 // Paint the grid. 1847 paintGrid(g, rMin, rMax, cMin, cMax); 1848 1849 // Paint the cells. 1850 paintCells(g, rMin, rMax, cMin, cMax); 1851 1852 paintDropLines(g); 1853 } 1854 1855 private void paintDropLines(Graphics g) { 1856 JTable.DropLocation loc = table.getDropLocation(); 1857 if (loc == null) { 1858 return; 1859 } 1860 1861 Color color = UIManager.getColor("Table.dropLineColor"); 1862 Color shortColor = UIManager.getColor("Table.dropLineShortColor"); 1863 if (color == null && shortColor == null) { 1864 return; 1865 } 1866 1867 Rectangle rect; 1868 1869 rect = getHDropLineRect(loc); 1870 if (rect != null) { 1871 int x = rect.x; 1872 int w = rect.width; 1873 if (color != null) { 1874 extendRect(rect, true); 1875 g.setColor(color); 1876 g.fillRect(rect.x, rect.y, rect.width, rect.height); 1877 } 1878 if (!loc.isInsertColumn() && shortColor != null) { 1879 g.setColor(shortColor); 1880 g.fillRect(x, rect.y, w, rect.height); 1881 } 1882 } 1883 1884 rect = getVDropLineRect(loc); 1885 if (rect != null) { 1886 int y = rect.y; 1887 int h = rect.height; 1888 if (color != null) { 1889 extendRect(rect, false); 1890 g.setColor(color); 1891 g.fillRect(rect.x, rect.y, rect.width, rect.height); 1892 } 1893 if (!loc.isInsertRow() && shortColor != null) { 1894 g.setColor(shortColor); 1895 g.fillRect(rect.x, y, rect.width, h); 1896 } 1897 } 1898 } 1899 1900 private Rectangle getHDropLineRect(JTable.DropLocation loc) { 1901 if (!loc.isInsertRow()) { 1902 return null; 1903 } 1904 1905 int row = loc.getRow(); 1906 int col = loc.getColumn(); 1907 if (col >= table.getColumnCount()) { 1908 col--; 1909 } 1910 1911 Rectangle rect = table.getCellRect(row, col, true); 1912 1913 if (row >= table.getRowCount()) { 1914 row--; 1915 Rectangle prevRect = table.getCellRect(row, col, true); 1916 rect.y = prevRect.y + prevRect.height; 1917 } 1918 1919 if (rect.y == 0) { 1920 rect.y = -1; 1921 } else { 1922 rect.y -= 2; 1923 } 1924 1925 rect.height = 3; 1926 1927 return rect; 1928 } 1929 1930 private Rectangle getVDropLineRect(JTable.DropLocation loc) { 1931 if (!loc.isInsertColumn()) { 1932 return null; 1933 } 1934 1935 boolean ltr = table.getComponentOrientation().isLeftToRight(); 1936 int col = loc.getColumn(); 1937 Rectangle rect = table.getCellRect(loc.getRow(), col, true); 1938 1939 if (col >= table.getColumnCount()) { 1940 col--; 1941 rect = table.getCellRect(loc.getRow(), col, true); 1942 if (ltr) { 1943 rect.x = rect.x + rect.width; 1944 } 1945 } else if (!ltr) { 1946 rect.x = rect.x + rect.width; 1947 } 1948 1949 if (rect.x == 0) { 1950 rect.x = -1; 1951 } else { 1952 rect.x -= 2; 1953 } 1954 1955 rect.width = 3; 1956 1957 return rect; 1958 } 1959 1960 private Rectangle extendRect(Rectangle rect, boolean horizontal) { 1961 if (rect == null) { 1962 return rect; 1963 } 1964 1965 if (horizontal) { 1966 rect.x = 0; 1967 rect.width = table.getWidth(); 1968 } else { 1969 rect.y = 0; 1970 1971 if (table.getRowCount() != 0) { 1972 Rectangle lastRect = table.getCellRect(table.getRowCount() - 1, 0, true); 1973 rect.height = lastRect.y + lastRect.height; 1974 } else { 1975 rect.height = table.getHeight(); 1976 } 1977 } 1978 1979 return rect; 1980 } 1981 1982 /* 1983 * Paints the grid lines within <I>aRect</I>, using the grid 1984 * color set with <I>setGridColor</I>. Paints vertical lines 1985 * if <code>getShowVerticalLines()</code> returns true and paints 1986 * horizontal lines if <code>getShowHorizontalLines()</code> 1987 * returns true. 1988 */ 1989 private void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) { 1990 g.setColor(table.getGridColor()); 1991 1992 Rectangle minCell = table.getCellRect(rMin, cMin, true); 1993 Rectangle maxCell = table.getCellRect(rMax, cMax, true); 1994 Rectangle damagedArea = minCell.union( maxCell ); 1995 1996 if (table.getShowHorizontalLines()) { 1997 int tableWidth = damagedArea.x + damagedArea.width; 1998 int y = damagedArea.y; 1999 for (int row = rMin; row <= rMax; row++) { 2000 y += table.getRowHeight(row); 2001 g.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1); 2002 } 2003 } 2004 if (table.getShowVerticalLines()) { 2005 TableColumnModel cm = table.getColumnModel(); 2006 int tableHeight = damagedArea.y + damagedArea.height; 2007 int x; 2008 if (table.getComponentOrientation().isLeftToRight()) { 2009 x = damagedArea.x; 2010 for (int column = cMin; column <= cMax; column++) { 2011 int w = cm.getColumn(column).getWidth(); 2012 x += w; 2013 g.drawLine(x - 1, 0, x - 1, tableHeight - 1); 2014 } 2015 } else { 2016 x = damagedArea.x; 2017 for (int column = cMax; column >= cMin; column--) { 2018 int w = cm.getColumn(column).getWidth(); 2019 x += w; 2020 g.drawLine(x - 1, 0, x - 1, tableHeight - 1); 2021 } 2022 } 2023 } 2024 } 2025 2026 private int viewIndexForColumn(TableColumn aColumn) { 2027 TableColumnModel cm = table.getColumnModel(); 2028 for (int column = 0; column < cm.getColumnCount(); column++) { 2029 if (cm.getColumn(column) == aColumn) { 2030 return column; 2031 } 2032 } 2033 return -1; 2034 } 2035 2036 private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) { 2037 JTableHeader header = table.getTableHeader(); 2038 TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn(); 2039 2040 TableColumnModel cm = table.getColumnModel(); 2041 int columnMargin = cm.getColumnMargin(); 2042 2043 Rectangle cellRect; 2044 TableColumn aColumn; 2045 int columnWidth; 2046 if (table.getComponentOrientation().isLeftToRight()) { 2047 for(int row = rMin; row <= rMax; row++) { 2048 cellRect = table.getCellRect(row, cMin, false); 2049 for(int column = cMin; column <= cMax; column++) { 2050 aColumn = cm.getColumn(column); 2051 columnWidth = aColumn.getWidth(); 2052 cellRect.width = columnWidth - columnMargin; 2053 if (aColumn != draggedColumn) { 2054 paintCell(g, cellRect, row, column); 2055 } 2056 cellRect.x += columnWidth; 2057 } 2058 } 2059 } else { 2060 for(int row = rMin; row <= rMax; row++) { 2061 cellRect = table.getCellRect(row, cMin, false); 2062 aColumn = cm.getColumn(cMin); 2063 if (aColumn != draggedColumn) { 2064 columnWidth = aColumn.getWidth(); 2065 cellRect.width = columnWidth - columnMargin; 2066 paintCell(g, cellRect, row, cMin); 2067 } 2068 for(int column = cMin+1; column <= cMax; column++) { 2069 aColumn = cm.getColumn(column); 2070 columnWidth = aColumn.getWidth(); 2071 cellRect.width = columnWidth - columnMargin; 2072 cellRect.x -= columnWidth; 2073 if (aColumn != draggedColumn) { 2074 paintCell(g, cellRect, row, column); 2075 } 2076 } 2077 } 2078 } 2079 2080 // Paint the dragged column if we are dragging. 2081 if (draggedColumn != null) { 2082 paintDraggedArea(g, rMin, rMax, draggedColumn, header.getDraggedDistance()); 2083 } 2084 2085 // Remove any renderers that may be left in the rendererPane. 2086 rendererPane.removeAll(); 2087 } 2088 2089 private void paintDraggedArea(Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) { 2090 int draggedColumnIndex = viewIndexForColumn(draggedColumn); 2091 2092 Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true); 2093 Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true); 2094 2095 Rectangle vacatedColumnRect = minCell.union(maxCell); 2096 2097 // Paint a gray well in place of the moving column. 2098 g.setColor(table.getParent().getBackground()); 2099 g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y, 2100 vacatedColumnRect.width, vacatedColumnRect.height); 2101 2102 // Move to the where the cell has been dragged. 2103 vacatedColumnRect.x += distance; 2104 2105 // Fill the background. 2106 g.setColor(table.getBackground()); 2107 g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y, 2108 vacatedColumnRect.width, vacatedColumnRect.height); 2109 2110 // Paint the vertical grid lines if necessary. 2111 if (table.getShowVerticalLines()) { 2112 g.setColor(table.getGridColor()); 2113 int x1 = vacatedColumnRect.x; 2114 int y1 = vacatedColumnRect.y; 2115 int x2 = x1 + vacatedColumnRect.width - 1; 2116 int y2 = y1 + vacatedColumnRect.height - 1; 2117 // Left 2118 g.drawLine(x1-1, y1, x1-1, y2); 2119 // Right 2120 g.drawLine(x2, y1, x2, y2); 2121 } 2122 2123 for(int row = rMin; row <= rMax; row++) { 2124 // Render the cell value 2125 Rectangle r = table.getCellRect(row, draggedColumnIndex, false); 2126 r.x += distance; 2127 paintCell(g, r, row, draggedColumnIndex); 2128 2129 // Paint the (lower) horizontal grid line if necessary. 2130 if (table.getShowHorizontalLines()) { 2131 g.setColor(table.getGridColor()); 2132 Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true); 2133 rcr.x += distance; 2134 int x1 = rcr.x; 2135 int y1 = rcr.y; 2136 int x2 = x1 + rcr.width - 1; 2137 int y2 = y1 + rcr.height - 1; 2138 g.drawLine(x1, y2, x2, y2); 2139 } 2140 } 2141 } 2142 2143 private void paintCell(Graphics g, Rectangle cellRect, int row, int column) { 2144 if (table.isEditing() && table.getEditingRow()==row && 2145 table.getEditingColumn()==column) { 2146 Component component = table.getEditorComponent(); 2147 component.setBounds(cellRect); 2148 component.validate(); 2149 } 2150 else { 2151 TableCellRenderer renderer = table.getCellRenderer(row, column); 2152 Component component = table.prepareRenderer(renderer, row, column); 2153 rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y, 2154 cellRect.width, cellRect.height, true); 2155 } 2156 } 2157 2158 private static int getAdjustedLead(JTable table, 2159 boolean row, 2160 ListSelectionModel model) { 2161 2162 int index = model.getLeadSelectionIndex(); 2163 int compare = row ? table.getRowCount() : table.getColumnCount(); 2164 return index < compare ? index : -1; 2165 } 2166 2167 private static int getAdjustedLead(JTable table, boolean row) { 2168 return row ? getAdjustedLead(table, row, table.getSelectionModel()) 2169 : getAdjustedLead(table, row, table.getColumnModel().getSelectionModel()); 2170 } 2171 2172 2173 private static final TransferHandler defaultTransferHandler = new TableTransferHandler(); 2174 2175 @SuppressWarnings("serial") // JDK-implementation class 2176 static class TableTransferHandler extends TransferHandler implements UIResource { 2177 2178 /** 2179 * Create a Transferable to use as the source for a data transfer. 2180 * 2181 * @param c The component holding the data to be transfered. This 2182 * argument is provided to enable sharing of TransferHandlers by 2183 * multiple components. 2184 * @return The representation of the data to be transfered. 2185 * 2186 */ 2187 protected Transferable createTransferable(JComponent c) { 2188 if (c instanceof JTable) { 2189 JTable table = (JTable) c; 2190 int[] rows; 2191 int[] cols; 2192 2193 if (!table.getRowSelectionAllowed() && !table.getColumnSelectionAllowed()) { 2194 return null; 2195 } 2196 2197 if (!table.getRowSelectionAllowed()) { 2198 int rowCount = table.getRowCount(); 2199 2200 rows = new int[rowCount]; 2201 for (int counter = 0; counter < rowCount; counter++) { 2202 rows[counter] = counter; 2203 } 2204 } else { 2205 rows = table.getSelectedRows(); 2206 } 2207 2208 if (!table.getColumnSelectionAllowed()) { 2209 int colCount = table.getColumnCount(); 2210 2211 cols = new int[colCount]; 2212 for (int counter = 0; counter < colCount; counter++) { 2213 cols[counter] = counter; 2214 } 2215 } else { 2216 cols = table.getSelectedColumns(); 2217 } 2218 2219 if (rows == null || cols == null || rows.length == 0 || cols.length == 0) { 2220 return null; 2221 } 2222 2223 StringBuilder plainStr = new StringBuilder(); 2224 StringBuilder htmlStr = new StringBuilder(); 2225 2226 htmlStr.append("<html>\n<body>\n<table>\n"); 2227 2228 for (int row = 0; row < rows.length; row++) { 2229 htmlStr.append("<tr>\n"); 2230 for (int col = 0; col < cols.length; col++) { 2231 Object obj = table.getValueAt(rows[row], cols[col]); 2232 String val = ((obj == null) ? "" : obj.toString()); 2233 plainStr.append(val + "\t"); 2234 htmlStr.append(" <td>" + val + "</td>\n"); 2235 } 2236 // we want a newline at the end of each line and not a tab 2237 plainStr.deleteCharAt(plainStr.length() - 1).append("\n"); 2238 htmlStr.append("</tr>\n"); 2239 } 2240 2241 // remove the last newline 2242 plainStr.deleteCharAt(plainStr.length() - 1); 2243 htmlStr.append("</table>\n</body>\n</html>"); 2244 2245 return new BasicTransferable(plainStr.toString(), htmlStr.toString()); 2246 } 2247 2248 return null; 2249 } 2250 2251 public int getSourceActions(JComponent c) { 2252 return COPY; 2253 } 2254 2255 } 2256} // End of Class BasicTableUI 2257