1 /* 2 * @(#)QuaquaTableUI.java 1.10.1 2008-11-05 3 * 4 * Copyright (c) 2004-2008 Werner Randelshofer 5 * Staldenmattweg 2, Immensee, CH-6405, Switzerland. 6 * All rights reserved. 7 * 8 * The copyright of this software is owned by Werner Randelshofer. 9 * You may not use, copy or modify this software, except in 10 * accordance with the license agreement you entered into with 11 * Werner Randelshofer. For details see accompanying license terms. 12 */ 13 package ch.randelshofer.quaqua; 14 15 import ch.randelshofer.quaqua.color.InactivatableColorUIResource; 16 import ch.randelshofer.quaqua.util.ViewportPainter; 17 import java.awt.*; 18 import java.awt.datatransfer.*; 19 import java.awt.dnd.*; 20 import java.awt.event.*; 21 import java.awt.image.*; 22 import java.beans.*; 23 import java.util.*; 24 import javax.swing.*; 25 import javax.swing.event.*; 26 import javax.swing.plaf.*; 27 import javax.swing.plaf.basic.*; 28 import javax.swing.table.*; 29 import javax.swing.event.*; 30 import javax.swing.text.*; 31 32 /** 33 * QuaquaTableUI. 34 * 35 * @author Werner Randelshofer 36 * @version 1.10.1 2008-11-05 Included fix by laurie_int regarding performance 37 * issue #72 "Table renders slow with very large tables". 38 * <br>1.10 2008-07-15 System property "Quaqua.Table.useJ2SE5MouseHandler" 39 * enforces use of J2SE5 mouse handler for J2SE6. 40 * <br>1.9.3 2008-07-06 Java 1.4 incorrectly reports button 3 pressed when 41 * the user presses the meta key. 42 * <br>1.9.2 2008-06-22 Selection foreground color must be set to 43 * inactive (=black) when the current cell is not selected, otherwise we get 44 * white text on white background. 45 * <br>1.9.1 2008-05-31 Moved all code related to InactivateableColorUIResource 46 * into method paintCell. 47 * <br>1.9 2008-05-10 Treat table as focused, if it is focused or if 48 * it is editing a table cell. 49 * <br>1.8.1 2008-05-03 Multiple cell selection did not work. 50 * <br>1.8 2008-04-21 Set client property "terminateEditOnFocusLost" to 51 * true on initDefaults. On mousePressed, requestFocusInWindow. 52 * <br>1.7 2008-03-21 Made selection behavior more consistent with native 53 * NSTable control. 54 * <br>1.6 2008-02-07 Reworked drawing of list selection. Implemented 55 * ListSelectionListener to ensure that selection changes are properly repainted. 56 * <br>1.5 2008-01-13 Set 'showHorizontalLines' and 'showVerticalLines' to 57 * false once when installing the UI, instead of overwriting these properties every 58 * time when the client property "Quaqua.Table.style" is changed. 59 * <br>1.4 2007-01-16 Focus border repainting factored out into QuaquaViewportUI. 60 * <br>1.3.3 2007-01-15 Change foreground color of cell renderer even if 61 * it is not an UIResource. 62 * <br>1.3.2 2007-01-05 Issue #6: Selection needs to be drawn differently 63 * when table hasn't focus or is disabled or is on an inactive window. 64 * Issue #10: Table cells mustn't draw selection background when 65 * rowSelectionAllowed is false. 66 * <br>1.3.1 2006-05-04 EditorCell was always drawn with alternating 67 * row2 color even when the table style was not set to striped. 68 * <br>1.3 2006-02-07 Support for client property "Table.isFileList" added. 69 * <br>1.2.1 2005-08-25 If the table is not striped, fill the viewport with 70 * the background color of the table. 71 * <br>1.2 2005-03-11 LnF Property "Table.alternateBackground" replaced 72 * by "Table.alternateBackground.0" and "Table.alternateBackground.1". 73 * <br>1.1 2004-07-04 FocusHandler added. 74 * <br>1.0 June 22, 2004 Created. 75 */ 76 public class QuaquaTableUI extends BasicTableUI 77 implements ViewportPainter { 78 79 private PropertyChangeListener propertyChangeListener; 80 private ListSelectionListener listSelectionListener; 81 private TableColumnModelListener columnModelListener; 82 private Handler handler; 83 private boolean isStriped = false; 84 85 /** Creates a new instance. */ QuaquaTableUI()86 public QuaquaTableUI() { 87 } 88 createUI(JComponent c)89 public static ComponentUI createUI(JComponent c) { 90 return new QuaquaTableUI(); 91 } 92 93 /** 94 * Creates the key listener for handling keyboard navigation in the JTable. 95 */ createKeyListener()96 protected KeyListener createKeyListener() { 97 return new KeyAdapter() { 98 99 public void keyPressed(KeyEvent e) { 100 // Eat away META down keys.. 101 // We need to do this, because the JTable.processKeyBinding(…) 102 // method does not treat VK_META as a modifier key, and starts 103 // editing a cell, whenever this key is pressed. 104 if (e.getKeyCode() == KeyEvent.VK_META) { 105 e.consume(); 106 } 107 } 108 }; 109 } 110 111 private Color getAlternateColor(int modulo) { 112 if (modulo == 0) { 113 return UIManager.getColor("Table.alternateBackground.0"); 114 } else { 115 return UIManager.getColor("Table.alternateBackground.1"); 116 } 117 } 118 119 /** 120 * Attaches listeners to the JTable. 121 */ 122 protected void installListeners() { 123 super.installListeners(); 124 propertyChangeListener = createPropertyChangeListener(); 125 table.addPropertyChangeListener(propertyChangeListener); 126 listSelectionListener = createListSelectionListener(); 127 if (table.getSelectionModel() != null) { 128 table.getSelectionModel().addListSelectionListener(listSelectionListener); 129 } 130 columnModelListener = createTableColumnModelListener(); 131 if (table.getColumnModel() != null) { 132 table.getColumnModel().addColumnModelListener(columnModelListener); 133 } 134 // table.add 135 } 136 137 protected void uninstallListeners() { 138 super.uninstallListeners(); 139 table.removePropertyChangeListener(propertyChangeListener); 140 if (table.getSelectionModel() != null) { 141 table.getSelectionModel().removeListSelectionListener(listSelectionListener); 142 } 143 if (table.getColumnModel() != null) { 144 table.getColumnModel().removeColumnModelListener(columnModelListener); 145 } 146 propertyChangeListener = null; 147 listSelectionListener = null; 148 149 } 150 151 protected void installDefaults() { 152 super.installDefaults(); 153 Object property = table.getClientProperty("Quaqua.Table.style"); 154 isStriped = property != null && property.equals("striped"); 155 updateStriped(); 156 table.setShowHorizontalLines(false); 157 table.setShowVerticalLines(false); 158 // table.putClientProperty("JTable.autoStartsEdit", Boolean.FALSE); 159 160 // By default, terminate editing on focus lost. 161 table.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); 162 163 // FIXME - Intercell spacings different from 1,1 don't work currently 164 //table.setIntercellSpacing(new Dimension(4,4)); 165 } 166 167 private void updateStriped() { 168 /*if (isStriped) { 169 table.setIntercellSpacing(new Dimension(1, 1)); 170 } else { 171 //getTableHeader().setDefaultRenderer(new DefaultTableHeaderRenderer()); 172 table.setIntercellSpacing(new Dimension(1, 1)); 173 }*/ 174 } 175 176 /** Paint a representation of the <code>table</code> instance 177 * that was set in installUI(). 178 */ 179 public void paint(Graphics g, JComponent c) { 180 if (table.getRowCount() <= 0 || table.getColumnCount() <= 0) { 181 return; 182 } 183 Rectangle clip = g.getClipBounds(); 184 Point upperLeft = clip.getLocation(); 185 Point lowerRight = new Point(clip.x + clip.width - 1, clip.y + clip.height - 1); 186 int rMin = table.rowAtPoint(upperLeft); 187 int rMax = table.rowAtPoint(lowerRight); 188 // This should never happen. 189 if (rMin == -1) { 190 rMin = 0; 191 } 192 // If the table does not have enough rows to fill the view we'll get -1. 193 // Replace this with the row2 of the last row2. 194 if (rMax == -1) { 195 rMax = table.getRowCount() - 1; 196 } 197 198 boolean ltr = table.getComponentOrientation().isLeftToRight(); 199 int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight); 200 int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft); 201 // This should never happen. 202 if (cMin == -1) { 203 cMin = 0; 204 } 205 // If the table does not have enough columns to fill the view we'll get -1. 206 // Replace this with the row2 of the last column. 207 if (cMax == -1) { 208 cMax = table.getColumnCount() - 1; 209 } 210 211 // Paint the cells. 212 paintCells(g, rMin, rMax, cMin, cMax); 213 // Paint the grid. 214 paintGrid(g, rMin, rMax, cMin, cMax); 215 } 216 217 public void paintViewport(Graphics g, JViewport c) { 218 Dimension vs = c.getSize(); 219 Dimension ts = table.getSize(); 220 Point p = table.getLocation(); 221 int rh = table.getRowHeight(); 222 int n = table.getRowCount(); 223 int row = Math.abs(p.y / rh); 224 int th = n * rh - row * rh; 225 226 227 if (isStriped) { 228 // Fill the viewport with alternate color 1 229 g.setColor(getAlternateColor(1)); 230 g.fillRect(0, 0, c.getWidth(), c.getHeight()); 231 232 // Now check if we need to paint some stripes 233 g.setColor(getAlternateColor(0)); 234 235 // Paint empty rows at the right to fill the viewport 236 if (ts.width < vs.width) { 237 for (int y = p.y + row * rh, ymax = Math.min(th, vs.height); y < ymax; y+=rh) { 238 if (row % 2 == 0) { 239 g.fillRect(0, y, vs.width, rh); 240 } 241 row++; 242 } 243 } 244 245 246 // Paint empty rows at the bottom to fill the viewport 247 if (th < vs.height) { 248 row = n; 249 int y = th; 250 while (y < vs.height) { 251 if (row % 2 == 0) { 252 g.fillRect(0, y, vs.width, rh); 253 } 254 y += rh; 255 row++; 256 } 257 } 258 } else { 259 // Fill the viewport with the background color of the table 260 g.setColor(table.getBackground()); 261 g.fillRect(0, 0, c.getWidth(), c.getHeight()); 262 } 263 264 // Paint the horizontal grid lines 265 if (table.getShowHorizontalLines()) { 266 g.setColor(table.getGridColor()); 267 if (ts.width < vs.width) { 268 row = Math.abs(p.y / rh); 269 int y = p.y + row * rh + rh - 1; 270 while (y < th) { 271 g.drawLine(0, y, vs.width, y); 272 y += rh; 273 } 274 } 275 if (th < vs.height) { 276 int y = th + rh - 1; 277 while (y < vs.height) { 278 g.drawLine(0, y, vs.width, y); 279 y += rh; 280 } 281 } 282 } 283 284 285 // Paint the vertical grid lines 286 if (th < vs.height && table.getShowVerticalLines()) { 287 g.setColor(table.getGridColor()); 288 TableColumnModel cm = table.getColumnModel(); 289 n = cm.getColumnCount(); 290 int y = th; 291 int x = table.getX() - 1; 292 for (int i = 0; i < n; i++) { 293 TableColumn col = cm.getColumn(i); 294 x += col.getWidth(); 295 g.drawLine(x, y, x, vs.height); 296 } 297 } 298 } 299 300 /* 301 * Paints the grid lines within <I>aRect</I>, using the grid 302 * color set with <I>setGridColor</I>. Paints vertical lines 303 * if <code>getShowVerticalLines()</code> returns true and paints 304 * horizontal lines if <code>getShowHorizontalLines()</code> 305 * returns true. 306 */ 307 private void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) { 308 g.setColor(table.getGridColor()); 309 Rectangle minCell = table.getCellRect(rMin, cMin, true); 310 Rectangle maxCell = table.getCellRect(rMax, cMax, true); 311 Rectangle damagedArea = minCell.union(maxCell); 312 313 if (table.getShowHorizontalLines()) { 314 int tableWidth = damagedArea.x + damagedArea.width; 315 int y = damagedArea.y; 316 for (int row = rMin; row <= rMax; row++) { 317 y += table.getRowHeight(row); 318 g.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1); 319 } 320 } 321 if (table.getShowVerticalLines()) { 322 JTableHeader header = table.getTableHeader(); 323 TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn(); 324 Rectangle vacatedColumnRect; 325 if (draggedColumn != null) { 326 int draggedColumnIndex = viewIndexForColumn(draggedColumn); 327 328 Rectangle minDraggedCell = table.getCellRect(rMin, draggedColumnIndex, true); 329 Rectangle maxDraggedCell = table.getCellRect(rMax, draggedColumnIndex, true); 330 331 vacatedColumnRect = minDraggedCell.union(maxDraggedCell); 332 333 // Move to the where the cell has been dragged. 334 vacatedColumnRect.x += header.getDraggedDistance(); 335 } else { 336 vacatedColumnRect = new Rectangle(0, 0, -1, -1); 337 } 338 339 TableColumnModel cm = table.getColumnModel(); 340 int tableHeight = damagedArea.y + damagedArea.height; 341 int x; 342 if (table.getComponentOrientation().isLeftToRight()) { 343 x = damagedArea.x; 344 for (int column = cMin; column <= cMax; column++) { 345 int w = cm.getColumn(column).getWidth(); 346 x += w; 347 if (x < vacatedColumnRect.x || x > vacatedColumnRect.x + vacatedColumnRect.width) { 348 g.drawLine(x - 1, 0, x - 1, tableHeight - 1); 349 } 350 } 351 } else { 352 x = damagedArea.x + damagedArea.width; 353 for (int column = cMin; column < cMax; column++) { 354 int w = cm.getColumn(column).getWidth(); 355 x -= w; 356 if (x < vacatedColumnRect.x || x > vacatedColumnRect.x + vacatedColumnRect.width) { 357 g.drawLine(x - 1, 0, x - 1, tableHeight - 1); 358 } 359 } 360 x -= cm.getColumn(cMax).getWidth(); 361 g.drawLine(x, 0, x, tableHeight - 1); 362 } 363 } 364 } 365 366 private void paintDraggedArea(Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) { 367 int draggedColumnIndex = viewIndexForColumn(draggedColumn); 368 369 Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true); 370 Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true); 371 372 Rectangle vacatedColumnRect = minCell.union(maxCell); 373 374 // Paint a gray well in place of the moving column. 375 g.setColor(table.getParent().getBackground()); 376 g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y, 377 vacatedColumnRect.width, vacatedColumnRect.height); 378 379 // Move to the where the cell has been dragged. 380 vacatedColumnRect.x += distance; 381 382 // Fill the background. 383 g.setColor(table.getBackground()); 384 g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y, 385 vacatedColumnRect.width, vacatedColumnRect.height); 386 387 // Paint the vertical grid lines if necessary. 388 if (table.getShowVerticalLines()) { 389 g.setColor(table.getGridColor()); 390 int x1 = vacatedColumnRect.x; 391 int y1 = vacatedColumnRect.y; 392 int x2 = x1 + vacatedColumnRect.width - 1; 393 int y2 = y1 + vacatedColumnRect.height - 1; 394 // Left 395 g.drawLine(x1 - 1, y1, x1 - 1, y2); 396 // Right 397 g.drawLine(x2, y1, x2, y2); 398 } 399 400 boolean isFocused = isFocused(); 401 402 for (int row = rMin; row <= rMax; row++) { 403 // Render the cell value 404 Rectangle r = table.getCellRect(row, draggedColumnIndex, false); 405 r.x += distance; 406 paintCell(g, r, row, draggedColumnIndex, isFocused); 407 408 // Paint the (lower) horizontal grid line if necessary. 409 if (table.getShowHorizontalLines()) { 410 g.setColor(table.getGridColor()); 411 Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true); 412 rcr.x += distance; 413 int x1 = rcr.x; 414 int y1 = rcr.y; 415 int x2 = x1 + rcr.width - 1; 416 int y2 = y1 + rcr.height - 1; 417 g.drawLine(x1, y2, x2, y2); 418 } 419 } 420 } 421 422 private int viewIndexForColumn(TableColumn aColumn) { 423 TableColumnModel cm = table.getColumnModel(); 424 for (int column = 0; column < cm.getColumnCount(); column++) { 425 if (cm.getColumn(column) == aColumn) { 426 return column; 427 } 428 } 429 return -1; 430 } 431 432 private boolean isFocused() { 433 return table.isEditing() || QuaquaUtilities.isFocused(table); 434 } 435 436 private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) { 437 boolean isFocused = isFocused(); 438 439 JTableHeader header = table.getTableHeader(); 440 TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn(); 441 442 TableColumnModel cm = table.getColumnModel(); 443 int columnMargin = cm.getColumnMargin(); 444 445 Rectangle cellRect; 446 TableColumn aColumn; 447 int columnWidth; 448 if (table.getComponentOrientation().isLeftToRight()) { 449 for (int row = rMin; row <= rMax; row++) { 450 cellRect = table.getCellRect(row, cMin, false); 451 for (int column = cMin; column <= cMax; column++) { 452 aColumn = cm.getColumn(column); 453 columnWidth = aColumn.getWidth(); 454 cellRect.width = columnWidth - columnMargin; 455 if (aColumn != draggedColumn) { 456 paintCell(g, cellRect, row, column, isFocused); 457 } 458 cellRect.x += columnWidth; 459 } 460 } 461 } else { 462 for (int row = rMin; row <= rMax; row++) { 463 cellRect = table.getCellRect(row, cMin, false); 464 aColumn = cm.getColumn(cMin); 465 if (aColumn != draggedColumn) { 466 columnWidth = aColumn.getWidth(); 467 cellRect.width = columnWidth - columnMargin; 468 paintCell(g, cellRect, row, cMin, isFocused); 469 } 470 for (int column = cMin + 1; column <= cMax; column++) { 471 aColumn = cm.getColumn(column); 472 columnWidth = aColumn.getWidth(); 473 cellRect.width = columnWidth - columnMargin; 474 cellRect.x -= columnWidth; 475 if (aColumn != draggedColumn) { 476 paintCell(g, cellRect, row, column, isFocused); 477 } 478 } 479 } 480 } 481 482 // Paint the dragged column if we are dragging. 483 if (draggedColumn != null) { 484 paintDraggedArea(g, rMin, rMax, draggedColumn, header.getDraggedDistance()); 485 } 486 487 // Remove any renderers that may be left in the rendererPane. 488 rendererPane.removeAll(); 489 490 } 491 492 private void paintCell(Graphics g, Rectangle cellRect, int row, int column, boolean isFocused) { 493 // Ugly dirty hack to get correct painting of inactive tables 494 Color background = UIManager.getColor("Table.selectionBackground"); 495 Color foreground = UIManager.getColor("Table.selectionForeground"); 496 if (background instanceof InactivatableColorUIResource) { 497 ((InactivatableColorUIResource) background).setActive(isFocused && 498 (table.getRowSelectionAllowed() || table.getColumnSelectionAllowed())); 499 } 500 if (foreground instanceof InactivatableColorUIResource) { 501 // Note: We must draw with inactive color, if the current cell is not selected 502 // Otherwise, we get white text on white background. 503 ((InactivatableColorUIResource) foreground).setActive(isFocused && 504 (table.getRowSelectionAllowed() || table.getColumnSelectionAllowed()) && 505 table.isCellSelected(row, column)); 506 } 507 508 Dimension spacing = table.getIntercellSpacing(); 509 if (table.getShowHorizontalLines()) { 510 spacing.height -= 1; 511 } 512 if (table.getShowVerticalLines()) { 513 spacing.width -= 1; 514 } 515 516 if (table.isEditing() && table.getEditingRow() == row && 517 table.getEditingColumn() == column) { 518 Component component = table.getEditorComponent(); 519 // component.setBackground((isStriped) ? getAlternateColor(row2 % 2) : table.getBackground()); 520 // We only need to paint the alternate background color for even 521 // rows, because the background for uneven rows is painted by 522 // method paintViewport(). 523 /* 524 if (isStriped && row2 % 2 == 0) { 525 g.setColor(getAlternateColor(0)); 526 g.fillRect(cellRect.x - spacing.width, cellRect.y, cellRect.width + spacing.width * 2, cellRect.height + spacing.height); 527 }*/ 528 component.setFont(table.getFont()); 529 component.setBounds(cellRect); 530 component.validate(); 531 } else { 532 TableCellRenderer renderer = table.getCellRenderer(row, column); 533 Component component = table.prepareRenderer(renderer, row, column); 534 535 if (isStriped) { 536 g.setColor(getAlternateColor(row % 2)); 537 g.fillRect(cellRect.x - spacing.width, cellRect.y, cellRect.width + spacing.width * 2, cellRect.height + spacing.height); 538 } 539 if (/*!table.isEditing() &&*/table.isCellSelected(row, column)) { 540 g.setColor(background); 541 g.fillRect(cellRect.x - spacing.width, cellRect.y, cellRect.width + spacing.width * 2, cellRect.height); 542 } 543 544 if ((component instanceof UIResource) && (component instanceof JComponent)) { 545 ((JComponent) component).setOpaque(false); 546 } 547 548 //component.setBackground(background); 549 rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y, 550 cellRect.width, cellRect.height, true); 551 552 } 553 // Ugly dirty hack to get proper rendering of inactive tables 554 // Here we clean up the values of the "active" property of the selection 555 // colors. 556 if (!isFocused) { 557 if (background instanceof InactivatableColorUIResource) { 558 ((InactivatableColorUIResource) background).setActive(true); 559 } 560 if (foreground instanceof InactivatableColorUIResource) { 561 ((InactivatableColorUIResource) foreground).setActive(true); 562 } 563 } 564 } 565 566 /** 567 * Creates the mouse listener for the JTable. 568 */ 569 protected MouseInputListener createMouseInputListener() { 570 // FIXME - We haven't yet implemented a mouse handler for J2SE6. 571 // Only use our own mouse listener on Java 1.4 and 1.5, 572 // it does not work with J2SE6. 573 if (QuaquaManager.getProperty("Quaqua.Table.useJ2SE5MouseHandler","false").equals("true") || 574 QuaquaManager.getProperty("java.version").startsWith("1.4") || 575 QuaquaManager.getProperty("java.version").startsWith("1.5")) { 576 return new MouseHandler(); 577 } else { 578 return super.createMouseInputListener(); 579 } 580 } 581 582 /** 583 * Creates the property change listener for the JTable. 584 */ 585 private PropertyChangeListener createPropertyChangeListener() { 586 return getHandler(); 587 } 588 589 /** 590 * Creates the list selection listener for the JTable. 591 */ 592 private ListSelectionListener createListSelectionListener() { 593 return getHandler(); 594 } 595 596 /** 597 * Creates the list selection listener for the JTable. 598 */ 599 private TableColumnModelListener createTableColumnModelListener() { 600 return getHandler(); 601 } 602 603 /** 604 * Lazily creates the handler. 605 */ 606 private Handler getHandler() { 607 if (handler == null) { 608 handler = new Handler(); 609 } 610 return handler; 611 } 612 613 /** 614 * Creates the focus listener for handling keyboard navigation in the JTable. 615 */ 616 protected FocusListener createFocusListener() { 617 return new FocusHandler(); 618 } 619 // 620 // The Table's focus listener 621 // 622 /** 623 * This inner class is marked "public" due to a compiler bug. 624 * This class should be treated as a "protected" inner class. 625 * Instantiate it only within subclasses of BasicTableUI. 626 */ 627 /** 628 * PropertyChangeListener for the table. Updates the appropriate 629 * varaible, or TreeState, based on what changes. 630 */ 631 private class Handler implements 632 PropertyChangeListener, ListSelectionListener, TableColumnModelListener { 633 634 private boolean rowSelectionAdjusting; 635 636 public void propertyChange(PropertyChangeEvent event) { 637 String name = event.getPropertyName(); 638 639 if (name.equals("Quaqua.Table.style")) { 640 Object value = event.getNewValue(); 641 isStriped = value != null && value.equals("striped"); 642 updateStriped(); 643 } else if (name.equals("showVerticalLines") || 644 name.equals("showHorizontalLines")) { 645 if (table.getParent() instanceof JViewport) { 646 table.getParent().repaint(); 647 } 648 } else if (name.equals("selectionModel")) { 649 if (event.getOldValue() != null) { 650 ((ListSelectionModel) event.getOldValue()).removeListSelectionListener(listSelectionListener); 651 } 652 if (event.getNewValue() != null) { 653 ((ListSelectionModel) event.getNewValue()).addListSelectionListener(listSelectionListener); 654 } 655 } else if (name.equals("columnModel")) { 656 if (event.getOldValue() != null) { 657 ((TableColumnModel) event.getOldValue()).removeColumnModelListener(columnModelListener); 658 } 659 if (event.getNewValue() != null) { 660 ((TableColumnModel) event.getNewValue()).addColumnModelListener(columnModelListener); 661 } 662 } else if (name.equals("tableCellEditor")) { 663 table.repaint(); 664 } else if (name.equals("JComponent.sizeVariant")) { 665 QuaquaUtilities.applySizeVariant(table); 666 } 667 } 668 669 public void columnAdded(TableColumnModelEvent e) { 670 } 671 672 public void columnRemoved(TableColumnModelEvent e) { 673 } 674 675 public void columnMoved(TableColumnModelEvent e) { 676 } 677 678 public void columnMarginChanged(ChangeEvent e) { 679 } 680 681 private int getAdjustedIndex(int index, boolean row) { 682 int compare = row ? table.getRowCount() : table.getColumnCount(); 683 return index < compare ? index : -1; 684 } 685 686 public void columnSelectionChanged(ListSelectionEvent e) { 687 ListSelectionModel selectionModel = table.getSelectionModel(); 688 int firstIndex = limit(e.getFirstIndex(), 0, table.getColumnCount() - 1); 689 int lastIndex = limit(e.getLastIndex(), 0, table.getColumnCount() - 1); 690 int minRow = 0; 691 int maxRow = table.getRowCount() - 1; 692 if (table.getRowSelectionAllowed()) { 693 minRow = selectionModel.getMinSelectionIndex(); 694 maxRow = selectionModel.getMaxSelectionIndex(); 695 int leadRow = getAdjustedIndex(selectionModel.getLeadSelectionIndex(), true); 696 697 if (minRow == -1 || maxRow == -1) { 698 if (leadRow == -1) { 699 // nothing to repaint, return 700 return; 701 } 702 703 // only thing to repaint is the lead 704 minRow = maxRow = leadRow; 705 } else { 706 // We need to consider more than just the range between 707 // the min and max selected index. The lead row, which could 708 // be outside this range, should be considered also. 709 if (leadRow != -1) { 710 minRow = Math.min(minRow, leadRow); 711 maxRow = Math.max(maxRow, leadRow); 712 } 713 } 714 } 715 Rectangle firstColumnRect = table.getCellRect(minRow, firstIndex, false); 716 Rectangle lastColumnRect = table.getCellRect(maxRow, lastIndex, false); 717 Rectangle dirtyRegion = firstColumnRect.union(lastColumnRect); 718 Dimension intercellSpacing = table.getIntercellSpacing(); 719 if (intercellSpacing != null) { 720 dirtyRegion.width += table.getIntercellSpacing().width; 721 } 722 table.repaint(dirtyRegion); 723 } 724 725 /** 726 * This is a reimplementation of the JTable.valueChanged method, 727 * with the only difference, that we repaint the cells _including_ the 728 * intercell spacing. 729 * 730 * @param e 731 */ 732 public void valueChanged(ListSelectionEvent e) { 733 boolean isAdjusting = e.getValueIsAdjusting(); 734 if (rowSelectionAdjusting && !isAdjusting) { 735 // The assumption is that when the model is no longer adjusting 736 // we will have already gotten all the changes, and therefore 737 // don't need to do an additional paint. 738 rowSelectionAdjusting = false; 739 return; 740 } 741 rowSelectionAdjusting = isAdjusting; 742 // The getCellRect() calls will fail unless there is at least one column. 743 if (table.getRowCount() <= 0 || table.getColumnCount() <= 0) { 744 return; 745 } 746 int firstIndex = limit(e.getFirstIndex(), 0, table.getRowCount() - 1); 747 int lastIndex = limit(e.getLastIndex(), 0, table.getRowCount() - 1); 748 Rectangle firstRowRect = table.getCellRect(firstIndex, 0, true); 749 Rectangle lastRowRect = table.getCellRect(lastIndex, table.getColumnCount() - 1, true); 750 Rectangle dirtyRegion = firstRowRect.union(lastRowRect); 751 dirtyRegion.width += table.getIntercellSpacing().width; 752 table.repaint(dirtyRegion); 753 } 754 755 private int limit(int i, int a, int b) { 756 return Math.min(b, Math.max(i, a)); 757 } 758 } // End of BasicTableUI.Handler 759 760 public class MouseHandler implements MouseInputListener { 761 762 // Component receiving mouse events during editing. 763 // May not be editorComponent. 764 private Component dispatchComponent; 765 private boolean selectedOnPress; 766 private boolean mouseReleaseDeselects; 767 private final static int MOUSE_DRAG_DOES_NOTHING = 0; 768 private final static int MOUSE_DRAG_SELECTS = 1; 769 private final static int MOUSE_DRAG_TOGGLES_SELECTION = 2; 770 private int mouseDragAction; 771 /** index of previously toggled row. */ 772 private int toggledRow = -1; 773 /** index of previously toggled column. */ 774 private int toggledColumn = -1; 775 776 // The Table's mouse listener methods. 777 public void mouseClicked(MouseEvent e) { 778 } 779 780 private void setDispatchComponent(MouseEvent e) { 781 Component editorComponent = table.getEditorComponent(); 782 Point p = e.getPoint(); 783 Point p2 = SwingUtilities.convertPoint(table, p, editorComponent); 784 dispatchComponent = SwingUtilities.getDeepestComponentAt(editorComponent, 785 p2.x, p2.y); 786 } 787 788 private boolean repostEvent(MouseEvent e) { 789 // Check for isEditing() in case another event has 790 // caused the editor to be removed. See bug #4306499. 791 if (dispatchComponent == null || !table.isEditing()) { 792 return false; 793 } 794 MouseEvent e2 = SwingUtilities.convertMouseEvent(table, e, dispatchComponent); 795 dispatchComponent.dispatchEvent(e2); 796 return true; 797 } 798 799 private void setValueIsAdjusting(boolean flag) { 800 table.getSelectionModel().setValueIsAdjusting(flag); 801 table.getColumnModel().getSelectionModel().setValueIsAdjusting(flag); 802 } 803 804 private boolean shouldIgnore(MouseEvent e) { 805 return e.isConsumed() || (!(SwingUtilities.isLeftMouseButton(e) && table.isEnabled())) || e.isPopupTrigger() && 806 (table.rowAtPoint(e.getPoint()) == -1 || 807 table.isRowSelected(table.rowAtPoint(e.getPoint()))); 808 } 809 810 public void mousePressed(MouseEvent e) { 811 mouseDragAction = MOUSE_DRAG_DOES_NOTHING; 812 mouseReleaseDeselects = false; 813 toggledRow = toggledColumn = -1; 814 815 Point p = e.getPoint(); 816 int row = table.rowAtPoint(p); 817 int column = table.columnAtPoint(p); 818 819 // Note: We must check for table.editCellAt, regardless whether 820 // the table is currently editing or not. 821 //---if (! table.isEditing()) { 822 // table.requestFocus(); 823 if (table.editCellAt(row, column, e)) { 824 setDispatchComponent(e); 825 repostEvent(e); 826 } 827 //---} 828 829 // Note: Some applications depend on selection changes only occuring 830 // on focused components. Maybe we must not do any changes to the 831 // selection changes at all, when the compnent is not focused? 832 table.requestFocusInWindow(); 833 834 if (row != -1 && column != -1) { 835 if (table.isRowSelected(row) && e.isPopupTrigger()) { 836 // Do not change the selection, if the item is already 837 // selected, and the user triggers the popup menu. 838 } else { 839 int anchorIndex = table.getSelectionModel().getAnchorSelectionIndex(); 840 // Workaround: Java 1.4 incorrectly reports mouse button 3 down when the Meta-Key is pressed 841 String javaVersion = System.getProperty("java.version"); 842 if (javaVersion.startsWith("1.4") && (e.getModifiersEx() & (MouseEvent.META_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == MouseEvent.META_DOWN_MASK 843 || ! javaVersion.startsWith("1.4") && (e.getModifiersEx() & (MouseEvent.META_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) == MouseEvent.META_DOWN_MASK) { 844 // toggle the selection 845 table.changeSelection(row, column, true, false); 846 toggledRow = row; 847 toggledColumn = column; 848 mouseDragAction = MOUSE_DRAG_TOGGLES_SELECTION; 849 } else if ((e.getModifiersEx() & (MouseEvent.SHIFT_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK | MouseEvent.BUTTON3_DOWN_MASK)) == MouseEvent.SHIFT_DOWN_MASK && 850 anchorIndex != -1) { 851 // add all rows to the selection from the anchor to the row 852 table.changeSelection(row, column, false, true); 853 //table.setRowSelectionInterval(anchorIndex, row); 854 mouseDragAction = MOUSE_DRAG_SELECTS; 855 } else if ((e.getModifiersEx() & (MouseEvent.SHIFT_DOWN_MASK | MouseEvent.META_DOWN_MASK)) == 0) { 856 if (table.isCellSelected(row, column)) { 857 mouseReleaseDeselects = table.isFocusOwner(); 858 } else { 859 // Only select the cell 860 table.changeSelection(row, column, false, false); 861 //table.setRowSelectionInterval(row, row); 862 mouseDragAction = MOUSE_DRAG_SELECTS; 863 } 864 //table.getSelectionModel().setAnchorSelectionIndex(row); 865 } 866 } 867 } 868 869 table.getSelectionModel().setValueIsAdjusting(mouseDragAction != MOUSE_DRAG_DOES_NOTHING); 870 /* 871 if (e.isConsumed()) { 872 selectedOnPress = false; 873 return; 874 } 875 selectedOnPress = true; 876 mouseReleaseDeselects = true; 877 adjustFocusAndSelection(e); 878 */ 879 } 880 881 public void mouseReleased(MouseEvent e) { 882 repostEvent(e); 883 mouseDragAction = MOUSE_DRAG_DOES_NOTHING; 884 if (mouseReleaseDeselects) { 885 int row = table.rowAtPoint(e.getPoint()); 886 int column = table.columnAtPoint(e.getPoint()); 887 table.changeSelection(row, column, false, false); 888 } 889 table.getSelectionModel().setValueIsAdjusting(false); 890 891 if (table.isRequestFocusEnabled() && !table.isEditing()) { 892 table.requestFocus(); 893 } 894 } 895 896 public void mouseEntered(MouseEvent e) { 897 } 898 899 public void mouseExited(MouseEvent e) { 900 } 901 902 // The Table's mouse motion listener methods. 903 public void mouseMoved(MouseEvent e) { 904 } 905 906 public void mouseDragged(MouseEvent e) { 907 if (shouldIgnore(e)) { 908 return; 909 } 910 /* 911 mouseReleaseDeselects = false; 912 repostEvent(e); 913 CellEditor editor = table.getCellEditor(); 914 if (editor == null || editor.shouldSelectCell(e)) { 915 Point p = e.getPoint(); 916 int row2 = table.rowAtPoint(p); 917 int column = table.columnAtPoint(p); 918 // The autoscroller can generate drag events outside the Table's range. 919 if ((column == -1) || (row2 == -1)) { 920 return; 921 } 922 // Fix for 4835633 923 // Until we support drag-selection, dragging should not change 924 // the selection (act like single-select). 925 Object bySize = table.getClientProperty("Table.isFileList"); 926 if (bySize instanceof Boolean && 927 ((Boolean) bySize).booleanValue()) { 928 return; 929 } 930 table.changeSelection(row2, column, false, true); 931 }*/ 932 CellEditor editor = table.getCellEditor(); 933 if (editor == null || editor.shouldSelectCell(e)) { 934 mouseReleaseDeselects = false; 935 if (mouseDragAction == MOUSE_DRAG_SELECTS) { 936 int row = table.rowAtPoint(e.getPoint()); 937 int column = table.columnAtPoint(e.getPoint()); 938 if (row != -1 && column != -1) { 939 Rectangle cellBounds = table.getCellRect(row, column, true); 940 table.scrollRectToVisible(cellBounds); 941 table.changeSelection(row, column, false, true); 942 } 943 } else if (mouseDragAction == MOUSE_DRAG_TOGGLES_SELECTION) { 944 int row = table.rowAtPoint(e.getPoint()); 945 int column = table.columnAtPoint(e.getPoint()); 946 boolean isCellSelection = table.getCellSelectionEnabled(); 947 if (row != -1 && column != -1 && 948 ((!isCellSelection && row != toggledRow) || 949 (isCellSelection && (row != toggledRow || column != toggledColumn)))) { 950 Rectangle cellBounds = table.getCellRect(row, column, true); 951 table.scrollRectToVisible(cellBounds); 952 table.changeSelection(row, column, true, false); 953 toggledRow = row; 954 toggledColumn = column; 955 } 956 } 957 } 958 } 959 } 960 961 private class FocusHandler implements FocusListener { 962 // FocusListener 963 private void repaintSelection() { 964 int[] rows = table.getSelectedRows(); 965 Rectangle dirtyRect = null; 966 for (int r = 0; r < rows.length; r++) { 967 for (int c = 0, n = table.getColumnCount(); c < n; c++) { 968 table.repaint(table.getCellRect(rows[r], c, false)); 969 } 970 } 971 } 972 973 public void focusGained(FocusEvent e) { 974 repaintSelection(); 975 } 976 977 public void focusLost(FocusEvent e) { 978 repaintSelection(); 979 } 980 } 981 } 982