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(SynthContext.class, c,
 250                     SynthLookAndFeel.getRegion(c), style, state);
 251     }
 252 
 253 //
 254 //  Paint methods and support
 255 //
 256 
 257     /**
 258      * Notifies this UI delegate to repaint the specified component.
 259      * This method paints the component background, then calls
 260      * the {@link #paint(SynthContext,Graphics)} method.
 261      *
 262      * <p>In general, this method does not need to be overridden by subclasses.
 263      * All Look and Feel rendering code should reside in the {@code paint} method.
 264      *
 265      * @param g the {@code Graphics} object used for painting
 266      * @param c the component being painted
 267      * @see #paint(SynthContext,Graphics)
 268      */
 269     @Override
 270     public void update(Graphics g, JComponent c) {
 271         SynthContext context = getContext(c);
 272 
 273         SynthLookAndFeel.update(context, g);
 274         context.getPainter().paintTableBackground(context,
 275                           g, 0, 0, c.getWidth(), c.getHeight());
 276         paint(context, g);
 277         context.dispose();
 278     }
 279 
 280     /**
 281      * {@inheritDoc}
 282      */
 283     @Override
 284     public void paintBorder(SynthContext context, Graphics g, int x,
 285                             int y, int w, int h) {
 286         context.getPainter().paintTableBorder(context, g, x, y, w, h);
 287     }
 288 
 289     /**
 290      * Paints the specified component according to the Look and Feel.
 291      * <p>This method is not used by Synth Look and Feel.
 292      * Painting is handled by the {@link #paint(SynthContext,Graphics)} method.
 293      *
 294      * @param g the {@code Graphics} object used for painting
 295      * @param c the component being painted
 296      * @see #paint(SynthContext,Graphics)
 297      */
 298     @Override
 299     public void paint(Graphics g, JComponent c) {
 300         SynthContext context = getContext(c);
 301 
 302         paint(context, g);
 303         context.dispose();
 304     }
 305 
 306     /**
 307      * Paints the specified component.
 308      *
 309      * @param context context for the component being painted
 310      * @param g the {@code Graphics} object used for painting
 311      * @see #update(Graphics,JComponent)
 312      */
 313     protected void paint(SynthContext context, Graphics g) {
 314         Rectangle clip = g.getClipBounds();
 315 
 316         Rectangle bounds = table.getBounds();
 317         // account for the fact that the graphics has already been translated
 318         // into the table's bounds
 319         bounds.x = bounds.y = 0;
 320 
 321         if (table.getRowCount() <= 0 || table.getColumnCount() <= 0 ||
 322                 // this check prevents us from painting the entire table
 323                 // when the clip doesn't intersect our bounds at all
 324                 !bounds.intersects(clip)) {
 325 
 326             paintDropLines(context, g);
 327             return;
 328         }
 329 
 330         boolean ltr = table.getComponentOrientation().isLeftToRight();
 331 
 332         Point upperLeft = clip.getLocation();
 333 
 334         Point lowerRight = new Point(clip.x + clip.width - 1,
 335                                      clip.y + clip.height - 1);
 336 
 337         int rMin = table.rowAtPoint(upperLeft);
 338         int rMax = table.rowAtPoint(lowerRight);
 339         // This should never happen (as long as our bounds intersect the clip,
 340         // which is why we bail above if that is the case).
 341         if (rMin == -1) {
 342             rMin = 0;
 343         }
 344         // If the table does not have enough rows to fill the view we'll get -1.
 345         // (We could also get -1 if our bounds don't intersect the clip,
 346         // which is why we bail above if that is the case).
 347         // Replace this with the index of the last row.
 348         if (rMax == -1) {
 349             rMax = table.getRowCount()-1;
 350         }
 351 
 352         int cMin = table.columnAtPoint(ltr ? upperLeft : lowerRight);
 353         int cMax = table.columnAtPoint(ltr ? lowerRight : upperLeft);
 354         // This should never happen.
 355         if (cMin == -1) {
 356             cMin = 0;
 357         }
 358         // If the table does not have enough columns to fill the view we'll get -1.
 359         // Replace this with the index of the last column.
 360         if (cMax == -1) {
 361             cMax = table.getColumnCount()-1;
 362         }
 363 
 364         // Paint the cells.
 365         paintCells(context, g, rMin, rMax, cMin, cMax);
 366 
 367         // Paint the grid.
 368         // it is important to paint the grid after the cells, otherwise the grid will be overpainted
 369         // because in Synth cell renderers are likely to be opaque
 370         paintGrid(context, g, rMin, rMax, cMin, cMax);
 371 
 372         paintDropLines(context, g);
 373     }
 374 
 375     private void paintDropLines(SynthContext context, Graphics g) {
 376         JTable.DropLocation loc = table.getDropLocation();
 377         if (loc == null) {
 378             return;
 379         }
 380 
 381         Color color = (Color)style.get(context, "Table.dropLineColor");
 382         Color shortColor = (Color)style.get(context, "Table.dropLineShortColor");
 383         if (color == null && shortColor == null) {
 384             return;
 385         }
 386 
 387         Rectangle rect;
 388 
 389         rect = getHDropLineRect(loc);
 390         if (rect != null) {
 391             int x = rect.x;
 392             int w = rect.width;
 393             if (color != null) {
 394                 extendRect(rect, true);
 395                 g.setColor(color);
 396                 g.fillRect(rect.x, rect.y, rect.width, rect.height);
 397             }
 398             if (!loc.isInsertColumn() && shortColor != null) {
 399                 g.setColor(shortColor);
 400                 g.fillRect(x, rect.y, w, rect.height);
 401             }
 402         }
 403 
 404         rect = getVDropLineRect(loc);
 405         if (rect != null) {
 406             int y = rect.y;
 407             int h = rect.height;
 408             if (color != null) {
 409                 extendRect(rect, false);
 410                 g.setColor(color);
 411                 g.fillRect(rect.x, rect.y, rect.width, rect.height);
 412             }
 413             if (!loc.isInsertRow() && shortColor != null) {
 414                 g.setColor(shortColor);
 415                 g.fillRect(rect.x, y, rect.width, h);
 416             }
 417         }
 418     }
 419 
 420     private Rectangle getHDropLineRect(JTable.DropLocation loc) {
 421         if (!loc.isInsertRow()) {
 422             return null;
 423         }
 424 
 425         int row = loc.getRow();
 426         int col = loc.getColumn();
 427         if (col >= table.getColumnCount()) {
 428             col--;
 429         }
 430 
 431         Rectangle rect = table.getCellRect(row, col, true);
 432 
 433         if (row >= table.getRowCount()) {
 434             row--;
 435             Rectangle prevRect = table.getCellRect(row, col, true);
 436             rect.y = prevRect.y + prevRect.height;
 437         }
 438 
 439         if (rect.y == 0) {
 440             rect.y = -1;
 441         } else {
 442             rect.y -= 2;
 443         }
 444 
 445         rect.height = 3;
 446 
 447         return rect;
 448     }
 449 
 450     private Rectangle getVDropLineRect(JTable.DropLocation loc) {
 451         if (!loc.isInsertColumn()) {
 452             return null;
 453         }
 454 
 455         boolean ltr = table.getComponentOrientation().isLeftToRight();
 456         int col = loc.getColumn();
 457         Rectangle rect = table.getCellRect(loc.getRow(), col, true);
 458 
 459         if (col >= table.getColumnCount()) {
 460             col--;
 461             rect = table.getCellRect(loc.getRow(), col, true);
 462             if (ltr) {
 463                 rect.x = rect.x + rect.width;
 464             }
 465         } else if (!ltr) {
 466             rect.x = rect.x + rect.width;
 467         }
 468 
 469         if (rect.x == 0) {
 470             rect.x = -1;
 471         } else {
 472             rect.x -= 2;
 473         }
 474 
 475         rect.width = 3;
 476 
 477         return rect;
 478     }
 479 
 480     private Rectangle extendRect(Rectangle rect, boolean horizontal) {
 481         if (rect == null) {
 482             return rect;
 483         }
 484 
 485         if (horizontal) {
 486             rect.x = 0;
 487             rect.width = table.getWidth();
 488         } else {
 489             rect.y = 0;
 490 
 491             if (table.getRowCount() != 0) {
 492                 Rectangle lastRect = table.getCellRect(table.getRowCount() - 1, 0, true);
 493                 rect.height = lastRect.y + lastRect.height;
 494             } else {
 495                 rect.height = table.getHeight();
 496             }
 497         }
 498 
 499         return rect;
 500     }
 501 
 502     /*
 503      * Paints the grid lines within <I>aRect</I>, using the grid
 504      * color set with <I>setGridColor</I>. Paints vertical lines
 505      * if <code>getShowVerticalLines()</code> returns true and paints
 506      * horizontal lines if <code>getShowHorizontalLines()</code>
 507      * returns true.
 508      */
 509     private void paintGrid(SynthContext context, Graphics g, int rMin,
 510                            int rMax, int cMin, int cMax) {
 511         g.setColor(table.getGridColor());
 512 
 513         Rectangle minCell = table.getCellRect(rMin, cMin, true);
 514         Rectangle maxCell = table.getCellRect(rMax, cMax, true);
 515         Rectangle damagedArea = minCell.union( maxCell );
 516         SynthGraphicsUtils synthG = context.getStyle().getGraphicsUtils(
 517                      context);
 518 
 519         if (table.getShowHorizontalLines()) {
 520             int tableWidth = damagedArea.x + damagedArea.width;
 521             int y = damagedArea.y;
 522             for (int row = rMin; row <= rMax; row++) {
 523                 y += table.getRowHeight(row);
 524                 synthG.drawLine(context, "Table.grid",
 525                                 g, damagedArea.x, y - 1, tableWidth - 1,y - 1);
 526             }
 527         }
 528         if (table.getShowVerticalLines()) {
 529             TableColumnModel cm = table.getColumnModel();
 530             int tableHeight = damagedArea.y + damagedArea.height;
 531             int x;
 532             if (table.getComponentOrientation().isLeftToRight()) {
 533                 x = damagedArea.x;
 534                 for (int column = cMin; column <= cMax; column++) {
 535                     int w = cm.getColumn(column).getWidth();
 536                     x += w;
 537                     synthG.drawLine(context, "Table.grid", g, x - 1, 0,
 538                                     x - 1, tableHeight - 1);
 539                 }
 540             } else {
 541                 x = damagedArea.x;
 542                 for (int column = cMax; column >= cMin; column--) {
 543                     int w = cm.getColumn(column).getWidth();
 544                     x += w;
 545                     synthG.drawLine(context, "Table.grid", g, x - 1, 0, x - 1,
 546                                     tableHeight - 1);
 547                 }
 548             }
 549         }
 550     }
 551 
 552     private int viewIndexForColumn(TableColumn aColumn) {
 553         TableColumnModel cm = table.getColumnModel();
 554         for (int column = 0; column < cm.getColumnCount(); column++) {
 555             if (cm.getColumn(column) == aColumn) {
 556                 return column;
 557             }
 558         }
 559         return -1;
 560     }
 561 
 562     private void paintCells(SynthContext context, Graphics g, int rMin,
 563                             int rMax, int cMin, int cMax) {
 564         JTableHeader header = table.getTableHeader();
 565         TableColumn draggedColumn = (header == null) ? null : header.getDraggedColumn();
 566 
 567         TableColumnModel cm = table.getColumnModel();
 568         int columnMargin = cm.getColumnMargin();
 569 
 570         Rectangle cellRect;
 571         TableColumn aColumn;
 572         int columnWidth;
 573         if (table.getComponentOrientation().isLeftToRight()) {
 574             for(int row = rMin; row <= rMax; row++) {
 575                 cellRect = table.getCellRect(row, cMin, false);
 576                 for(int column = cMin; column <= cMax; column++) {
 577                     aColumn = cm.getColumn(column);
 578                     columnWidth = aColumn.getWidth();
 579                     cellRect.width = columnWidth - columnMargin;
 580                     if (aColumn != draggedColumn) {
 581                         paintCell(context, g, cellRect, row, column);
 582                     }
 583                     cellRect.x += columnWidth;
 584                 }
 585             }
 586         } else {
 587             for(int row = rMin; row <= rMax; row++) {
 588                 cellRect = table.getCellRect(row, cMin, false);
 589                 aColumn = cm.getColumn(cMin);
 590                 if (aColumn != draggedColumn) {
 591                     columnWidth = aColumn.getWidth();
 592                     cellRect.width = columnWidth - columnMargin;
 593                     paintCell(context, g, cellRect, row, cMin);
 594                 }
 595                 for(int column = cMin+1; column <= cMax; column++) {
 596                     aColumn = cm.getColumn(column);
 597                     columnWidth = aColumn.getWidth();
 598                     cellRect.width = columnWidth - columnMargin;
 599                     cellRect.x -= columnWidth;
 600                     if (aColumn != draggedColumn) {
 601                         paintCell(context, g, cellRect, row, column);
 602                     }
 603                 }
 604             }
 605         }
 606 
 607         // Paint the dragged column if we are dragging.
 608         if (draggedColumn != null) {
 609             paintDraggedArea(context, g, rMin, rMax, draggedColumn, header.getDraggedDistance());
 610         }
 611 
 612         // Remove any renderers that may be left in the rendererPane.
 613         rendererPane.removeAll();
 614     }
 615 
 616     private void paintDraggedArea(SynthContext context, Graphics g, int rMin, int rMax, TableColumn draggedColumn, int distance) {
 617         int draggedColumnIndex = viewIndexForColumn(draggedColumn);
 618 
 619         Rectangle minCell = table.getCellRect(rMin, draggedColumnIndex, true);
 620         Rectangle maxCell = table.getCellRect(rMax, draggedColumnIndex, true);
 621 
 622         Rectangle vacatedColumnRect = minCell.union(maxCell);
 623 
 624         // Paint a gray well in place of the moving column.
 625         g.setColor(table.getParent().getBackground());
 626         g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
 627                    vacatedColumnRect.width, vacatedColumnRect.height);
 628 
 629         // Move to the where the cell has been dragged.
 630         vacatedColumnRect.x += distance;
 631 
 632         // Fill the background.
 633         g.setColor(context.getStyle().getColor(context, ColorType.BACKGROUND));
 634         g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
 635                    vacatedColumnRect.width, vacatedColumnRect.height);
 636 
 637         SynthGraphicsUtils synthG = context.getStyle().getGraphicsUtils(
 638                                             context);
 639 
 640 
 641         // Paint the vertical grid lines if necessary.
 642         if (table.getShowVerticalLines()) {
 643             g.setColor(table.getGridColor());
 644             int x1 = vacatedColumnRect.x;
 645             int y1 = vacatedColumnRect.y;
 646             int x2 = x1 + vacatedColumnRect.width - 1;
 647             int y2 = y1 + vacatedColumnRect.height - 1;
 648             // Left
 649             synthG.drawLine(context, "Table.grid", g, x1-1, y1, x1-1, y2);
 650             // Right
 651             synthG.drawLine(context, "Table.grid", g, x2, y1, x2, y2);
 652         }
 653 
 654         for(int row = rMin; row <= rMax; row++) {
 655             // Render the cell value
 656             Rectangle r = table.getCellRect(row, draggedColumnIndex, false);
 657             r.x += distance;
 658             paintCell(context, g, r, row, draggedColumnIndex);
 659 
 660             // Paint the (lower) horizontal grid line if necessary.
 661             if (table.getShowHorizontalLines()) {
 662                 g.setColor(table.getGridColor());
 663                 Rectangle rcr = table.getCellRect(row, draggedColumnIndex, true);
 664                 rcr.x += distance;
 665                 int x1 = rcr.x;
 666                 int y1 = rcr.y;
 667                 int x2 = x1 + rcr.width - 1;
 668                 int y2 = y1 + rcr.height - 1;
 669                 synthG.drawLine(context, "Table.grid", g, x1, y2, x2, y2);
 670             }
 671         }
 672     }
 673 
 674     private void paintCell(SynthContext context, Graphics g,
 675             Rectangle cellRect, int row, int column) {
 676         if (table.isEditing() && table.getEditingRow()==row &&
 677                                  table.getEditingColumn()==column) {
 678             Component component = table.getEditorComponent();
 679             component.setBounds(cellRect);
 680             component.validate();
 681         }
 682         else {
 683             TableCellRenderer renderer = table.getCellRenderer(row, column);
 684             Component component = table.prepareRenderer(renderer, row, column);
 685             Color b = component.getBackground();
 686             if ((b == null || b instanceof UIResource
 687                     || component instanceof SynthBooleanTableCellRenderer)
 688                     && !table.isCellSelected(row, column)) {
 689                 if (alternateColor != null && row % 2 != 0) {
 690                     component.setBackground(alternateColor);
 691                 }
 692             }
 693             rendererPane.paintComponent(g, component, table, cellRect.x,
 694                     cellRect.y, cellRect.width, cellRect.height, true);
 695         }
 696     }
 697 
 698     /**
 699      * {@inheritDoc}
 700      */
 701     @Override
 702     public void propertyChange(PropertyChangeEvent event) {
 703         if (SynthLookAndFeel.shouldUpdateStyle(event)) {
 704             updateStyle((JTable)event.getSource());
 705         }
 706     }
 707 
 708     @SuppressWarnings("serial") // Superclass is not serializable across versions
 709     private class SynthBooleanTableCellRenderer extends JCheckBox implements
 710                       TableCellRenderer {
 711         private boolean isRowSelected;
 712 
 713         public SynthBooleanTableCellRenderer() {
 714             setHorizontalAlignment(JLabel.CENTER);
 715             setName("Table.cellRenderer");
 716         }
 717 
 718         public Component getTableCellRendererComponent(
 719                             JTable table, Object value, boolean isSelected,
 720                             boolean hasFocus, int row, int column) {
 721             isRowSelected = isSelected;
 722 
 723             if (isSelected) {
 724                 setForeground(unwrap(table.getSelectionForeground()));
 725                 setBackground(unwrap(table.getSelectionBackground()));
 726             } else {
 727                 setForeground(unwrap(table.getForeground()));
 728                 setBackground(unwrap(table.getBackground()));
 729             }
 730 
 731             setSelected((value != null && ((Boolean)value).booleanValue()));
 732             return this;
 733         }
 734 
 735         private Color unwrap(Color c) {
 736             if (c instanceof UIResource) {
 737                 return new Color(c.getRGB());
 738             }
 739             return c;
 740         }
 741 
 742         public boolean isOpaque() {
 743             return isRowSelected ? true : super.isOpaque();
 744         }
 745     }
 746 
 747     @SuppressWarnings("serial") // Superclass is not serializable across versions
 748     private class SynthTableCellRenderer extends DefaultTableCellRenderer {
 749         private Object numberFormat;
 750         private Object dateFormat;
 751         private boolean opaque;
 752 
 753         public void setOpaque(boolean isOpaque) {
 754             opaque = isOpaque;
 755         }
 756 
 757         public boolean isOpaque() {
 758             return opaque;
 759         }
 760 
 761         public String getName() {
 762             String name = super.getName();
 763             if (name == null) {
 764                 return "Table.cellRenderer";
 765             }
 766             return name;
 767         }
 768 
 769         public void setBorder(Border b) {
 770             if (useUIBorder || b instanceof SynthBorder) {
 771                 super.setBorder(b);
 772             }
 773         }
 774 
 775         public Component getTableCellRendererComponent(
 776                   JTable table, Object value, boolean isSelected,
 777                   boolean hasFocus, int row, int column) {
 778             if (!useTableColors && (isSelected || hasFocus)) {
 779                 SynthLookAndFeel.setSelectedUI((SynthLabelUI)SynthLookAndFeel.
 780                              getUIOfType(getUI(), SynthLabelUI.class),
 781                                    isSelected, hasFocus, table.isEnabled(), false);
 782             }
 783             else {
 784                 SynthLookAndFeel.resetSelectedUI();
 785             }
 786             super.getTableCellRendererComponent(table, value, isSelected,
 787                                                 hasFocus, row, column);
 788 
 789             setIcon(null);
 790             if (table != null) {
 791                 configureValue(value, table.getColumnClass(column));
 792             }
 793             return this;
 794         }
 795 
 796         private void configureValue(Object value, Class columnClass) {
 797             if (columnClass == Object.class || columnClass == null) {
 798                 setHorizontalAlignment(JLabel.LEADING);
 799             } else if (columnClass == Float.class || columnClass == Double.class) {
 800                 if (numberFormat == null) {
 801                     numberFormat = NumberFormat.getInstance();
 802                 }
 803                 setHorizontalAlignment(JLabel.TRAILING);
 804                 setText((value == null) ? "" : ((NumberFormat)numberFormat).format(value));
 805             }
 806             else if (columnClass == Number.class) {
 807                 setHorizontalAlignment(JLabel.TRAILING);
 808                 // Super will have set value.
 809             }
 810             else if (columnClass == Icon.class || columnClass == ImageIcon.class) {
 811                 setHorizontalAlignment(JLabel.CENTER);
 812                 setIcon((value instanceof Icon) ? (Icon)value : null);
 813                 setText("");
 814             }
 815             else if (columnClass == Date.class) {
 816                 if (dateFormat == null) {
 817                     dateFormat = DateFormat.getDateInstance();
 818                 }
 819                 setHorizontalAlignment(JLabel.LEADING);
 820                 setText((value == null) ? "" : ((Format)dateFormat).format(value));
 821             }
 822             else {
 823                 configureValue(value, columnClass.getSuperclass());
 824             }
 825         }
 826 
 827         public void paint(Graphics g) {
 828             super.paint(g);
 829             SynthLookAndFeel.resetSelectedUI();
 830         }
 831     }
 832 }