001 /* BasicTableUI.java --
002 Copyright (C) 2004 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing.plaf.basic;
040
041 import java.awt.Color;
042 import java.awt.Component;
043 import java.awt.ComponentOrientation;
044 import java.awt.Dimension;
045 import java.awt.Graphics;
046 import java.awt.Point;
047 import java.awt.Rectangle;
048 import java.awt.event.ActionEvent;
049 import java.awt.event.FocusEvent;
050 import java.awt.event.FocusListener;
051 import java.awt.event.KeyEvent;
052 import java.awt.event.KeyListener;
053 import java.awt.event.MouseEvent;
054 import java.beans.PropertyChangeEvent;
055 import java.beans.PropertyChangeListener;
056
057 import javax.swing.AbstractAction;
058 import javax.swing.Action;
059 import javax.swing.ActionMap;
060 import javax.swing.CellRendererPane;
061 import javax.swing.DefaultCellEditor;
062 import javax.swing.DefaultListSelectionModel;
063 import javax.swing.InputMap;
064 import javax.swing.JComponent;
065 import javax.swing.JTable;
066 import javax.swing.ListSelectionModel;
067 import javax.swing.LookAndFeel;
068 import javax.swing.SwingUtilities;
069 import javax.swing.TransferHandler;
070 import javax.swing.UIManager;
071 import javax.swing.border.Border;
072 import javax.swing.event.ChangeEvent;
073 import javax.swing.event.MouseInputListener;
074 import javax.swing.plaf.ActionMapUIResource;
075 import javax.swing.plaf.ComponentUI;
076 import javax.swing.plaf.TableUI;
077 import javax.swing.table.TableCellEditor;
078 import javax.swing.table.TableCellRenderer;
079 import javax.swing.table.TableColumn;
080 import javax.swing.table.TableColumnModel;
081 import javax.swing.table.TableModel;
082
083 public 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 }