001/* BasicTableUI.java -- 002 Copyright (C) 2004 Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.plaf.basic; 040 041import java.awt.Color; 042import java.awt.Component; 043import java.awt.ComponentOrientation; 044import java.awt.Dimension; 045import java.awt.Graphics; 046import java.awt.Point; 047import java.awt.Rectangle; 048import java.awt.event.ActionEvent; 049import java.awt.event.FocusEvent; 050import java.awt.event.FocusListener; 051import java.awt.event.KeyEvent; 052import java.awt.event.KeyListener; 053import java.awt.event.MouseEvent; 054import java.beans.PropertyChangeEvent; 055import java.beans.PropertyChangeListener; 056 057import javax.swing.AbstractAction; 058import javax.swing.Action; 059import javax.swing.ActionMap; 060import javax.swing.CellRendererPane; 061import javax.swing.DefaultCellEditor; 062import javax.swing.DefaultListSelectionModel; 063import javax.swing.InputMap; 064import javax.swing.JComponent; 065import javax.swing.JTable; 066import javax.swing.ListSelectionModel; 067import javax.swing.LookAndFeel; 068import javax.swing.SwingUtilities; 069import javax.swing.TransferHandler; 070import javax.swing.UIManager; 071import javax.swing.border.Border; 072import javax.swing.event.ChangeEvent; 073import javax.swing.event.MouseInputListener; 074import javax.swing.plaf.ActionMapUIResource; 075import javax.swing.plaf.ComponentUI; 076import javax.swing.plaf.TableUI; 077import javax.swing.table.TableCellEditor; 078import javax.swing.table.TableCellRenderer; 079import javax.swing.table.TableColumn; 080import javax.swing.table.TableColumnModel; 081import javax.swing.table.TableModel; 082 083public class BasicTableUI extends TableUI 084{ 085 public static ComponentUI createUI(JComponent comp) 086 { 087 return new BasicTableUI(); 088 } 089 090 protected FocusListener focusListener; 091 protected KeyListener keyListener; 092 protected MouseInputListener mouseInputListener; 093 protected CellRendererPane rendererPane; 094 protected JTable table; 095 096 /** The normal cell border. */ 097 Border cellBorder; 098 099 /** The action bound to KeyStrokes. */ 100 TableAction action; 101 102 /** 103 * Listens for changes to the tables properties. 104 */ 105 private PropertyChangeListener propertyChangeListener; 106 107 /** 108 * Handles key events for the JTable. Key events should be handled through 109 * the InputMap/ActionMap mechanism since JDK1.3. This class is only there 110 * for backwards compatibility. 111 * 112 * @author Roman Kennke (kennke@aicas.com) 113 */ 114 public class KeyHandler implements KeyListener 115 { 116 117 /** 118 * Receives notification that a key has been pressed and released. 119 * Activates the editing session for the focused cell by pressing the 120 * character keys. 121 * 122 * @param event the key event 123 */ 124 public void keyTyped(KeyEvent event) 125 { 126 // Key events should be handled through the InputMap/ActionMap mechanism 127 // since JDK1.3. This class is only there for backwards compatibility. 128 129 // Editor activation is a specific kind of response to ''any'' 130 // character key. Hence it is handled here. 131 if (!table.isEditing() && table.isEnabled()) 132 { 133 int r = table.getSelectedRow(); 134 int c = table.getSelectedColumn(); 135 if (table.isCellEditable(r, c)) 136 table.editCellAt(r, c); 137 } 138 } 139 140 /** 141 * Receives notification that a key has been pressed. 142 * 143 * @param event the key event 144 */ 145 public void keyPressed(KeyEvent event) 146 { 147 // Key events should be handled through the InputMap/ActionMap mechanism 148 // since JDK1.3. This class is only there for backwards compatibility. 149 } 150 151 /** 152 * Receives notification that a key has been released. 153 * 154 * @param event the key event 155 */ 156 public void keyReleased(KeyEvent event) 157 { 158 // Key events should be handled through the InputMap/ActionMap mechanism 159 // since JDK1.3. This class is only there for backwards compatibility. 160 } 161 } 162 163 public class FocusHandler implements FocusListener 164 { 165 public void focusGained(FocusEvent e) 166 { 167 // The only thing that is affected by a focus change seems to be 168 // how the lead cell is painted. So we repaint this cell. 169 repaintLeadCell(); 170 } 171 172 public void focusLost(FocusEvent e) 173 { 174 // The only thing that is affected by a focus change seems to be 175 // how the lead cell is painted. So we repaint this cell. 176 repaintLeadCell(); 177 } 178 179 /** 180 * Repaints the lead cell in response to a focus change, to refresh 181 * the display of the focus indicator. 182 */ 183 private void repaintLeadCell() 184 { 185 int rowCount = table.getRowCount(); 186 int columnCount = table.getColumnCount(); 187 int rowLead = table.getSelectionModel().getLeadSelectionIndex(); 188 int columnLead = table.getColumnModel().getSelectionModel(). 189 getLeadSelectionIndex(); 190 if (rowLead >= 0 && rowLead < rowCount && columnLead >= 0 191 && columnLead < columnCount) 192 { 193 Rectangle dirtyRect = table.getCellRect(rowLead, columnLead, false); 194 table.repaint(dirtyRect); 195 } 196 } 197 } 198 199 public class MouseInputHandler implements MouseInputListener 200 { 201 Point begin, curr; 202 203 private void updateSelection(boolean controlPressed) 204 { 205 // Update the rows 206 int lo_row = table.rowAtPoint(begin); 207 int hi_row = table.rowAtPoint(curr); 208 ListSelectionModel rowModel = table.getSelectionModel(); 209 if (lo_row != -1 && hi_row != -1) 210 { 211 if (controlPressed && rowModel.getSelectionMode() 212 != ListSelectionModel.SINGLE_SELECTION) 213 rowModel.addSelectionInterval(lo_row, hi_row); 214 else 215 rowModel.setSelectionInterval(lo_row, hi_row); 216 } 217 218 // Update the columns 219 int lo_col = table.columnAtPoint(begin); 220 int hi_col = table.columnAtPoint(curr); 221 ListSelectionModel colModel = table.getColumnModel(). 222 getSelectionModel(); 223 if (lo_col != -1 && hi_col != -1) 224 { 225 if (controlPressed && colModel.getSelectionMode() != 226 ListSelectionModel.SINGLE_SELECTION) 227 colModel.addSelectionInterval(lo_col, hi_col); 228 else 229 colModel.setSelectionInterval(lo_col, hi_col); 230 } 231 } 232 233 /** 234 * For the double click, start the cell editor. 235 */ 236 public void mouseClicked(MouseEvent e) 237 { 238 Point p = e.getPoint(); 239 int row = table.rowAtPoint(p); 240 int col = table.columnAtPoint(p); 241 if (table.isCellEditable(row, col)) 242 { 243 // If the cell editor is the default editor, we request the 244 // number of the required clicks from it. Otherwise, 245 // require two clicks (double click). 246 TableCellEditor editor = table.getCellEditor(row, col); 247 if (editor instanceof DefaultCellEditor) 248 { 249 DefaultCellEditor ce = (DefaultCellEditor) editor; 250 if (e.getClickCount() < ce.getClickCountToStart()) 251 return; 252 } 253 table.editCellAt(row, col); 254 } 255 } 256 257 public void mouseDragged(MouseEvent e) 258 { 259 if (table.isEnabled()) 260 { 261 curr = new Point(e.getX(), e.getY()); 262 updateSelection(e.isControlDown()); 263 } 264 } 265 266 public void mouseEntered(MouseEvent e) 267 { 268 // Nothing to do here. 269 } 270 271 public void mouseExited(MouseEvent e) 272 { 273 // Nothing to do here. 274 } 275 276 public void mouseMoved(MouseEvent e) 277 { 278 // Nothing to do here. 279 } 280 281 public void mousePressed(MouseEvent e) 282 { 283 if (table.isEnabled()) 284 { 285 ListSelectionModel rowModel = table.getSelectionModel(); 286 ListSelectionModel colModel = table.getColumnModel().getSelectionModel(); 287 int rowLead = rowModel.getLeadSelectionIndex(); 288 int colLead = colModel.getLeadSelectionIndex(); 289 290 begin = new Point(e.getX(), e.getY()); 291 curr = new Point(e.getX(), e.getY()); 292 //if control is pressed and the cell is already selected, deselect it 293 if (e.isControlDown() && table.isCellSelected( 294 table.rowAtPoint(begin), table.columnAtPoint(begin))) 295 { 296 table.getSelectionModel(). 297 removeSelectionInterval(table.rowAtPoint(begin), 298 table.rowAtPoint(begin)); 299 table.getColumnModel().getSelectionModel(). 300 removeSelectionInterval(table.columnAtPoint(begin), 301 table.columnAtPoint(begin)); 302 } 303 else 304 updateSelection(e.isControlDown()); 305 306 // If we were editing, but the moved to another cell, stop editing 307 if (rowLead != rowModel.getLeadSelectionIndex() || 308 colLead != colModel.getLeadSelectionIndex()) 309 if (table.isEditing()) 310 table.editingStopped(new ChangeEvent(e)); 311 312 // Must request focus explicitly. 313 table.requestFocusInWindow(); 314 } 315 } 316 317 public void mouseReleased(MouseEvent e) 318 { 319 if (table.isEnabled()) 320 { 321 begin = null; 322 curr = null; 323 } 324 } 325 } 326 327 /** 328 * Listens for changes to the model property of the JTable and adjusts some 329 * settings. 330 * 331 * @author Roman Kennke (kennke@aicas.com) 332 */ 333 private class PropertyChangeHandler implements PropertyChangeListener 334 { 335 /** 336 * Receives notification if one of the JTable's properties changes. 337 * 338 * @param ev the property change event 339 */ 340 public void propertyChange(PropertyChangeEvent ev) 341 { 342 String propName = ev.getPropertyName(); 343 if (propName.equals("model")) 344 { 345 ListSelectionModel rowSel = table.getSelectionModel(); 346 rowSel.clearSelection(); 347 ListSelectionModel colSel = table.getColumnModel().getSelectionModel(); 348 colSel.clearSelection(); 349 TableModel model = table.getModel(); 350 351 // Adjust lead and anchor selection indices of the row and column 352 // selection models. 353 if (model.getRowCount() > 0) 354 { 355 rowSel.setAnchorSelectionIndex(0); 356 rowSel.setLeadSelectionIndex(0); 357 } 358 else 359 { 360 rowSel.setAnchorSelectionIndex(-1); 361 rowSel.setLeadSelectionIndex(-1); 362 } 363 if (model.getColumnCount() > 0) 364 { 365 colSel.setAnchorSelectionIndex(0); 366 colSel.setLeadSelectionIndex(0); 367 } 368 else 369 { 370 colSel.setAnchorSelectionIndex(-1); 371 colSel.setLeadSelectionIndex(-1); 372 } 373 } 374 } 375 } 376 377 protected FocusListener createFocusListener() 378 { 379 return new FocusHandler(); 380 } 381 382 protected MouseInputListener createMouseInputListener() 383 { 384 return new MouseInputHandler(); 385 } 386 387 388 /** 389 * Creates and returns a key listener for the JTable. 390 * 391 * @return a key listener for the JTable 392 */ 393 protected KeyListener createKeyListener() 394 { 395 return new KeyHandler(); 396 } 397 398 /** 399 * Return the maximum size of the table. The maximum height is the row 400 * height times the number of rows. The maximum width is the sum of 401 * the maximum widths of each column. 402 * 403 * @param comp the component whose maximum size is being queried, 404 * this is ignored. 405 * @return a Dimension object representing the maximum size of the table, 406 * or null if the table has no elements. 407 */ 408 public Dimension getMaximumSize(JComponent comp) 409 { 410 int maxTotalColumnWidth = 0; 411 for (int i = 0; i < table.getColumnCount(); i++) 412 maxTotalColumnWidth += table.getColumnModel().getColumn(i).getMaxWidth(); 413 414 return new Dimension(maxTotalColumnWidth, getHeight()); 415 } 416 417 /** 418 * Return the minimum size of the table. The minimum height is the row 419 * height times the number of rows. The minimum width is the sum of 420 * the minimum widths of each column. 421 * 422 * @param comp the component whose minimum size is being queried, 423 * this is ignored. 424 * @return a Dimension object representing the minimum size of the table, 425 * or null if the table has no elements. 426 */ 427 public Dimension getMinimumSize(JComponent comp) 428 { 429 int minTotalColumnWidth = 0; 430 for (int i = 0; i < table.getColumnCount(); i++) 431 minTotalColumnWidth += table.getColumnModel().getColumn(i).getMinWidth(); 432 433 return new Dimension(minTotalColumnWidth, getHeight()); 434 } 435 436 /** 437 * Returns the preferred size for the table of that UI. 438 * 439 * @param comp ignored, the <code>table</code> field is used instead 440 * 441 * @return the preferred size for the table of that UI 442 */ 443 public Dimension getPreferredSize(JComponent comp) 444 { 445 int prefTotalColumnWidth = 0; 446 TableColumnModel tcm = table.getColumnModel(); 447 448 for (int i = 0; i < tcm.getColumnCount(); i++) 449 { 450 TableColumn col = tcm.getColumn(i); 451 prefTotalColumnWidth += col.getPreferredWidth(); 452 } 453 454 return new Dimension(prefTotalColumnWidth, getHeight()); 455 } 456 457 /** 458 * Returns the table height. This helper method is used by 459 * {@link #getMinimumSize(JComponent)}, {@link #getPreferredSize(JComponent)} 460 * and {@link #getMaximumSize(JComponent)} to determine the table height. 461 * 462 * @return the table height 463 */ 464 private int getHeight() 465 { 466 int height = 0; 467 int rowCount = table.getRowCount(); 468 if (rowCount > 0 && table.getColumnCount() > 0) 469 { 470 Rectangle r = table.getCellRect(rowCount - 1, 0, true); 471 height = r.y + r.height; 472 } 473 return height; 474 } 475 476 protected void installDefaults() 477 { 478 LookAndFeel.installColorsAndFont(table, "Table.background", 479 "Table.foreground", "Table.font"); 480 table.setGridColor(UIManager.getColor("Table.gridColor")); 481 table.setSelectionForeground(UIManager.getColor("Table.selectionForeground")); 482 table.setSelectionBackground(UIManager.getColor("Table.selectionBackground")); 483 table.setOpaque(true); 484 } 485 486 /** 487 * Installs keyboard actions on the table. 488 */ 489 protected void installKeyboardActions() 490 { 491 // Install the input map. 492 InputMap inputMap = 493 (InputMap) SharedUIDefaults.get("Table.ancestorInputMap"); 494 SwingUtilities.replaceUIInputMap(table, 495 JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, 496 inputMap); 497 498 // FIXME: The JDK uses a LazyActionMap for parentActionMap 499 SwingUtilities.replaceUIActionMap(table, getActionMap()); 500 501 } 502 503 /** 504 * Fetches the action map from the UI defaults, or create a new one 505 * if the action map hasn't been initialized. 506 * 507 * @return the action map 508 */ 509 private ActionMap getActionMap() 510 { 511 ActionMap am = (ActionMap) UIManager.get("Table.actionMap"); 512 if (am == null) 513 { 514 am = createDefaultActions(); 515 UIManager.getLookAndFeelDefaults().put("Table.actionMap", am); 516 } 517 return am; 518 } 519 520 private ActionMap createDefaultActions() 521 { 522 ActionMapUIResource am = new ActionMapUIResource(); 523 Action action = new TableAction(); 524 525 am.put("cut", TransferHandler.getCutAction()); 526 am.put("copy", TransferHandler.getCopyAction()); 527 am.put("paste", TransferHandler.getPasteAction()); 528 529 am.put("cancel", action); 530 am.put("selectAll", action); 531 am.put("clearSelection", action); 532 am.put("startEditing", action); 533 534 am.put("selectNextRow", action); 535 am.put("selectNextRowCell", action); 536 am.put("selectNextRowExtendSelection", action); 537 am.put("selectNextRowChangeLead", action); 538 539 am.put("selectPreviousRow", action); 540 am.put("selectPreviousRowCell", action); 541 am.put("selectPreviousRowExtendSelection", action); 542 am.put("selectPreviousRowChangeLead", action); 543 544 am.put("selectNextColumn", action); 545 am.put("selectNextColumnCell", action); 546 am.put("selectNextColumnExtendSelection", action); 547 am.put("selectNextColumnChangeLead", action); 548 549 am.put("selectPreviousColumn", action); 550 am.put("selectPreviousColumnCell", action); 551 am.put("selectPreviousColumnExtendSelection", action); 552 am.put("selectPreviousColumnChangeLead", action); 553 554 am.put("scrollLeftChangeSelection", action); 555 am.put("scrollLeftExtendSelection", action); 556 am.put("scrollRightChangeSelection", action); 557 am.put("scrollRightExtendSelection", action); 558 559 am.put("scrollUpChangeSelection", action); 560 am.put("scrollUpExtendSelection", action); 561 am.put("scrollDownChangeSelection", action); 562 am.put("scrolldownExtendSelection", action); 563 564 am.put("selectFirstColumn", action); 565 am.put("selectFirstColumnExtendSelection", action); 566 am.put("selectLastColumn", action); 567 am.put("selectLastColumnExtendSelection", action); 568 569 am.put("selectFirstRow", action); 570 am.put("selectFirstRowExtendSelection", action); 571 am.put("selectLastRow", action); 572 am.put("selectLastRowExtendSelection", action); 573 574 am.put("addToSelection", action); 575 am.put("toggleAndAnchor", action); 576 am.put("extendTo", action); 577 am.put("moveSelectionTo", action); 578 579 return am; 580 } 581 582 /** 583 * This class implements the actions that we want to happen 584 * when specific keys are pressed for the JTable. The actionPerformed 585 * method is called when a key that has been registered for the JTable 586 * is received. 587 */ 588 private static class TableAction 589 extends AbstractAction 590 { 591 /** 592 * What to do when this action is called. 593 * 594 * @param e the ActionEvent that caused this action. 595 */ 596 public void actionPerformed(ActionEvent e) 597 { 598 JTable table = (JTable) e.getSource(); 599 600 DefaultListSelectionModel rowModel 601 = (DefaultListSelectionModel) table.getSelectionModel(); 602 DefaultListSelectionModel colModel 603 = (DefaultListSelectionModel) table.getColumnModel().getSelectionModel(); 604 605 int rowLead = rowModel.getLeadSelectionIndex(); 606 int rowMax = table.getModel().getRowCount() - 1; 607 608 int colLead = colModel.getLeadSelectionIndex(); 609 int colMax = table.getModel().getColumnCount() - 1; 610 611 // The command with which the action has been called is stored 612 // in this undocumented action value. This allows us to have only 613 // one Action instance to serve all keyboard input for JTable. 614 String command = (String) getValue("__command__"); 615 if (command.equals("selectPreviousRowExtendSelection")) 616 { 617 rowModel.setLeadSelectionIndex(Math.max(rowLead - 1, 0)); 618 } 619 else if (command.equals("selectLastColumn")) 620 { 621 colModel.setSelectionInterval(colMax, colMax); 622 } 623 else if (command.equals("startEditing")) 624 { 625 if (table.isCellEditable(rowLead, colLead)) 626 table.editCellAt(rowLead, colLead); 627 } 628 else if (command.equals("selectFirstRowExtendSelection")) 629 { 630 rowModel.setLeadSelectionIndex(0); 631 } 632 else if (command.equals("selectFirstColumn")) 633 { 634 colModel.setSelectionInterval(0, 0); 635 } 636 else if (command.equals("selectFirstColumnExtendSelection")) 637 { 638 colModel.setLeadSelectionIndex(0); 639 } 640 else if (command.equals("selectLastRow")) 641 { 642 rowModel.setSelectionInterval(rowMax, rowMax); 643 } 644 else if (command.equals("selectNextRowExtendSelection")) 645 { 646 rowModel.setLeadSelectionIndex(Math.min(rowLead + 1, rowMax)); 647 } 648 else if (command.equals("selectFirstRow")) 649 { 650 rowModel.setSelectionInterval(0, 0); 651 } 652 else if (command.equals("selectNextColumnExtendSelection")) 653 { 654 colModel.setLeadSelectionIndex(Math.min(colLead + 1, colMax)); 655 } 656 else if (command.equals("selectLastColumnExtendSelection")) 657 { 658 colModel.setLeadSelectionIndex(colMax); 659 } 660 else if (command.equals("selectPreviousColumnExtendSelection")) 661 { 662 colModel.setLeadSelectionIndex(Math.max(colLead - 1, 0)); 663 } 664 else if (command.equals("selectNextRow")) 665 { 666 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax), 667 Math.min(rowLead + 1, rowMax)); 668 } 669 else if (command.equals("scrollUpExtendSelection")) 670 { 671 int target; 672 if (rowLead == getFirstVisibleRowIndex(table)) 673 target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) 674 - getFirstVisibleRowIndex(table) + 1)); 675 else 676 target = getFirstVisibleRowIndex(table); 677 678 rowModel.setLeadSelectionIndex(target); 679 colModel.setLeadSelectionIndex(colLead); 680 } 681 else if (command.equals("selectPreviousRow")) 682 { 683 rowModel.setSelectionInterval(Math.max(rowLead - 1, 0), 684 Math.max(rowLead - 1, 0)); 685 } 686 else if (command.equals("scrollRightChangeSelection")) 687 { 688 int target; 689 if (colLead == getLastVisibleColumnIndex(table)) 690 target = Math.min(colMax, colLead 691 + (getLastVisibleColumnIndex(table) 692 - getFirstVisibleColumnIndex(table) + 1)); 693 else 694 target = getLastVisibleColumnIndex(table); 695 696 colModel.setSelectionInterval(target, target); 697 rowModel.setSelectionInterval(rowLead, rowLead); 698 } 699 else if (command.equals("selectPreviousColumn")) 700 { 701 colModel.setSelectionInterval(Math.max(colLead - 1, 0), 702 Math.max(colLead - 1, 0)); 703 } 704 else if (command.equals("scrollLeftChangeSelection")) 705 { 706 int target; 707 if (colLead == getFirstVisibleColumnIndex(table)) 708 target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) 709 - getFirstVisibleColumnIndex(table) + 1)); 710 else 711 target = getFirstVisibleColumnIndex(table); 712 713 colModel.setSelectionInterval(target, target); 714 rowModel.setSelectionInterval(rowLead, rowLead); 715 } 716 else if (command.equals("clearSelection")) 717 { 718 table.clearSelection(); 719 } 720 else if (command.equals("cancel")) 721 { 722 // FIXME: implement other parts of "cancel" like undo-ing last 723 // selection. Right now it just calls editingCancelled if 724 // we're currently editing. 725 if (table.isEditing()) 726 table.editingCanceled(new ChangeEvent("cancel")); 727 } 728 else if (command.equals("selectNextRowCell") 729 || command.equals("selectPreviousRowCell") 730 || command.equals("selectNextColumnCell") 731 || command.equals("selectPreviousColumnCell")) 732 { 733 // If nothing is selected, select the first cell in the table 734 if (table.getSelectedRowCount() == 0 && 735 table.getSelectedColumnCount() == 0) 736 { 737 rowModel.setSelectionInterval(0, 0); 738 colModel.setSelectionInterval(0, 0); 739 return; 740 } 741 742 // If the lead selection index isn't selected (ie a remove operation 743 // happened, then set the lead to the first selected cell in the 744 // table 745 if (!table.isCellSelected(rowLead, colLead)) 746 { 747 rowModel.addSelectionInterval(rowModel.getMinSelectionIndex(), 748 rowModel.getMinSelectionIndex()); 749 colModel.addSelectionInterval(colModel.getMinSelectionIndex(), 750 colModel.getMinSelectionIndex()); 751 return; 752 } 753 754 // multRowsSelected and multColsSelected tell us if multiple rows or 755 // columns are selected, respectively 756 boolean multRowsSelected, multColsSelected; 757 multRowsSelected = table.getSelectedRowCount() > 1 && 758 table.getRowSelectionAllowed(); 759 760 multColsSelected = table.getSelectedColumnCount() > 1 && 761 table.getColumnSelectionAllowed(); 762 763 // If there is just one selection, select the next cell, and wrap 764 // when you get to the edges of the table. 765 if (!multColsSelected && !multRowsSelected) 766 { 767 if (command.indexOf("Column") != -1) 768 advanceSingleSelection(colModel, colMax, rowModel, rowMax, 769 command.equals("selectPreviousColumnCell")); 770 else 771 advanceSingleSelection(rowModel, rowMax, colModel, colMax, 772 command.equals("selectPreviousRowCell")); 773 return; 774 } 775 776 777 // rowMinSelected and rowMaxSelected are the minimum and maximum 778 // values respectively of selected cells in the row selection model 779 // Similarly for colMinSelected and colMaxSelected. 780 int rowMaxSelected = table.getRowSelectionAllowed() ? 781 rowModel.getMaxSelectionIndex() : table.getModel().getRowCount() - 1; 782 int rowMinSelected = table.getRowSelectionAllowed() ? 783 rowModel.getMinSelectionIndex() : 0; 784 int colMaxSelected = table.getColumnSelectionAllowed() ? 785 colModel.getMaxSelectionIndex() : 786 table.getModel().getColumnCount() - 1; 787 int colMinSelected = table.getColumnSelectionAllowed() ? 788 colModel.getMinSelectionIndex() : 0; 789 790 // If there are multiple rows and columns selected, select the next 791 // cell and wrap at the edges of the selection. 792 if (command.indexOf("Column") != -1) 793 advanceMultipleSelection(table, colModel, colMinSelected, 794 colMaxSelected, rowModel, rowMinSelected, 795 rowMaxSelected, 796 command.equals("selectPreviousColumnCell"), 797 true); 798 799 else 800 advanceMultipleSelection(table, rowModel, rowMinSelected, 801 rowMaxSelected, colModel, colMinSelected, 802 colMaxSelected, 803 command.equals("selectPreviousRowCell"), 804 false); 805 } 806 else if (command.equals("selectNextColumn")) 807 { 808 colModel.setSelectionInterval(Math.min(colLead + 1, colMax), 809 Math.min(colLead + 1, colMax)); 810 } 811 else if (command.equals("scrollLeftExtendSelection")) 812 { 813 int target; 814 if (colLead == getFirstVisibleColumnIndex(table)) 815 target = Math.max(0, colLead - (getLastVisibleColumnIndex(table) 816 - getFirstVisibleColumnIndex(table) + 1)); 817 else 818 target = getFirstVisibleColumnIndex(table); 819 820 colModel.setLeadSelectionIndex(target); 821 rowModel.setLeadSelectionIndex(rowLead); 822 } 823 else if (command.equals("scrollDownChangeSelection")) 824 { 825 int target; 826 if (rowLead == getLastVisibleRowIndex(table)) 827 target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) 828 - getFirstVisibleRowIndex(table) + 1)); 829 else 830 target = getLastVisibleRowIndex(table); 831 832 rowModel.setSelectionInterval(target, target); 833 colModel.setSelectionInterval(colLead, colLead); 834 } 835 else if (command.equals("scrollRightExtendSelection")) 836 { 837 int target; 838 if (colLead == getLastVisibleColumnIndex(table)) 839 target = Math.min(colMax, colLead + (getLastVisibleColumnIndex(table) 840 - getFirstVisibleColumnIndex(table) + 1)); 841 else 842 target = getLastVisibleColumnIndex(table); 843 844 colModel.setLeadSelectionIndex(target); 845 rowModel.setLeadSelectionIndex(rowLead); 846 } 847 else if (command.equals("selectAll")) 848 { 849 table.selectAll(); 850 } 851 else if (command.equals("selectLastRowExtendSelection")) 852 { 853 rowModel.setLeadSelectionIndex(rowMax); 854 colModel.setLeadSelectionIndex(colLead); 855 } 856 else if (command.equals("scrollDownExtendSelection")) 857 { 858 int target; 859 if (rowLead == getLastVisibleRowIndex(table)) 860 target = Math.min(rowMax, rowLead + (getLastVisibleRowIndex(table) 861 - getFirstVisibleRowIndex(table) + 1)); 862 else 863 target = getLastVisibleRowIndex(table); 864 865 rowModel.setLeadSelectionIndex(target); 866 colModel.setLeadSelectionIndex(colLead); 867 } 868 else if (command.equals("scrollUpChangeSelection")) 869 { 870 int target; 871 if (rowLead == getFirstVisibleRowIndex(table)) 872 target = Math.max(0, rowLead - (getLastVisibleRowIndex(table) 873 - getFirstVisibleRowIndex(table) + 1)); 874 else 875 target = getFirstVisibleRowIndex(table); 876 877 rowModel.setSelectionInterval(target, target); 878 colModel.setSelectionInterval(colLead, colLead); 879 } 880 else if (command.equals("selectNextRowChangeLead")) 881 { 882 if (rowModel.getSelectionMode() 883 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) 884 { 885 // just "selectNextRow" 886 rowModel.setSelectionInterval(Math.min(rowLead + 1, rowMax), 887 Math.min(rowLead + 1, rowMax)); 888 colModel.setSelectionInterval(colLead, colLead); 889 } 890 else 891 rowModel.moveLeadSelectionIndex(Math.min(rowLead + 1, rowMax)); 892 } 893 else if (command.equals("selectPreviousRowChangeLead")) 894 { 895 if (rowModel.getSelectionMode() 896 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) 897 { 898 // just selectPreviousRow 899 rowModel.setSelectionInterval(Math.max(rowLead - 1, 0), 900 Math.min(rowLead - 1, 0)); 901 colModel.setSelectionInterval(colLead, colLead); 902 } 903 else 904 rowModel.moveLeadSelectionIndex(Math.max(rowLead - 1, 0)); 905 } 906 else if (command.equals("selectNextColumnChangeLead")) 907 { 908 if (colModel.getSelectionMode() 909 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) 910 { 911 // just selectNextColumn 912 rowModel.setSelectionInterval(rowLead, rowLead); 913 colModel.setSelectionInterval(Math.min(colLead + 1, colMax), 914 Math.min(colLead + 1, colMax)); 915 } 916 else 917 colModel.moveLeadSelectionIndex(Math.min(colLead + 1, colMax)); 918 } 919 else if (command.equals("selectPreviousColumnChangeLead")) 920 { 921 if (colModel.getSelectionMode() 922 != ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) 923 { 924 // just selectPreviousColumn 925 rowModel.setSelectionInterval(rowLead, rowLead); 926 colModel.setSelectionInterval(Math.max(colLead - 1, 0), 927 Math.max(colLead - 1, 0)); 928 929 } 930 else 931 colModel.moveLeadSelectionIndex(Math.max(colLead - 1, 0)); 932 } 933 else if (command.equals("addToSelection")) 934 { 935 if (!table.isEditing()) 936 { 937 int oldRowAnchor = rowModel.getAnchorSelectionIndex(); 938 int oldColAnchor = colModel.getAnchorSelectionIndex(); 939 rowModel.addSelectionInterval(rowLead, rowLead); 940 colModel.addSelectionInterval(colLead, colLead); 941 rowModel.setAnchorSelectionIndex(oldRowAnchor); 942 colModel.setAnchorSelectionIndex(oldColAnchor); 943 } 944 } 945 else if (command.equals("extendTo")) 946 { 947 rowModel.setSelectionInterval(rowModel.getAnchorSelectionIndex(), 948 rowLead); 949 colModel.setSelectionInterval(colModel.getAnchorSelectionIndex(), 950 colLead); 951 } 952 else if (command.equals("toggleAndAnchor")) 953 { 954 if (rowModel.isSelectedIndex(rowLead)) 955 rowModel.removeSelectionInterval(rowLead, rowLead); 956 else 957 rowModel.addSelectionInterval(rowLead, rowLead); 958 959 if (colModel.isSelectedIndex(colLead)) 960 colModel.removeSelectionInterval(colLead, colLead); 961 else 962 colModel.addSelectionInterval(colLead, colLead); 963 964 rowModel.setAnchorSelectionIndex(rowLead); 965 colModel.setAnchorSelectionIndex(colLead); 966 } 967 else if (command.equals("stopEditing")) 968 { 969 table.editingStopped(new ChangeEvent(command)); 970 } 971 else 972 { 973 // If we're here that means we bound this TableAction class 974 // to a keyboard input but we either want to ignore that input 975 // or we just haven't implemented its action yet. 976 977 // Uncomment the following line to print the names of unused bindings 978 // when their keys are pressed 979 980 // System.out.println ("not implemented: "+e.getActionCommand()); 981 } 982 983 // Any commands whose keyStrokes should be used by the Editor should not 984 // cause editing to be stopped: ie, the SPACE sends "addToSelection" but 985 // if the table is in editing mode, the space should not cause us to stop 986 // editing because it should be used by the Editor. 987 if (table.isEditing() && command != "startEditing" 988 && command != "addToSelection") 989 table.editingStopped(new ChangeEvent("update")); 990 991 table.scrollRectToVisible(table.getCellRect( 992 rowModel.getLeadSelectionIndex(), colModel.getLeadSelectionIndex(), 993 false)); 994 } 995 996 /** 997 * Returns the column index of the first visible column. 998 * @return the column index of the first visible column. 999 */ 1000 int getFirstVisibleColumnIndex(JTable table) 1001 { 1002 ComponentOrientation or = table.getComponentOrientation(); 1003 Rectangle r = table.getVisibleRect(); 1004 if (!or.isLeftToRight()) 1005 r.translate((int) r.getWidth() - 1, 0); 1006 return table.columnAtPoint(r.getLocation()); 1007 } 1008 1009 /** 1010 * Returns the column index of the last visible column. 1011 * 1012 */ 1013 int getLastVisibleColumnIndex(JTable table) 1014 { 1015 ComponentOrientation or = table.getComponentOrientation(); 1016 Rectangle r = table.getVisibleRect(); 1017 if (or.isLeftToRight()) 1018 r.translate((int) r.getWidth() - 1, 0); 1019 return table.columnAtPoint(r.getLocation()); 1020 } 1021 1022 /** 1023 * Returns the row index of the first visible row. 1024 * 1025 */ 1026 int getFirstVisibleRowIndex(JTable table) 1027 { 1028 ComponentOrientation or = table.getComponentOrientation(); 1029 Rectangle r = table.getVisibleRect(); 1030 if (!or.isLeftToRight()) 1031 r.translate((int) r.getWidth() - 1, 0); 1032 return table.rowAtPoint(r.getLocation()); 1033 } 1034 1035 /** 1036 * Returns the row index of the last visible row. 1037 * 1038 */ 1039 int getLastVisibleRowIndex(JTable table) 1040 { 1041 ComponentOrientation or = table.getComponentOrientation(); 1042 Rectangle r = table.getVisibleRect(); 1043 r.translate(0, (int) r.getHeight() - 1); 1044 if (or.isLeftToRight()) 1045 r.translate((int) r.getWidth() - 1, 0); 1046 // The next if makes sure that we don't return -1 simply because 1047 // there is white space at the bottom of the table (ie, the display 1048 // area is larger than the table) 1049 if (table.rowAtPoint(r.getLocation()) == -1) 1050 { 1051 if (getFirstVisibleRowIndex(table) == -1) 1052 return -1; 1053 else 1054 return table.getModel().getRowCount() - 1; 1055 } 1056 return table.rowAtPoint(r.getLocation()); 1057 } 1058 1059 /** 1060 * A helper method for the key bindings. Used because the actions 1061 * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar. 1062 * 1063 * Selects the next (previous if SHIFT pressed) column for TAB, or row for 1064 * ENTER from within the currently selected cells. 1065 * 1066 * @param firstModel the ListSelectionModel for columns (TAB) or 1067 * rows (ENTER) 1068 * @param firstMin the first selected index in firstModel 1069 * @param firstMax the last selected index in firstModel 1070 * @param secondModel the ListSelectionModel for rows (TAB) or 1071 * columns (ENTER) 1072 * @param secondMin the first selected index in secondModel 1073 * @param secondMax the last selected index in secondModel 1074 * @param reverse true if shift was held for the event 1075 * @param eventIsTab true if TAB was pressed, false if ENTER pressed 1076 */ 1077 void advanceMultipleSelection(JTable table, ListSelectionModel firstModel, 1078 int firstMin, 1079 int firstMax, ListSelectionModel secondModel, 1080 int secondMin, int secondMax, boolean reverse, 1081 boolean eventIsTab) 1082 { 1083 // If eventIsTab, all the "firsts" correspond to columns, otherwise, to 1084 // rows "seconds" correspond to the opposite 1085 int firstLead = firstModel.getLeadSelectionIndex(); 1086 int secondLead = secondModel.getLeadSelectionIndex(); 1087 int numFirsts = eventIsTab ? 1088 table.getModel().getColumnCount() : table.getModel().getRowCount(); 1089 int numSeconds = eventIsTab ? 1090 table.getModel().getRowCount() : table.getModel().getColumnCount(); 1091 1092 // check if we have to wrap the "firsts" around, going to the other side 1093 if ((firstLead == firstMax && !reverse) || 1094 (reverse && firstLead == firstMin)) 1095 { 1096 firstModel.addSelectionInterval(reverse ? firstMax : firstMin, 1097 reverse ? firstMax : firstMin); 1098 1099 // check if we have to wrap the "seconds" 1100 if ((secondLead == secondMax && !reverse) || 1101 (reverse && secondLead == secondMin)) 1102 secondModel.addSelectionInterval(reverse ? secondMax : secondMin, 1103 reverse ? secondMax : secondMin); 1104 1105 // if we're not wrapping the seconds, we have to find out where we 1106 // are within the secondModel and advance to the next cell (or 1107 // go back to the previous cell if reverse == true) 1108 else 1109 { 1110 int[] secondsSelected; 1111 if (eventIsTab && table.getRowSelectionAllowed() || 1112 !eventIsTab && table.getColumnSelectionAllowed()) 1113 secondsSelected = eventIsTab ? 1114 table.getSelectedRows() : table.getSelectedColumns(); 1115 else 1116 { 1117 // if row selection is not allowed, then the entire column gets 1118 // selected when you click on it, so consider ALL rows selected 1119 secondsSelected = new int[numSeconds]; 1120 for (int i = 0; i < numSeconds; i++) 1121 secondsSelected[i] = i; 1122 } 1123 1124 // and now find the "next" index within the model 1125 int secondIndex = reverse ? secondsSelected.length - 1 : 0; 1126 if (!reverse) 1127 while (secondsSelected[secondIndex] <= secondLead) 1128 secondIndex++; 1129 else 1130 while (secondsSelected[secondIndex] >= secondLead) 1131 secondIndex--; 1132 1133 // and select it - updating the lead selection index 1134 secondModel.addSelectionInterval(secondsSelected[secondIndex], 1135 secondsSelected[secondIndex]); 1136 } 1137 } 1138 // We didn't have to wrap the firsts, so just find the "next" first 1139 // and select it, we don't have to change "seconds" 1140 else 1141 { 1142 int[] firstsSelected; 1143 if (eventIsTab && table.getColumnSelectionAllowed() || 1144 !eventIsTab && table.getRowSelectionAllowed()) 1145 firstsSelected = eventIsTab ? 1146 table.getSelectedColumns() : table.getSelectedRows(); 1147 else 1148 { 1149 // if selection not allowed, consider ALL firsts to be selected 1150 firstsSelected = new int[numFirsts]; 1151 for (int i = 0; i < numFirsts; i++) 1152 firstsSelected[i] = i; 1153 } 1154 int firstIndex = reverse ? firstsSelected.length - 1 : 0; 1155 if (!reverse) 1156 while (firstsSelected[firstIndex] <= firstLead) 1157 firstIndex++; 1158 else 1159 while (firstsSelected[firstIndex] >= firstLead) 1160 firstIndex--; 1161 firstModel.addSelectionInterval(firstsSelected[firstIndex], 1162 firstsSelected[firstIndex]); 1163 secondModel.addSelectionInterval(secondLead, secondLead); 1164 } 1165 } 1166 1167 /** 1168 * A helper method for the key bindings. Used because the actions 1169 * for TAB, SHIFT-TAB, ENTER, and SHIFT-ENTER are very similar. 1170 * 1171 * Selects the next (previous if SHIFT pressed) column (TAB) or row (ENTER) 1172 * in the table, changing the current selection. All cells in the table 1173 * are eligible, not just the ones that are currently selected. 1174 * @param firstModel the ListSelectionModel for columns (TAB) or rows 1175 * (ENTER) 1176 * @param firstMax the last index in firstModel 1177 * @param secondModel the ListSelectionModel for rows (TAB) or columns 1178 * (ENTER) 1179 * @param secondMax the last index in secondModel 1180 * @param reverse true if SHIFT was pressed for the event 1181 */ 1182 1183 void advanceSingleSelection(ListSelectionModel firstModel, int firstMax, 1184 ListSelectionModel secondModel, int secondMax, 1185 boolean reverse) 1186 { 1187 // for TABs, "first" corresponds to columns and "seconds" to rows. 1188 // the opposite is true for ENTERs 1189 int firstLead = firstModel.getLeadSelectionIndex(); 1190 int secondLead = secondModel.getLeadSelectionIndex(); 1191 1192 // if we are going backwards subtract 2 because we later add 1 1193 // for a net change of -1 1194 if (reverse && (firstLead == 0)) 1195 { 1196 // check if we have to wrap around 1197 if (secondLead == 0) 1198 secondLead += secondMax + 1; 1199 secondLead -= 2; 1200 } 1201 1202 // do we have to wrap the "seconds"? 1203 if (reverse && (firstLead == 0) || !reverse && (firstLead == firstMax)) 1204 secondModel.setSelectionInterval((secondLead + 1) % (secondMax + 1), 1205 (secondLead + 1) % (secondMax + 1)); 1206 // if not, just reselect the current lead 1207 else 1208 secondModel.setSelectionInterval(secondLead, secondLead); 1209 1210 // if we are going backwards, subtract 2 because we add 1 later 1211 // for net change of -1 1212 if (reverse) 1213 { 1214 // check for wraparound 1215 if (firstLead == 0) 1216 firstLead += firstMax + 1; 1217 firstLead -= 2; 1218 } 1219 // select the next "first" 1220 firstModel.setSelectionInterval((firstLead + 1) % (firstMax + 1), 1221 (firstLead + 1) % (firstMax + 1)); 1222 } 1223 } 1224 1225 protected void installListeners() 1226 { 1227 if (focusListener == null) 1228 focusListener = createFocusListener(); 1229 table.addFocusListener(focusListener); 1230 if (keyListener == null) 1231 keyListener = createKeyListener(); 1232 table.addKeyListener(keyListener); 1233 if (mouseInputListener == null) 1234 mouseInputListener = createMouseInputListener(); 1235 table.addMouseListener(mouseInputListener); 1236 table.addMouseMotionListener(mouseInputListener); 1237 if (propertyChangeListener == null) 1238 propertyChangeListener = new PropertyChangeHandler(); 1239 table.addPropertyChangeListener(propertyChangeListener); 1240 } 1241 1242 /** 1243 * Uninstalls UI defaults that have been installed by 1244 * {@link #installDefaults()}. 1245 */ 1246 protected void uninstallDefaults() 1247 { 1248 // Nothing to do here for now. 1249 } 1250 1251 /** 1252 * Uninstalls the keyboard actions that have been installed by 1253 * {@link #installKeyboardActions()}. 1254 */ 1255 protected void uninstallKeyboardActions() 1256 { 1257 SwingUtilities.replaceUIInputMap(table, JComponent. 1258 WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null); 1259 SwingUtilities.replaceUIActionMap(table, null); 1260 } 1261 1262 protected void uninstallListeners() 1263 { 1264 table.removeFocusListener(focusListener); 1265 table.removeKeyListener(keyListener); 1266 table.removeMouseListener(mouseInputListener); 1267 table.removeMouseMotionListener(mouseInputListener); 1268 table.removePropertyChangeListener(propertyChangeListener); 1269 propertyChangeListener = null; 1270 } 1271 1272 public void installUI(JComponent comp) 1273 { 1274 table = (JTable) comp; 1275 rendererPane = new CellRendererPane(); 1276 table.add(rendererPane); 1277 1278 installDefaults(); 1279 installKeyboardActions(); 1280 installListeners(); 1281 } 1282 1283 public void uninstallUI(JComponent c) 1284 { 1285 uninstallListeners(); 1286 uninstallKeyboardActions(); 1287 uninstallDefaults(); 1288 1289 table.remove(rendererPane); 1290 rendererPane = null; 1291 table = null; 1292 } 1293 1294 /** 1295 * Paints a single cell in the table. 1296 * 1297 * @param g The graphics context to paint in 1298 * @param row The row number to paint 1299 * @param col The column number to paint 1300 * @param bounds The bounds of the cell to paint, assuming a coordinate 1301 * system beginning at <code>(0,0)</code> in the upper left corner of the 1302 * table 1303 * @param rend A cell renderer to paint with 1304 */ 1305 void paintCell(Graphics g, int row, int col, Rectangle bounds, 1306 TableCellRenderer rend) 1307 { 1308 Component comp = table.prepareRenderer(rend, row, col); 1309 rendererPane.paintComponent(g, comp, table, bounds); 1310 } 1311 1312 /** 1313 * Paint the associated table. 1314 */ 1315 public void paint(Graphics gfx, JComponent ignored) 1316 { 1317 int ncols = table.getColumnCount(); 1318 int nrows = table.getRowCount(); 1319 if (nrows == 0 || ncols == 0) 1320 return; 1321 1322 Rectangle clip = gfx.getClipBounds(); 1323 1324 // Determine the range of cells that are within the clip bounds. 1325 Point p1 = new Point(clip.x, clip.y); 1326 int c0 = table.columnAtPoint(p1); 1327 if (c0 == -1) 1328 c0 = 0; 1329 int r0 = table.rowAtPoint(p1); 1330 if (r0 == -1) 1331 r0 = 0; 1332 Point p2 = new Point(clip.x + clip.width, clip.y + clip.height); 1333 int cn = table.columnAtPoint(p2); 1334 if (cn == -1) 1335 cn = table.getColumnCount() - 1; 1336 int rn = table.rowAtPoint(p2); 1337 if (rn == -1) 1338 rn = table.getRowCount() - 1; 1339 1340 int columnMargin = table.getColumnModel().getColumnMargin(); 1341 int rowMargin = table.getRowMargin(); 1342 1343 TableColumnModel cmodel = table.getColumnModel(); 1344 int[] widths = new int[cn + 1]; 1345 for (int i = c0; i <= cn; i++) 1346 { 1347 widths[i] = cmodel.getColumn(i).getWidth() - columnMargin; 1348 } 1349 1350 Rectangle bounds = table.getCellRect(r0, c0, false); 1351 // The left boundary of the area being repainted. 1352 int left = bounds.x; 1353 1354 // The top boundary of the area being repainted. 1355 int top = bounds.y; 1356 1357 // The bottom boundary of the area being repainted. 1358 int bottom; 1359 1360 // paint the cell contents 1361 Color grid = table.getGridColor(); 1362 for (int r = r0; r <= rn; ++r) 1363 { 1364 for (int c = c0; c <= cn; ++c) 1365 { 1366 bounds.width = widths[c]; 1367 paintCell(gfx, r, c, bounds, table.getCellRenderer(r, c)); 1368 bounds.x += widths[c] + columnMargin; 1369 } 1370 bounds.x = left; 1371 bounds.y += table.getRowHeight(r); 1372 // Update row height for tables with custom heights. 1373 bounds.height = table.getRowHeight(r + 1) - rowMargin; 1374 } 1375 1376 bottom = bounds.y - rowMargin; 1377 1378 // paint vertical grid lines 1379 if (grid != null && table.getShowVerticalLines()) 1380 { 1381 Color save = gfx.getColor(); 1382 gfx.setColor(grid); 1383 int x = left - columnMargin; 1384 for (int c = c0; c <= cn; ++c) 1385 { 1386 // The vertical grid is draw right from the cells, so we 1387 // add before drawing. 1388 x += widths[c] + columnMargin; 1389 gfx.drawLine(x, top, x, bottom); 1390 } 1391 gfx.setColor(save); 1392 } 1393 1394 // paint horizontal grid lines 1395 if (grid != null && table.getShowHorizontalLines()) 1396 { 1397 Color save = gfx.getColor(); 1398 gfx.setColor(grid); 1399 int y = top - rowMargin; 1400 for (int r = r0; r <= rn; ++r) 1401 { 1402 // The horizontal grid is draw below the cells, so we 1403 // add before drawing. 1404 y += table.getRowHeight(r); 1405 gfx.drawLine(left, y, p2.x, y); 1406 } 1407 gfx.setColor(save); 1408 } 1409 } 1410}