1 /*
   2  * Copyright (c) 2002, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing.plaf.synth;
  27 
  28 import java.awt.Color;
  29 import java.awt.Component;
  30 import java.awt.Dimension;
  31 import java.awt.Graphics;
  32 import java.awt.Point;
  33 import java.awt.Rectangle;
  34 import java.beans.PropertyChangeEvent;
  35 import java.beans.PropertyChangeListener;
  36 import java.text.DateFormat;
  37 import java.text.Format;
  38 import java.text.NumberFormat;
  39 import java.util.Date;
  40 import javax.swing.Icon;
  41 import javax.swing.ImageIcon;
  42 import javax.swing.JCheckBox;
  43 import javax.swing.JComponent;
  44 import javax.swing.JLabel;
  45 import javax.swing.JTable;
  46 import javax.swing.LookAndFeel;
  47 import javax.swing.border.Border;
  48 import javax.swing.plaf.*;
  49 import javax.swing.plaf.basic.BasicTableUI;
  50 import javax.swing.table.DefaultTableCellRenderer;
  51 import javax.swing.table.JTableHeader;
  52 import javax.swing.table.TableCellRenderer;
  53 import javax.swing.table.TableColumn;
  54 import javax.swing.table.TableColumnModel;
  55 
  56 /**
  57  * Provides the Synth L&F UI delegate for
  58  * {@link javax.swing.JTable}.
  59  *
  60  * @author Philip Milne
  61  * @since 1.7
  62  */
  63 public class SynthTableUI extends BasicTableUI
  64                           implements SynthUI, PropertyChangeListener {
  65 //
  66 // Instance Variables
  67 //
  68 
  69     private SynthStyle style;
  70 
  71     private boolean useTableColors;
  72     private boolean useUIBorder;
  73     private Color alternateColor; //the background color to use for cells for alternate cells
  74 
  75     // TableCellRenderer installed on the JTable at the time we're installed,
  76     // cached so that we can reinstall them at uninstallUI time.
  77     private TableCellRenderer dateRenderer;
  78     private TableCellRenderer numberRenderer;
  79     private TableCellRenderer doubleRender;
  80     private TableCellRenderer floatRenderer;
  81     private TableCellRenderer iconRenderer;
  82     private TableCellRenderer imageIconRenderer;
  83     private TableCellRenderer booleanRenderer;
  84     private TableCellRenderer objectRenderer;
  85 
  86 //
  87 //  The installation/uninstall procedures and support
  88 //
  89 
  90     /**
  91      * Creates a new UI object for the given component.
  92      *
  93      * @param c component to create UI object for
  94      * @return the UI object
  95      */
  96     public static ComponentUI createUI(JComponent c) {
  97         return new SynthTableUI();
  98     }
  99 
 100     /**
 101      * Initializes JTable properties, such as font, foreground, and background.
 102      * The font, foreground, and background properties are only set if their
 103      * current value is either null or a UIResource, other properties are set
 104      * if the current value is null.
 105      *
 106      * @see #installUI
 107      */
 108     @Override
 109     protected void installDefaults() {
 110         dateRenderer = installRendererIfPossible(Date.class, null);
 111         numberRenderer = installRendererIfPossible(Number.class, null);
 112         doubleRender = installRendererIfPossible(Double.class, null);
 113         floatRenderer = installRendererIfPossible(Float.class, null);
 114         iconRenderer = installRendererIfPossible(Icon.class, null);
 115         imageIconRenderer = installRendererIfPossible(ImageIcon.class, null);
 116         booleanRenderer = installRendererIfPossible(Boolean.class,
 117                                  new SynthBooleanTableCellRenderer());
 118         objectRenderer = installRendererIfPossible(Object.class,
 119                                         new SynthTableCellRenderer());
 120         updateStyle(table);
 121     }
 122 
 123     private TableCellRenderer installRendererIfPossible(Class objectClass,
 124                                      TableCellRenderer renderer) {
 125         TableCellRenderer currentRenderer = table.getDefaultRenderer(
 126                                  objectClass);
 127         if (currentRenderer instanceof UIResource) {
 128             table.setDefaultRenderer(objectClass, renderer);
 129         }
 130         return currentRenderer;
 131     }
 132 
 133     private void updateStyle(JTable c) {
 134         SynthContext context = getContext(c, ENABLED);
 135         SynthStyle oldStyle = style;
 136         style = SynthLookAndFeel.updateStyle(context, this);
 137         if (style != oldStyle) {
 138             context.setComponentState(ENABLED | SELECTED);
 139 
 140             Color sbg = table.getSelectionBackground();
 141             if (sbg == null || sbg instanceof UIResource) {
 142                 table.setSelectionBackground(style.getColor(
 143                                         context, ColorType.TEXT_BACKGROUND));
 144             }
 145 
 146             Color sfg = table.getSelectionForeground();
 147             if (sfg == null || sfg instanceof UIResource) {
 148                 table.setSelectionForeground(style.getColor(
 149                                   context, ColorType.TEXT_FOREGROUND));
 150             }
 151 
 152             context.setComponentState(ENABLED);
 153 
 154             Color gridColor = table.getGridColor();
 155             if (gridColor == null || gridColor instanceof UIResource) {
 156                 gridColor = (Color)style.get(context, "Table.gridColor");
 157                 if (gridColor == null) {
 158                     gridColor = style.getColor(context, ColorType.FOREGROUND);
 159                 }
 160                 table.setGridColor(gridColor == null ? new ColorUIResource(Color.GRAY) : gridColor);
 161             }
 162 
 163             useTableColors = style.getBoolean(context,
 164                                   "Table.rendererUseTableColors", true);
 165             useUIBorder = style.getBoolean(context,
 166                                   "Table.rendererUseUIBorder", true);
 167 
 168             Object rowHeight = style.get(context, "Table.rowHeight");
 169             if (rowHeight != null) {
 170                 LookAndFeel.installProperty(table, "rowHeight", rowHeight);
 171             }
 172             boolean showGrid = style.getBoolean(context, "Table.showGrid", true);
 173             if (!showGrid) {
 174                 table.setShowGrid(false);
 175             }
 176             Dimension d = table.getIntercellSpacing();
 177 //            if (d == null || d instanceof UIResource) {
 178             if (d != null) {
 179                 d = (Dimension)style.get(context, "Table.intercellSpacing");
 180             }
 181             alternateColor = (Color)style.get(context, "Table.alternateRowColor");
 182             if (d != null) {
 183                 table.setIntercellSpacing(d);
 184             }
 185 
 186 
 187             if (oldStyle != null) {
 188                 uninstallKeyboardActions();
 189                 installKeyboardActions();
 190             }
 191         }
 192         context.dispose();
 193     }
 194 
 195     /**
 196      * Attaches listeners to the JTable.
 197      */
 198     @Override
 199     protected void installListeners() {
 200         super.installListeners();
 201         table.addPropertyChangeListener(this);
 202     }
 203 
 204     /**
 205      * {@inheritDoc}
 206      */
 207     @Override
 208     protected void uninstallDefaults() {
 209         table.setDefaultRenderer(Date.class, dateRenderer);
 210         table.setDefaultRenderer(Number.class, numberRenderer);
 211         table.setDefaultRenderer(Double.class, doubleRender);
 212         table.setDefaultRenderer(Float.class, floatRenderer);
 213         table.setDefaultRenderer(Icon.class, iconRenderer);
 214         table.setDefaultRenderer(ImageIcon.class, imageIconRenderer);
 215         table.setDefaultRenderer(Boolean.class, booleanRenderer);
 216         table.setDefaultRenderer(Object.class, objectRenderer);
 217 
 218         if (table.getTransferHandler() instanceof UIResource) {
 219             table.setTransferHandler(null);
 220         }
 221         SynthContext context = getContext(table, ENABLED);
 222         style.uninstallDefaults(context);
 223         context.dispose();
 224         style = null;
 225     }
 226 
 227     /**
 228      * {@inheritDoc}
 229      */
 230     @Override
 231     protected void uninstallListeners() {
 232         table.removePropertyChangeListener(this);
 233         super.uninstallListeners();
 234     }
 235 
 236     //
 237     // SynthUI
 238     //
 239 
 240     /**
 241      * {@inheritDoc}
 242      */
 243     @Override
 244     public SynthContext getContext(JComponent c) {
 245         return getContext(c, SynthLookAndFeel.getComponentState(c));
 246     }
 247 
 248     private SynthContext getContext(JComponent c, int state) {
 249         return SynthContext.getContext(c, style, state);
 250     }
 251 
 252 //
 253 //  Paint methods and support
 254 //
 255 
 256     /**
 257      * Notifies this UI delegate to repaint the specified component.
 258      * This method paints the component background, then calls
 259      * the {@link #paint(SynthContext,Graphics)} method.
 260      *
 261      * <p>In general, this method does not need to be overridden by subclasses.
 262      * All Look and Feel rendering code should reside in the {@code paint} method.
 263      *
 264      * @param g the {@code Graphics} object used for painting
 265      * @param c the component being painted
 266      * @see #paint(SynthContext,Graphics)
 267      */
 268     @Override
 269     public void update(Graphics g, JComponent c) {
 270         SynthContext context = getContext(c);
 271 
 272         SynthLookAndFeel.update(context, g);
 273         context.getPainter().paintTableBackground(context,
 274                           g, 0, 0, c.getWidth(), c.getHeight());
 275         paint(context, g);
 276         context.dispose();
 277     }
 278 
 279     /**
 280      * {@inheritDoc}
 281      */
 282     @Override
 283     public void paintBorder(SynthContext context, Graphics g, int x,
 284                             int y, int w, int h) {
 285         context.getPainter().paintTableBorder(context, g, x, y, w, h);
 286     }
 287 
 288     /**
 289      * Paints the specified component according to the Look and Feel.
 290      * <p>This method is not used by Synth Look and Feel.
 291      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
 292      *
 293      * @param g the {@code Graphics} object used for painting
 294      * @param c the component being painted
 295      * @see #paint(SynthContext,Graphics)
 296      */
 297     @Override
 298     public void paint(Graphics g, JComponent c) {
 299         SynthContext context = getContext(c);
 300 
 301         paint(context, g);
 302         context.dispose();
 303     }
 304 
 305     /**
 306      * Paints the specified component.
 307      *
 308      * @param context context for the component being painted
 309      * @param g the {@code Graphics} object used for painting
 310      * @see #update(Graphics,JComponent)
 311      */
 312     protected void paint(SynthContext context, Graphics g) {
 313         Rectangle clip = g.getClipBounds();
 314 
 315         Rectangle bounds = table.getBounds();
 316         // account for the fact that the graphics has already been translated
 317         // into the table's bounds
 318         bounds.x = bounds.y = 0;
 319 
 320         if (table.getRowCount() <= 0 || table.getColumnCount() <= 0 ||
 321                 // this check prevents us from painting the entire table
 322                 // when the clip doesn't intersect our bounds at all
 323                 !bounds.intersects(clip)) {
 324 
 325             paintDropLines(context, g);
 326             return;
 327         }
 328 
 329         boolean ltr = table.getComponentOrientation().isLeftToRight();
 330 
 331         Point upperLeft = clip.getLocation();
 332 
 333         Point lowerRight = new Point(clip.x + clip.width - 1,
 334                                      clip.y + clip.height - 1);
 335 
 336         int rMin = table.rowAtPoint(upperLeft);
 337         int rMax = table.rowAtPoint(lowerRight);
 338         // This should never happen (as long as our bounds intersect the clip,
 339         // which is why we bail above if that is the case).
 340         if (rMin == -1) {
 341             rMin = 0;
 342         }
 343         // If the table does not have enough rows to fill the view we'll get -1.
 344         // (We could also get -1 if our bounds don't intersect the clip,
 345         // which is why we bail above if that is the case).
 346         // Replace this with the index of the last row.
 347         if (rMax == -1) {
 348             rMax = table.getRowCount()-1;
 349         }
 350 
 351         int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight);
 352         int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);
 353         // This should never happen.
 354         if (cMin == -1) {
 355             cMin = 0;
 356         }
 357         // If the table does not have enough columns to fill the view we'll get -1.
 358         // Replace this with the index of the last column.
 359         if (cMax == -1) {
 360             cMax = table.getColumnCount()-1;
 361         }
 362 
 363         // Paint the cells.
 364         paintCells(context, g, rMin, rMax, cMin, cMax);
 365 
 366         // Paint the grid.
 367         // it is important to paint the grid after the cells, otherwise the grid will be overpainted
 368         // because in Synth cell renderers are likely to be opaque
 369         paintGrid(context, g, rMin, rMax, cMin, cMax);
 370 
 371         paintDropLines(context, g);
 372     }
 373 
 374     private void paintDropLines(SynthContext context, Graphics g) {
 375         JTable.DropLocation loc = table.getDropLocation();
 376         if (loc == null) {
 377             return;
 378         }
 379 
 380         Color color = (Color)style.get(context, "Table.dropLineColor");
 381         Color shortColor = (Color)style.get(context, "Table.dropLineShortColor");
 382         if (color == null && shortColor == null) {
 383             return;
 384         }
 385 
 386         Rectangle rect;
 387 
 388         rect = getHDropLineRect(loc);
 389         if (rect != null) {
 390             int x = rect.x;
 391             int w = rect.width;
 392             if (color != null) {
 393                 extendRect(rect, true);
 394                 g.setColor(color);
 395                 g.fillRect(rect.x, rect.y, rect.width, rect.height);
 396             }
 397             if (!loc.isInsertColumn() && shortColor != null) {
 398                 g.setColor(shortColor);
 399                 g.fillRect(x, rect.y, w, rect.height);
 400             }
 401         }
 402 
 403         rect = getVDropLineRect(loc);
 404         if (rect != null) {
 405             int y = rect.y;
 406             int h = rect.height;
 407             if (color != null) {
 408                 extendRect(rect, false);
 409                 g.setColor(color);
 410                 g.fillRect(rect.x, rect.y, rect.width, rect.height);
 411             }
 412             if (!loc.isInsertRow() && shortColor != null) {
 413                 g.setColor(shortColor);
 414                 g.fillRect(rect.x, y, rect.width, h);
 415             }
 416         }
 417     }
 418 
 419     private Rectangle getHDropLineRect(JTable.DropLocation loc) {
 420         if (!loc.isInsertRow()) {
 421             return null;
 422         }
 423 
 424         int row = loc.getRow();
 425         int col = loc.getColumn();
 426         if (col >= table.getColumnCount()) {
 427             col--;
 428         }
 429 
 430         Rectangle rect = table.getCellRect(row, col, true);
 431 
 432         if (row >= table.getRowCount()) {
 433             row--;
 434             Rectangle prevRect = table.getCellRect(row, col, true);
 435             rect.y = prevRect.y + prevRect.height;
 436         }
 437 
 438         if (rect.y == 0) {
 439             rect.y = -1;
 440         } else {
 441             rect.y -= 2;
 442         }
 443 
 444         rect.height = 3;
 445 
 446         return rect;
 447     }
 448 
 449     private Rectangle getVDropLineRect(JTable.DropLocation loc) {
 450         if (!loc.isInsertColumn()) {
 451             return null;
 452         }
 453 
 454         boolean ltr = table.getComponentOrientation().isLeftToRight();
 455         int col = loc.getColumn();
 456         Rectangle rect = table.getCellRect(loc.getRow(), col, true);
 457 
 458         if (col >= table.getColumnCount()) {
 459             col--;
 460             rect = table.getCellRect(loc.getRow(), col, true);
 461             if (ltr) {
 462                 rect.x = rect.x + rect.width;
 463             }
 464         } else if (!ltr) {
 465             rect.x = rect.x + rect.width;
 466         }
 467 
 468         if (rect.x == 0) {
 469             rect.x = -1;
 470         } else {
 471             rect.x -= 2;
 472         }
 473 
 474         rect.width = 3;
 475 
 476         return rect;
 477     }
 478 
 479     private Rectangle extendRect(Rectangle rect, boolean horizontal) {
 480         if (rect == null) {
 481             return rect;
 482         }
 483 
 484         if (horizontal) {
 485             rect.x = 0;
 486             rect.width = table.getWidth();
 487         } else {
 488             rect.y = 0;
 489 
 490             if (table.getRowCount() != 0) {
 491                 Rectangle lastRect = table.getCellRect(table.getRowCount() - 1, 0, true);
 492                 rect.height = lastRect.y + lastRect.height;
 493             } else {
 494                 rect.height = table.getHeight();
 495             }
 496         }
 497 
 498         return rect;
 499     }
 500 
 501     /*
 502      * Paints the grid lines within <I>aRect</I>, using the grid
 503      * color set with <I>setGridColor</I>. Paints vertical lines
 504      * if <code>getShowVerticalLines()</code> returns true and paints
 505      * horizontal lines if <code>getShowHorizontalLines()</code>
 506      * returns true.
 507      */
 508     private void paintGrid(SynthContext context, Graphics g, int rMin,
 509                            int rMax, int cMin, int cMax) {
 510         g.setColor(table.getGridColor());
 511 
 512         Rectangle minCell = table.getCellRect(rMin, cMin, true);
 513         Rectangle maxCell = table.getCellRect(rMax, cMax, true);
 514         Rectangle damagedArea = minCell.union( maxCell );
 515         SynthGraphicsUtils synthG = context.getStyle().getGraphicsUtils(
 516                      context);
 517 
 518         if (table.getShowHorizontalLines()) {
 519             int tableWidth = damagedArea.x + damagedArea.width;
 520             int y = damagedArea.y;
 521             for (int row = rMin; row <= rMax; row++) {
 522                 y += table.getRowHeight(row);
 523                 synthG.drawLine(context, "Table.grid",
 524                                 g, damagedArea.x, y - 1, tableWidth - 1,y - 1);
 525             }
 526         }
 527         if (table.getShowVerticalLines()) {
 528             TableColumnModel cm = table.getColumnModel();
 529             int tableHeight = damagedArea.y + damagedArea.height;
 530             int x;
 531             if (table.getComponentOrientation().isLeftToRight()) {
 532                 x = damagedArea.x;
 533                 for (int column = cMin; column <= cMax; column++) {
 534                     int w = cm.getColumn(column).getWidth();
 535                     x += w;
 536                     synthG.drawLine(context, "Table.grid", g, x - 1, 0,
 537                                     x - 1, tableHeight - 1);
 538                 }
 539             } else {
 540                 x = damagedArea.x;
 541                 for (int column = cMax; column >= cMin; column--) {
 542                     int w = cm.getColumn(column).getWidth();
 543                     x += w;
 544                     synthG.drawLine(context, "Table.grid", g, x - 1, 0, x - 1,
 545                                     tableHeight - 1);
 546                 }
 547             }
 548         }
 549     }
 550 
 551     private int viewIndexForColumn(TableColumn aColumn) {
 552         TableColumnModel cm = table.getColumnModel();
 553         for (int column = 0; column < cm.getColumnCount(); column++) {
 554             if (cm.getColumn(column) == aColumn) {
 555                 return column;
 556             }
 557         }
 558         return -1;
 559     }
 560 
 561     private void paintCells(SynthContext context, Graphics g, int rMin,
 562                             int rMax, int cMin, int cMax) {
 563         JTableHeader header = table.getTableHeader();
 564         TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
 565 
 566         TableColumnModel cm = table.getColumnModel();
 567         int columnMargin = cm.getColumnMargin();
 568 
 569         Rectangle cellRect;
 570         TableColumn aColumn;
 571         int columnWidth;
 572         if (table.getComponentOrientation().isLeftToRight()) {
 573             for(int row = rMin; row <= rMax; row++) {
 574                 cellRect = table.getCellRect(row, cMin, false);
 575                 for(int column = cMin; column <= cMax; column++) {
 576                     aColumn = cm.getColumn(column);
 577                     columnWidth = aColumn.getWidth();
 578                     cellRect.width = columnWidth - columnMargin;
 579                     if (aColumn != draggedColumn) {
 580                         paintCell(context, g, cellRect, row, column);
 581                     }
 582                     cellRect.x += columnWidth;
 583                 }
 584             }
 585         } else {
 586             for(int row = rMin; row <= rMax; row++) {
 587                 cellRect = table.getCellRect(row, cMin, false);
 588                 aColumn = cm.getColumn(cMin);
 589                 if (aColumn != draggedColumn) {
 590                     columnWidth = aColumn.getWidth();
 591                     cellRect.width = columnWidth - columnMargin;
 592                     paintCell(context, g, cellRect, row, cMin);
 593                 }
 594                 for(int column = cMin+1; column <= cMax; column++) {
 595                     aColumn = cm.getColumn(column);
 596                     columnWidth = aColumn.getWidth();
 597                     cellRect.width = columnWidth - columnMargin;
 598                     cellRect.x -= columnWidth;
 599                     if (aColumn != draggedColumn) {
 600                         paintCell(context, g, cellRect, row, column);
 601                     }
 602                 }
 603             }
 604         }
 605 
 606         // Paint the dragged column if we are dragging.
 607         if (draggedColumn != null) {
 608             paintDraggedArea(context, g, rMin, rMax, draggedColumn, header.getDraggedDistance());
 609         }
 610 
 611         // Remove any renderers that may be left in the rendererPane.
 612         rendererPane.removeAll();
 613     }
 614 
 615     private void paintDraggedArea(SynthContext context, Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) {
 616         int draggedColumnIndex = viewIndexForColumn(draggedColumn);
 617 
 618         Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
 619         Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
 620 
 621         Rectangle vacatedColumnRect = minCell.union(maxCell);
 622 
 623         // Paint a gray well in place of the moving column.
 624         g.setColor(table.getParent().getBackground());
 625         g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
 626                    vacatedColumnRect.width, vacatedColumnRect.height);
 627 
 628         // Move to the where the cell has been dragged.
 629         vacatedColumnRect.x += distance;
 630 
 631         // Fill the background.
 632         g.setColor(context.getStyle().getColor(context, ColorType.BACKGROUND));
 633         g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
 634                    vacatedColumnRect.width, vacatedColumnRect.height);
 635 
 636         SynthGraphicsUtils synthG = context.getStyle().getGraphicsUtils(
 637                                             context);
 638 
 639 
 640         // Paint the vertical grid lines if necessary.
 641         if (table.getShowVerticalLines()) {
 642             g.setColor(table.getGridColor());
 643             int x1 = vacatedColumnRect.x;
 644             int y1 = vacatedColumnRect.y;
 645             int x2 = x1 + vacatedColumnRect.width - 1;
 646             int y2 = y1 + vacatedColumnRect.height - 1;
 647             // Left
 648             synthG.drawLine(context, "Table.grid", g, x1-1, y1, x1-1, y2);
 649             // Right
 650             synthG.drawLine(context, "Table.grid", g, x2, y1, x2, y2);
 651         }
 652 
 653         for(int row = rMin; row <= rMax; row++) {
 654             // Render the cell value
 655             Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
 656             r.x += distance;
 657             paintCell(context, g, r, row, draggedColumnIndex);
 658 
 659             // Paint the (lower) horizontal grid line if necessary.
 660             if (table.getShowHorizontalLines()) {
 661                 g.setColor(table.getGridColor());
 662                 Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
 663                 rcr.x += distance;
 664                 int x1 = rcr.x;
 665                 int y1 = rcr.y;
 666                 int x2 = x1 + rcr.width - 1;
 667                 int y2 = y1 + rcr.height - 1;
 668                 synthG.drawLine(context, "Table.grid", g, x1, y2, x2, y2);
 669             }
 670         }
 671     }
 672 
 673     private void paintCell(SynthContext context, Graphics g,
 674             Rectangle cellRect, int row, int column) {
 675         if (table.isEditing() && table.getEditingRow()==row &&
 676                                  table.getEditingColumn()==column) {
 677             Component component = table.getEditorComponent();
 678             component.setBounds(cellRect);
 679             component.validate();
 680         }
 681         else {
 682             TableCellRenderer renderer = table.getCellRenderer(row, column);
 683             Component component = table.prepareRenderer(renderer, row, column);
 684             Color b = component.getBackground();
 685             if ((b == null || b instanceof UIResource
 686                     || component instanceof SynthBooleanTableCellRenderer)
 687                     && !table.isCellSelected(row, column)) {
 688                 if (alternateColor != null && row % 2 != 0) {
 689                     component.setBackground(alternateColor);
 690                 }
 691             }
 692             rendererPane.paintComponent(g, component, table, cellRect.x,
 693                     cellRect.y, cellRect.width, cellRect.height, true);
 694         }
 695     }
 696 
 697     /**
 698      * {@inheritDoc}
 699      */
 700     @Override
 701     public void propertyChange(PropertyChangeEvent event) {
 702         if (SynthLookAndFeel.shouldUpdateStyle(event)) {
 703             updateStyle((JTable)event.getSource());
 704         }
 705     }
 706 
 707     @SuppressWarnings("serial") // Superclass is not serializable across versions
 708     private class SynthBooleanTableCellRenderer extends JCheckBox implements
 709                       TableCellRenderer {
 710         private boolean isRowSelected;
 711 
 712         public SynthBooleanTableCellRenderer() {
 713             setHorizontalAlignment(JLabel.CENTER);
 714             setName("Table.cellRenderer");
 715         }
 716 
 717         public Component getTableCellRendererComponent(
 718                             JTable table, Object value, boolean isSelected,
 719                             boolean hasFocus, int row, int column) {
 720             isRowSelected = isSelected;
 721 
 722             if (isSelected) {
 723                 setForeground(unwrap(table.getSelectionForeground()));
 724                 setBackground(unwrap(table.getSelectionBackground()));
 725             } else {
 726                 setForeground(unwrap(table.getForeground()));
 727                 setBackground(unwrap(table.getBackground()));
 728             }
 729 
 730             setSelected((value != null && ((Boolean)value).booleanValue()));
 731             return this;
 732         }
 733 
 734         private Color unwrap(Color c) {
 735             if (c instanceof UIResource) {
 736                 return new Color(c.getRGB());
 737             }
 738             return c;
 739         }
 740 
 741         public boolean isOpaque() {
 742             return isRowSelected ? true : super.isOpaque();
 743         }
 744     }
 745 
 746     @SuppressWarnings("serial") // Superclass is not serializable across versions
 747     private class SynthTableCellRenderer extends DefaultTableCellRenderer {
 748         private Object numberFormat;
 749         private Object dateFormat;
 750         private boolean opaque;
 751 
 752         public void setOpaque(boolean isOpaque) {
 753             opaque = isOpaque;
 754         }
 755 
 756         public boolean isOpaque() {
 757             return opaque;
 758         }
 759 
 760         public String getName() {
 761             String name = super.getName();
 762             if (name == null) {
 763                 return "Table.cellRenderer";
 764             }
 765             return name;
 766         }
 767 
 768         public void setBorder(Border b) {
 769             if (useUIBorder || b instanceof SynthBorder) {
 770                 super.setBorder(b);
 771             }
 772         }
 773 
 774         public Component getTableCellRendererComponent(
 775                   JTable table, Object value, boolean isSelected,
 776                   boolean hasFocus, int row, int column) {
 777             if (!useTableColors && (isSelected || hasFocus)) {
 778                 SynthLookAndFeel.setSelectedUI((SynthLabelUI)SynthLookAndFeel.
 779                              getUIOfType(getUI(), SynthLabelUI.class),
 780                                    isSelected, hasFocus, table.isEnabled(), false);
 781             }
 782             else {
 783                 SynthLookAndFeel.resetSelectedUI();
 784             }
 785             super.getTableCellRendererComponent(table, value, isSelected,
 786                                                 hasFocus, row, column);
 787 
 788             setIcon(null);
 789             if (table != null) {
 790                 configureValue(value, table.getColumnClass(column));
 791             }
 792             return this;
 793         }
 794 
 795         private void configureValue(Object value, Class columnClass) {
 796             if (columnClass == Object.class || columnClass == null) {
 797                 setHorizontalAlignment(JLabel.LEADING);
 798             } else if (columnClass == Float.class || columnClass == Double.class) {
 799                 if (numberFormat == null) {
 800                     numberFormat = NumberFormat.getInstance();
 801                 }
 802                 setHorizontalAlignment(JLabel.TRAILING);
 803                 setText((value == null) ? "" : ((NumberFormat)numberFormat).format(value));
 804             }
 805             else if (columnClass == Number.class) {
 806                 setHorizontalAlignment(JLabel.TRAILING);
 807                 // Super will have set value.
 808             }
 809             else if (columnClass == Icon.class || columnClass == ImageIcon.class) {
 810                 setHorizontalAlignment(JLabel.CENTER);
 811                 setIcon((value instanceof Icon) ? (Icon)value : null);
 812                 setText("");
 813             }
 814             else if (columnClass == Date.class) {
 815                 if (dateFormat == null) {
 816                     dateFormat = DateFormat.getDateInstance();
 817                 }
 818                 setHorizontalAlignment(JLabel.LEADING);
 819                 setText((value == null) ? "" : ((Format)dateFormat).format(value));
 820             }
 821             else {
 822                 configureValue(value, columnClass.getSuperclass());
 823             }
 824         }
 825 
 826         public void paint(Graphics g) {
 827             super.paint(g);
 828             SynthLookAndFeel.resetSelectedUI();
 829         }
 830     }
 831 }