Package org.pushingpixels.substance.internal.ui

Source Code of org.pushingpixels.substance.internal.ui.SubstanceTableUI$TableUpdateOptimizationInfo

/*
* Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*  o Redistributions of source code must retain the above copyright notice,
*    this list of conditions and the following disclaimer.
*
*  o Redistributions in binary form must reproduce the above copyright notice,
*    this list of conditions and the following disclaimer in the documentation
*    and/or other materials provided with the distribution.
*
*  o Neither the name of Substance Kirill Grouchnikov nor the names of
*    its contributors may be used to endorse or promote products derived
*    from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pushingpixels.substance.internal.ui;

import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.List;

import javax.swing.*;
import javax.swing.RowSorter.SortKey;
import javax.swing.border.Border;
import javax.swing.event.*;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.*;

import org.pushingpixels.lafwidget.LafWidgetUtilities;
import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
import org.pushingpixels.lafwidget.animation.AnimationFacet;
import org.pushingpixels.substance.api.*;
import org.pushingpixels.substance.api.SubstanceConstants.Side;
import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableCellRenderer;
import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableHeaderCellRenderer;
import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
import org.pushingpixels.substance.internal.utils.*;
import org.pushingpixels.trident.Timeline.TimelineState;
import org.pushingpixels.trident.callback.TimelineCallback;
import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;

/**
* UI for tables in <b>Substance</b> look and feel. Unfortunately, the entire
* painting stack has been copied from {@link BasicTableUI} since the methods
* are private. The animation effects are implemented in the
* {@link #paintCell(Graphics, Rectangle, int, int)}.
*
* @author Kirill Grouchnikov
*/
public class SubstanceTableUI extends BasicTableUI implements
    UpdateOptimizationAware {
  /**
   * Holds the list of currently selected row-column indexes.
   */
  protected Map<TableCellId, Object> selectedIndices;

  /**
   * Holds the currently rolled-over row-column index, or <code>null</code> if
   * none such.
   */
  protected Set<TableCellId> rolledOverIndices;

  protected TableCellId focusedCellId;

  /**
   * Holds the currently rolled-over column index, or <code>-1</code> if none
   * such. This is used for the table header animations.
   */
  protected int rolledOverColumn;

  /**
   * Map of default renderers.
   */
  protected Map<Class<?>, TableCellRenderer> defaultRenderers;

  /**
   * Map of default editors.
   */
  protected Map<Class<?>, TableCellEditor> defaultEditors;

  /**
   * Listener that listens to changes on table properties.
   */
  protected PropertyChangeListener substancePropertyChangeListener;

  /**
   * Listener for transition animations on list selections.
   */
  protected TableStateListener substanceTableStateListener;

  /**
   * Listener for transition animations on table rollovers.
   */
  protected RolloverFadeListener substanceFadeRolloverListener;

  protected FocusListener substanceFocusListener;

  private StateTransitionMultiTracker<TableCellId> stateTransitionMultiTracker;

  /**
   * Cell renderer insets. Is computed in {@link #installDefaults()} and
   * reused in
   * {@link SubstanceDefaultTableCellRenderer#getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)}
   * and
   * {@link SubstanceDefaultTableHeaderCellRenderer#getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)}
   * for performance optimizations.
   */
  private Insets cellRendererInsets;

  /*
   * (non-Javadoc)
   *
   * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
   */
  public static ComponentUI createUI(JComponent comp) {
    SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
    return new SubstanceTableUI();
  }

  /**
   * Creates a UI delegate for table.
   */
  public SubstanceTableUI() {
    super();
    this.selectedIndices = new HashMap<TableCellId, Object>();
    this.rolledOverIndices = new HashSet<TableCellId>();
    this.stateTransitionMultiTracker = new StateTransitionMultiTracker<TableCellId>();
    this.rolledOverColumn = -1;

    this.cellId = new TableCellId(-1, -1);
  }

  static class BooleanEditor extends DefaultCellEditor {
    private static class SubstanceEditorCheckBox extends JCheckBox {
      @Override
      public void setOpaque(boolean isOpaque) {
        if (!isOpaque) {
          super.setOpaque(isOpaque);
        }
      }

      @Override
      public boolean isOpaque() {
        return false;
      }

      @Override
      public void setBorder(Border border) {
      }
    }

    public BooleanEditor() {
      super(new SubstanceEditorCheckBox());
      JCheckBox checkBox = (JCheckBox) getComponent();
      checkBox.setOpaque(false);
      checkBox.setHorizontalAlignment(JCheckBox.CENTER);
    }
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.swing.plaf.basic.BasicTableUI#installDefaults()
   */
  @Override
  protected void installDefaults() {
    super.installDefaults();
    if (SubstanceCoreUtilities.toDrawWatermark(this.table))
      this.table.setOpaque(false);

    // fix for defect 117 - need to restore default table cell
    // renderers when Substance is unset
    this.defaultRenderers = new HashMap<Class<?>, TableCellRenderer>();

    Class<?>[] defClasses = new Class[] { Object.class, Icon.class,
        ImageIcon.class, Number.class, Float.class, Double.class,
        Date.class, Boolean.class };
    for (Class<?> clazz : defClasses) {
      this.defaultRenderers.put(clazz,
          this.table.getDefaultRenderer(clazz));
    }

    // Override default renderers - note fix for issue 194
    // that doesn't override user-specific renderers (those that don't come
    // from JTable class).
    this.installRendererIfNecessary(Object.class,
        new SubstanceDefaultTableCellRenderer());
    this.installRendererIfNecessary(Icon.class,
        new SubstanceDefaultTableCellRenderer.IconRenderer());
    this.installRendererIfNecessary(ImageIcon.class,
        new SubstanceDefaultTableCellRenderer.IconRenderer());
    this.installRendererIfNecessary(Number.class,
        new SubstanceDefaultTableCellRenderer.NumberRenderer());
    this.installRendererIfNecessary(Float.class,
        new SubstanceDefaultTableCellRenderer.DoubleRenderer());
    this.installRendererIfNecessary(Double.class,
        new SubstanceDefaultTableCellRenderer.DoubleRenderer());
    this.installRendererIfNecessary(Date.class,
        new SubstanceDefaultTableCellRenderer.DateRenderer());

    // fix for bug 56 - making default renderer for Boolean a check box.
    this.installRendererIfNecessary(Boolean.class,
        new SubstanceDefaultTableCellRenderer.BooleanRenderer());

    this.defaultEditors = new HashMap<Class<?>, TableCellEditor>();

    Class<?>[] defEditorClasses = new Class[] { Boolean.class };
    for (Class<?> clazz : defEditorClasses) {
      this.defaultEditors.put(clazz, this.table.getDefaultEditor(clazz));
    }
    this.installEditorIfNecessary(Boolean.class, new BooleanEditor());

    int rows = this.table.getRowCount();
    int cols = this.table.getColumnCount();
    for (int i = 0; i < rows; i++) {
      for (int j = 0; j < cols; j++) {
        if (this.table.isCellSelected(i, j)) {
          TableCellId cellId = new TableCellId(i, j);
          this.selectedIndices.put(cellId,
              this.table.getValueAt(i, j));
        }
      }
    }

    // This is a little tricky, and hopefully will not
    // interfere with existing applications. The row height in tables
    // is computed differently from trees and lists. While lists
    // trees respect the current renderers and their insets, the
    // JTable uses hard-code value of 16 pixels as the default
    // row height. This, obviously, doesn't sit well with the support
    // for custom fonts and high-DPI monitors.
    //
    // The current solution first checks whether all the renderers
    // come from Substance. If not, it does nothing. If they do, it
    // creates a dummy label, computes its preferred height and apply
    // insets. There's no need to go over each cell and compute its
    // preferred height - since at this moment the cell renderers
    // *are* Substance labels.
    boolean areAllRenderersFromSubstance = true;
    TableColumnModel columnModel = table.getColumnModel();
    for (int i = 0; i < columnModel.getColumnCount(); i++) {
      TableColumn column = columnModel.getColumn(i);
      TableCellRenderer renderer = column.getCellRenderer();
      if (renderer == null) {
        renderer = table.getDefaultRenderer(table.getColumnClass(i));
      }
      if ((renderer instanceof SubstanceDefaultTableCellRenderer)
          || (renderer instanceof SubstanceDefaultTableCellRenderer.BooleanRenderer))
        continue;
      areAllRenderersFromSubstance = false;
      break;
    }
    if (areAllRenderersFromSubstance) {
      Insets rendererInsets = SubstanceSizeUtils
          .getTableCellRendererInsets(SubstanceSizeUtils
              .getComponentFontSize(table));
      JLabel dummy = new JLabel("dummy");
      dummy.setFont(table.getFont());
      int rowHeight = dummy.getPreferredSize().height
          + rendererInsets.bottom + rendererInsets.top;
      table.setRowHeight(rowHeight);
    }

    // instead of computing the cell renderer insets on
    // every cell rendering, compute it once and expose to the
    // SubstanceDefaultTableCellRenderer
    this.cellRendererInsets = SubstanceSizeUtils
        .getTableCellRendererInsets(SubstanceSizeUtils
            .getComponentFontSize(table));
  }

  /**
   * Installs Substance-specific renderers for column classes that don't have
   * application-specific renderers installed by the user code.
   *
   * @param clazz
   *            Column class.
   * @param renderer
   *            Default renderer for the specified column class.
   */
  protected void installRendererIfNecessary(Class<?> clazz,
      TableCellRenderer renderer) {
    TableCellRenderer currRenderer = this.table.getDefaultRenderer(clazz);
    if (currRenderer != null) {
      boolean isCore = (currRenderer instanceof DefaultTableCellRenderer.UIResource)
          || (currRenderer.getClass().getName()
              .startsWith("javax.swing.JTable"));
      if (!isCore)
        return;
    }
    this.table.setDefaultRenderer(clazz, renderer);
  }

  /**
   * Installs Substance-specific renderers for column classes that don't have
   * application-specific renderers installed by the user code.
   *
   * @param clazz
   *            Column class.
   * @param editor
   *            Default renderer for the specified column class.
   */
  protected void installEditorIfNecessary(Class<?> clazz,
      TableCellEditor editor) {
    TableCellEditor currEditor = this.table.getDefaultEditor(clazz);
    if (currEditor != null) {
      boolean isCore = currEditor.getClass().getName()
          .startsWith("javax.swing.JTable");
      if (!isCore)
        return;
    }
    this.table.setDefaultEditor(clazz, editor);
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.swing.plaf.basic.BasicTableUI#uninstallDefaults()
   */
  @Override
  protected void uninstallDefaults() {
    // fix for defect 117 - need to restore default table cell
    // renderers when Substance is unset
    for (Map.Entry<Class<?>, TableCellRenderer> entry : this.defaultRenderers
        .entrySet()) {
      // fix for issue 194 - restore only those renderers that were
      // overriden by Substance.
      this.uninstallRendererIfNecessary(entry.getKey(), entry.getValue());
    }

    for (Map.Entry<Class<?>, TableCellEditor> entry : this.defaultEditors
        .entrySet()) {
      this.uninstallEditorIfNecessary(entry.getKey(), entry.getValue());
    }

    this.selectedIndices.clear();
    // this.table.putClientProperty(SubstanceTableUI.SELECTED_INDICES,
    // null);

    super.uninstallDefaults();
  }

  /**
   * Uninstalls default Substance renderers that were installed in
   * {@link #installRendererIfNecessary(Class, TableCellRenderer)}.
   *
   * @param clazz
   *            Column class.
   * @param renderer
   *            Renderer to restore.
   */
  protected void uninstallRendererIfNecessary(Class<?> clazz,
      TableCellRenderer renderer) {
    TableCellRenderer currRenderer = this.table.getDefaultRenderer(clazz);
    if (currRenderer != null) {
      boolean isSubstanceRenderer = isSubstanceDefaultRenderer(currRenderer);
      if (!isSubstanceRenderer)
        return;
    }
    if (renderer instanceof Component)
      SwingUtilities.updateComponentTreeUI((Component) renderer);
    this.table.setDefaultRenderer(clazz, renderer);
  }

  /**
   * Uninstalls default Substance editors that were installed in
   * {@link #installEditorIfNecessary(Class, TableCellEditor)}.
   *
   * @param clazz
   *            Column class.
   * @param editor
   *            Editor to restore.
   */
  protected void uninstallEditorIfNecessary(Class<?> clazz,
      TableCellEditor editor) {
    TableCellEditor currEditor = this.table.getDefaultEditor(clazz);
    if (currEditor != null) {
      boolean isSubstanceEditor = isSubstanceDefaultEditor(currEditor);
      if (!isSubstanceEditor)
        return;
    }
    if (editor instanceof Component)
      SwingUtilities.updateComponentTreeUI((Component) editor);
    this.table.setDefaultEditor(clazz, editor);
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.swing.plaf.basic.BasicTableUI#installListeners()
   */
  @Override
  protected void installListeners() {
    super.installListeners();
    this.substancePropertyChangeListener = new PropertyChangeListener() {
      public void propertyChange(PropertyChangeEvent evt) {
        if (SubstanceLookAndFeel.WATERMARK_VISIBLE.equals(evt
            .getPropertyName())) {
          SubstanceTableUI.this.table
              .setOpaque(!SubstanceCoreUtilities
                  .toDrawWatermark(SubstanceTableUI.this.table));
        }

        if ("columnSelectionAllowed".equals(evt.getPropertyName())
            || "rowSelectionAllowed".equals(evt.getPropertyName())) {
          SubstanceTableUI.this.syncSelection(true);
        }

        if ("model".equals(evt.getPropertyName())) {
          TableModel old = (TableModel) evt.getOldValue();
          if (old != null) {
            old.removeTableModelListener(substanceTableStateListener);
          }
          // fix for defect 291 - track changes to the table.
          table.getModel().addTableModelListener(
              substanceTableStateListener);
          selectedIndices.clear();
          stateTransitionMultiTracker.clear();
          SubstanceTableUI.this.syncSelection(true);
        }

        if ("columnModel".equals(evt.getPropertyName())) {
          TableColumnModel old = (TableColumnModel) evt.getOldValue();
          if (old != null) {
            old.getSelectionModel().removeListSelectionListener(
                substanceTableStateListener);
          }
          table.getColumnModel()
              .getSelectionModel()
              .addListSelectionListener(
                  substanceTableStateListener);
          selectedIndices.clear();
          stateTransitionMultiTracker.clear();
          SubstanceTableUI.this.syncSelection(true);

          JTableHeader tableHeader = table.getTableHeader();
          // fix for issue 408 - table header may be null.
          if (tableHeader != null) {
            // fix for issue 309 - syncing animations on tables
            // and table headers.
            SubstanceTableHeaderUI headerUI = (SubstanceTableHeaderUI) tableHeader
                .getUI();
            headerUI.processColumnModelChangeEvent(
                (TableColumnModel) evt.getOldValue(),
                (TableColumnModel) evt.getNewValue());
          }
        }

        // fix for defect 243 - not tracking changes to selection
        // model results in incorrect selection painting on JXTreeTable
        // component from SwingX.
        if ("selectionModel".equals(evt.getPropertyName())) {
          ListSelectionModel old = (ListSelectionModel) evt
              .getOldValue();
          if (old != null) {
            old.removeListSelectionListener(substanceTableStateListener);
          }
          table.getSelectionModel().addListSelectionListener(
              substanceTableStateListener);
          selectedIndices.clear();
          stateTransitionMultiTracker.clear();
          SubstanceTableUI.this.syncSelection(true);
        }

        // fix for issue 479 - tracking sort / filter changes and
        // canceling selection animations
        if ("rowSorter".equals(evt.getPropertyName())) {
          RowSorter old = (RowSorter) evt.getOldValue();
          if (old != null) {
            old.removeRowSorterListener(substanceTableStateListener);
          }
          RowSorter newSorter = (RowSorter) evt.getNewValue();
          if (newSorter != null) {
            newSorter
                .addRowSorterListener(substanceTableStateListener);
          }
          selectedIndices.clear();
          stateTransitionMultiTracker.clear();
          SubstanceTableUI.this.syncSelection(true);
        }

        if ("font".equals(evt.getPropertyName())) {
          SwingUtilities.invokeLater(new Runnable() {
            public void run() {
              // fix for bug 341
              if (table == null)
                return;
              table.updateUI();
            }
          });
        }

        if ("background".equals(evt.getPropertyName())) {
          // propagate application-specific background color to the
          // header.
          Color newBackgr = (Color) evt.getNewValue();
          JTableHeader header = table.getTableHeader();
          if (header != null) {
            Color headerBackground = header.getBackground();
            if (SubstanceCoreUtilities
                .canReplaceChildBackgroundColor(headerBackground)) {
              if (!(newBackgr instanceof UIResource)) {
                if (newBackgr == null) {
                  header.setBackground(null);
                } else {
                  // Issue 450 - wrap the color in
                  // SubstanceColorResource to
                  // mark that it can be replaced.
                  header.setBackground(new SubstanceColorResource(
                      newBackgr));
                }
              } else {
                header.setBackground(newBackgr);
              }
            }
          }
        }

        // fix for issue 361 - track enabled status of the table
        // and propagate to the table header
        if ("enabled".equals(evt.getPropertyName())) {
          JTableHeader header = table.getTableHeader();
          if (header != null) {
            header.setEnabled(table.isEnabled());
          }
        }

        if ("dropLocation".equals(evt.getPropertyName())) {
          JTable.DropLocation oldValue = (JTable.DropLocation) evt
              .getOldValue();
          if (oldValue != null) {
            Rectangle oldRect = getCellRectangleForRepaint(
                oldValue.getRow(), oldValue.getColumn());
            table.repaint(oldRect);
          }
          JTable.DropLocation newValue = table.getDropLocation();
          if (newValue != null) {
            Rectangle newRect = getCellRectangleForRepaint(table
                .getDropLocation().getRow(), table
                .getDropLocation().getColumn());
            table.repaint(newRect);
          }
        }

        if ("tableCellEditor".equals(evt.getPropertyName())) {
          // fix for issue 481 - rollovers on cell editing
          TableCellEditor newEditor = (TableCellEditor) evt
              .getNewValue();
          TableCellEditor oldEditor = (TableCellEditor) evt
              .getOldValue();
          if (oldEditor != null) {
            table.getEditorComponent().removeMouseListener(
                substanceFadeRolloverListener);
          }
          if (newEditor != null) {
            table.getEditorComponent().addMouseListener(
                substanceFadeRolloverListener);
          }
        }
      }
    };
    this.table
        .addPropertyChangeListener(this.substancePropertyChangeListener);

    // Add listener for the selection animation
    this.substanceTableStateListener = new TableStateListener();
    this.table.getSelectionModel().addListSelectionListener(
        this.substanceTableStateListener);
    TableColumnModel columnModel = this.table.getColumnModel();
    columnModel.getSelectionModel().addListSelectionListener(
        this.substanceTableStateListener);
    this.table.getModel().addTableModelListener(
        this.substanceTableStateListener);
    if (this.table.getRowSorter() != null) {
      this.table.getRowSorter().addRowSorterListener(
          this.substanceTableStateListener);
    }

    // Add listener for the transition animation
    this.substanceFadeRolloverListener = new RolloverFadeListener();
    this.table.addMouseMotionListener(this.substanceFadeRolloverListener);
    this.table.addMouseListener(this.substanceFadeRolloverListener);

    // fix for issue 481 - tracking focus events on the table
    this.substanceFocusListener = new FocusListener() {
      @Override
      public void focusLost(FocusEvent e) {
        if (focusedCellId == null)
          return;

        ComponentState cellState = getCellState(focusedCellId);
        StateTransitionTracker tracker = getTracker(focusedCellId,
            cellState.isFacetActive(ComponentStateFacet.ROLLOVER),
            cellState.isFacetActive(ComponentStateFacet.SELECTION));
        tracker.setFocusState(false);

        focusedCellId = null;
      }

      @Override
      public void focusGained(FocusEvent e) {
        int rowLead = table.getSelectionModel().getLeadSelectionIndex();
        int colLead = table.getColumnModel().getSelectionModel()
            .getLeadSelectionIndex();
        if ((rowLead >= 0) && (colLead >= 0)) {
          TableCellId toFocus = new TableCellId(rowLead, colLead);
          if (toFocus.equals(focusedCellId))
            return;
          ComponentState cellState = getCellState(toFocus);
          StateTransitionTracker tracker = getTracker(
              toFocus,
              cellState
                  .isFacetActive(ComponentStateFacet.ROLLOVER),
              cellState
                  .isFacetActive(ComponentStateFacet.SELECTION));
          tracker.setFocusState(true);

          focusedCellId = toFocus;
        }
      }
    };
    this.table.addFocusListener(this.substanceFocusListener);
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.swing.plaf.basic.BasicTableUI#uninstallListeners()
   */
  @Override
  protected void uninstallListeners() {
    this.table
        .removePropertyChangeListener(this.substancePropertyChangeListener);
    this.substancePropertyChangeListener = null;

    this.table.getSelectionModel().removeListSelectionListener(
        this.substanceTableStateListener);
    this.table.getColumnModel().getSelectionModel()
        .removeListSelectionListener(this.substanceTableStateListener);
    this.table.getModel().removeTableModelListener(
        this.substanceTableStateListener);
    if (this.table.getRowSorter() != null) {
      this.table.getRowSorter().removeRowSorterListener(
          this.substanceTableStateListener);
    }
    this.substanceTableStateListener = null;

    // Remove listener for the fade animation
    this.table
        .removeMouseMotionListener(this.substanceFadeRolloverListener);
    this.table.removeMouseListener(this.substanceFadeRolloverListener);
    this.substanceFadeRolloverListener = null;

    this.table.removeFocusListener(this.substanceFocusListener);
    this.substanceFocusListener = null;

    super.uninstallListeners();
  }

  /**
   * Paint a representation of the <code>table</code> instance that was set in
   * installUI().
   */
  @Override
  public void paint(Graphics g, JComponent c) {
    Rectangle clip = g.getClipBounds();

    Rectangle bounds = this.table.getBounds();
    // account for the fact that the graphics has already been translated
    // into the table's bounds
    bounds.x = bounds.y = 0;

    if (this.table.getRowCount() <= 0 || this.table.getColumnCount() <= 0 ||
    // this check prevents us from painting the entire table
    // when the clip doesn't intersect our bounds at all
        !bounds.intersects(clip)) {

      return;
    }

    Point upperLeft = clip.getLocation();
    Point lowerRight = new Point(clip.x + clip.width - 1, clip.y
        + clip.height - 1);
    int rMin = this.table.rowAtPoint(upperLeft);
    int rMax = this.table.rowAtPoint(lowerRight);
    // This should never happen (as long as our bounds intersect the clip,
    // which is why we bail above if that is the case).
    if (rMin == -1) {
      rMin = 0;
    }
    // If the table does not have enough rows to fill the view we'll get -1.
    // (We could also get -1 if our bounds don't intersect the clip,
    // which is why we bail above if that is the case).
    // Replace this with the index of the last row.
    if (rMax == -1) {
      rMax = this.table.getRowCount() - 1;
    }

    boolean ltr = this.table.getComponentOrientation().isLeftToRight();
    int cMin = this.table.columnAtPoint(ltr ? upperLeft : lowerRight);
    int cMax = this.table.columnAtPoint(ltr ? lowerRight : upperLeft);
    // This should never happen.
    if (cMin == -1) {
      cMin = 0;
    }
    // If the table does not have enough columns to fill the view we'll get
    // -1.
    // Replace this with the index of the last column.
    if (cMax == -1) {
      cMax = this.table.getColumnCount() - 1;
    }

    // Paint the cells.
    this.paintCells(g, rMin, rMax, cMin, cMax);

    // Paint the grid.
    this.paintGrid(g, rMin, rMax, cMin, cMax);

    // Paint the drop lines
    this.paintDropLines(g);
  }

  /**
   * Paints the grid lines within <I>aRect</I>, using the grid color set with
   * <I>setGridColor</I>. Paints vertical lines if
   * <code>getShowVerticalLines()</code> returns true and paints horizontal
   * lines if <code>getShowHorizontalLines()</code> returns true.
   */
  protected void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) {
    Graphics2D g2d = (Graphics2D) g.create();
    ComponentState currState = this.table.isEnabled() ? ComponentState.ENABLED
        : ComponentState.DISABLED_UNSELECTED;
    float alpha = SubstanceColorSchemeUtilities.getAlpha(this.table,
        currState);
    g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.table,
        alpha, g));

    Color gridColor = this.table.getGridColor();
    if (gridColor instanceof UIResource) {
      SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
          .getColorScheme(this.table,
              ColorSchemeAssociationKind.BORDER, this.table
                  .isEnabled() ? ComponentState.ENABLED
                  : ComponentState.DISABLED_UNSELECTED);
      gridColor = scheme.getLineColor();
    }
    g2d.setColor(gridColor);

    Rectangle minCell = this.table.getCellRect(rMin, cMin, true);
    Rectangle maxCell = this.table.getCellRect(rMax, cMax, true);
    Rectangle damagedArea = minCell.union(maxCell);

    float strokeWidth = SubstanceSizeUtils
        .getBorderStrokeWidth(SubstanceSizeUtils
            .getComponentFontSize(this.table));
    g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND,
        BasicStroke.JOIN_BEVEL));
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);

    if (this.table.getShowHorizontalLines()) {
      int tableWidth = damagedArea.x + damagedArea.width;
      int y = damagedArea.y;
      for (int row = rMin; row <= rMax; row++) {
        y += this.table.getRowHeight(row);
        g2d.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1);
      }
    }
    if (this.table.getShowVerticalLines()) {
      TableColumnModel cm = this.table.getColumnModel();
      int tableHeight = damagedArea.y + damagedArea.height;
      int x;
      if (this.table.getComponentOrientation().isLeftToRight()) {
        x = damagedArea.x;
        for (int column = cMin; column <= cMax; column++) {
          int w = cm.getColumn(column).getWidth();
          if (hasLeadingVerticalGridLine(cm, column)) {
            g2d.drawLine(x, 0, x, tableHeight - 1);
          }
          x += w;
          if (hasTrailingVerticalGridLine(cm, column)) {
            g2d.drawLine(x - 1, 0, x - 1, tableHeight - 1);
          }
        }
      } else {
        x = damagedArea.x + damagedArea.width;
        // fix for defect 196 - proper grid painting on RTL tables
        for (int column = cMin; column <= cMax; column++) {
          int w = cm.getColumn(column).getWidth();
          if (hasLeadingVerticalGridLine(cm, column)) {
            g2d.drawLine(x - 1, 0, x - 1, tableHeight - 1);
          }
          x -= w;
          if (hasTrailingVerticalGridLine(cm, column)) {
            g2d.drawLine(x, 0, x, tableHeight - 1);
          }
        }
      }
    }
    g2d.dispose();
  }

  private boolean hasTrailingVerticalGridLine(TableColumnModel cm, int column) {
    boolean toDrawLine = (column != (cm.getColumnCount() - 1));
    if (!toDrawLine) {
      Container parent = this.table.getParent();
      toDrawLine = (parent != null)
          && (parent.getWidth() > this.table.getWidth());
    }
    return toDrawLine;
  }

  private boolean hasLeadingVerticalGridLine(TableColumnModel cm, int column) {
    if (column != 0)
      return false;
    Container parent = this.table.getParent();
    if (parent instanceof JViewport) {
      Container grand = parent.getParent();
      if (grand instanceof JScrollPane) {
        return (((JScrollPane) grand).getRowHeader() != null);
      }
    }
    return false;
  }

  private int viewIndexForColumn(TableColumn aColumn) {
    TableColumnModel cm = this.table.getColumnModel();
    for (int column = 0; column < cm.getColumnCount(); column++) {
      if (cm.getColumn(column) == aColumn) {
        return column;
      }
    }
    return -1;
  }

  protected void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
    JTableHeader header = this.table.getTableHeader();
    TableColumn draggedColumn = (header == null) ? null : header
        .getDraggedColumn();

    TableColumnModel cm = this.table.getColumnModel();
    int columnMargin = cm.getColumnMargin();
    int rowMargin = this.table.getRowMargin();

    Rectangle cellRect;
    Rectangle highlightCellRect;
    TableColumn aColumn;
    int columnWidth;
    if (this.table.getComponentOrientation().isLeftToRight()) {
      for (int row = rMin; row <= rMax; row++) {
        cellRect = this.table.getCellRect(row, cMin, false);

        highlightCellRect = new Rectangle(cellRect);
        highlightCellRect.y -= rowMargin / 2;
        highlightCellRect.height += rowMargin;

        for (int column = cMin; column <= cMax; column++) {
          aColumn = cm.getColumn(column);
          columnWidth = aColumn.getWidth();

          cellRect.width = columnWidth - columnMargin;
          highlightCellRect.x = cellRect.x - columnMargin / 2;
          highlightCellRect.width = columnWidth;
          if (!hasTrailingVerticalGridLine(cm, column)) {
            cellRect.width++;
            highlightCellRect.width++;
          }

          if (aColumn != draggedColumn) {
            this.paintCell(g, cellRect, highlightCellRect, row,
                column);
          }
          cellRect.x += columnWidth;
        }
      }
    } else {
      for (int row = rMin; row <= rMax; row++) {
        cellRect = this.table.getCellRect(row, cMin, false);
        highlightCellRect = new Rectangle(cellRect);
        highlightCellRect.y -= rowMargin / 2;
        highlightCellRect.height += rowMargin;

        for (int column = cMin; column <= cMax; column++) {
          aColumn = cm.getColumn(column);
          columnWidth = aColumn.getWidth();
          cellRect.width = columnWidth - columnMargin;

          highlightCellRect.x = cellRect.x - columnMargin / 2;
          highlightCellRect.width = columnWidth;
          if (aColumn != draggedColumn) {
            this.paintCell(g, cellRect, highlightCellRect, row,
                column);
          }
          cellRect.x -= columnWidth;
        }
      }
    }

    // Paint the dragged column if we are dragging.
    if (draggedColumn != null) {
      Graphics2D g2d = (Graphics2D) g.create();
      // enhancement 331 - translucent dragged column
      g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.table,
          0.65f, g));
      this.paintDraggedArea(g2d, rMin, rMax, draggedColumn,
          header.getDraggedDistance());
      g2d.dispose();
    }

    // Remove any renderers that may be left in the rendererPane.
    this.rendererPane.removeAll();
  }

  protected void paintDraggedArea(Graphics g, int rMin, int rMax,
      TableColumn draggedColumn, int distance) {
    int draggedColumnIndex = this.viewIndexForColumn(draggedColumn);

    Rectangle minCell = this.table.getCellRect(rMin, draggedColumnIndex,
        true);
    Rectangle maxCell = this.table.getCellRect(rMax, draggedColumnIndex,
        true);

    Rectangle vacatedColumnRect = minCell.union(maxCell);

    // Paint a gray well in place of the moving column.
    g.setColor(this.table.getParent().getBackground());
    g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
        vacatedColumnRect.width, vacatedColumnRect.height);

    // Move to the where the cell has been dragged.
    vacatedColumnRect.x += distance;

    // Fill the background.
    g.setColor(this.table.getBackground());
    g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
        vacatedColumnRect.width, vacatedColumnRect.height);

    // Paint the vertical grid lines if necessary.
    if (this.table.getShowVerticalLines()) {
      g.setColor(this.table.getGridColor());
      int x1 = vacatedColumnRect.x;
      int y1 = vacatedColumnRect.y;
      int x2 = x1 + vacatedColumnRect.width - 1;
      int y2 = y1 + vacatedColumnRect.height - 1;
      // Left
      g.drawLine(x1 - 1, y1, x1 - 1, y2);
      // Right
      g.drawLine(x2, y1, x2, y2);
    }

    for (int row = rMin; row <= rMax; row++) {
      // Render the cell value
      Rectangle r = this.table
          .getCellRect(row, draggedColumnIndex, false);
      r.x += distance;
      this.paintCell(g, r, r, row, draggedColumnIndex);

      // Paint the (lower) horizontal grid line if necessary.
      if (this.table.getShowHorizontalLines()) {
        g.setColor(this.table.getGridColor());
        Rectangle rcr = this.table.getCellRect(row, draggedColumnIndex,
            true);
        rcr.x += distance;
        int x1 = rcr.x;
        int y1 = rcr.y;
        int x2 = x1 + rcr.width - 1;
        int y2 = y1 + rcr.height - 1;
        g.drawLine(x1, y2, x2, y2);
      }
    }
  }

  protected void paintCell(Graphics g, Rectangle cellRect,
      Rectangle highlightCellRect, int row, int column) {
    // System.out.println("Painting " + row + ":" + column);
    Component rendererComponent = null;
    if (!this.table.isEditing() || this.table.getEditingRow() != row
        || this.table.getEditingColumn() != column) {
      TableCellRenderer renderer = this.table
          .getCellRenderer(row, column);
      boolean isSubstanceRenderer = isSubstanceDefaultRenderer(renderer);
      rendererComponent = this.table.prepareRenderer(renderer, row,
          column);
      boolean isSubstanceRendererComponent = isSubstanceDefaultRenderer(rendererComponent);
      if (isSubstanceRenderer && !isSubstanceRendererComponent) {
        throw new IllegalArgumentException(
            "Renderer extends the SubstanceDefaultTableCellRenderer but does not return one in its getTableCellRendererComponent() method");
      }

      if (!isSubstanceRenderer) {
        rendererPane.paintComponent(g, rendererComponent, table,
            cellRect.x, cellRect.y, cellRect.width,
            cellRect.height, true);
        return;
      }
    }

    Graphics2D g2d = (Graphics2D) g.create();
    // fix for issue 183 - passing the original Graphics context
    // to compute the alpha composite. If the table is in a JXPanel
    // (component from SwingX) and it has custom alpha value set,
    // then the original graphics context will have a SRC_OVER
    // alpha composite applied to it.
    g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.table, g));

    TableCellId cellId = new TableCellId(row, column);

    StateTransitionTracker.ModelStateInfo modelStateInfo = this
        .getModelStateInfo(cellId);
    Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
        : modelStateInfo.getStateContributionMap());
    // optimize for tables that don't initiate rollover
    // or selection animations
    if (!updateInfo.hasRolloverAnimations
        && !updateInfo.hasSelectionAnimations)
      activeStates = null;
    ComponentState currState = ((modelStateInfo == null) ? this
        .getCellState(cellId) : modelStateInfo.getCurrModelState());

    boolean hasHighlights = (currState != ComponentState.ENABLED)
        || (activeStates != null);
    if (activeStates != null) {
      for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
          .entrySet()) {
        hasHighlights = (this.updateInfo.getHighlightAlpha(stateEntry
            .getKey()) * stateEntry.getValue().getContribution() > 0.0f);
        if (hasHighlights)
          break;
      }
    } else {
      hasHighlights = (this.updateInfo.getHighlightAlpha(currState) > 0.0f);
    }

    Set<SubstanceConstants.Side> highlightOpenSides = null;
    float highlightBorderAlpha = 0.0f;

    if (hasHighlights) {
      // compute the highlight visuals, but only if there are
      // highlights on this cell (optimization)
      highlightOpenSides = EnumSet.noneOf(Side.class);
      // show highlight border only when the table grid is not shown
      highlightBorderAlpha = (table.getShowHorizontalLines() || table
          .getShowVerticalLines()) ? 0.0f : 0.8f;
      if (!table.getColumnSelectionAllowed()
          && table.getRowSelectionAllowed()) {
        // if row selection is on and column selection is off, we
        // will show the highlight for the entire row

        // all cells have open left side
        highlightOpenSides.add(SubstanceConstants.Side.LEFT);
        // all cells have open right side
        highlightOpenSides.add(SubstanceConstants.Side.RIGHT);
      }
      if (table.getColumnSelectionAllowed()
          && !table.getRowSelectionAllowed()) {
        // if row selection is off and column selection is on, we
        // will show the highlight for the entire column

        // the top side is open for all rows except the
        // first, or when the table header is visible
        highlightOpenSides.add(SubstanceConstants.Side.TOP);
        // all cells but the last have open bottom side
        highlightOpenSides.add(SubstanceConstants.Side.BOTTOM);
      }
      if (row > 1) {
        ComponentState upperNeighbourState = this
            .getCellState(new TableCellId(row - 1, column));
        if (currState == upperNeighbourState) {
          // the cell above it is in the same state
          highlightOpenSides.add(SubstanceConstants.Side.TOP);
        }
      }
      if (column > 1) {
        ComponentState leftNeighbourState = this
            .getCellState(new TableCellId(row, column - 1));
        if (currState == leftNeighbourState) {
          // the cell to the left is in the same state
          highlightOpenSides.add(SubstanceConstants.Side.LEFT);
        }
      }
      if (row == 0) {
        highlightOpenSides.add(SubstanceConstants.Side.TOP);
      }
      if (row == (table.getRowCount() - 1)) {
        highlightOpenSides.add(SubstanceConstants.Side.BOTTOM);
      }
      if (column == 0) {
        highlightOpenSides.add(SubstanceConstants.Side.LEFT);
      }
      if (column == (table.getColumnCount() - 1)) {
        highlightOpenSides.add(SubstanceConstants.Side.RIGHT);
      }
    }

    boolean isRollover = this.rolledOverIndices.contains(cellId);
    if (this.table.isEditing() && this.table.getEditingRow() == row
        && this.table.getEditingColumn() == column) {
      Component component = this.table.getEditorComponent();
      component.applyComponentOrientation(this.table
          .getComponentOrientation());

      if (hasHighlights) {
        float extra = SubstanceSizeUtils
            .getBorderStrokeWidth(SubstanceSizeUtils
                .getComponentFontSize(this.table
                    .getTableHeader()));
        float extraWidth = highlightOpenSides
            .contains(SubstanceConstants.Side.LEFT) ? 0.0f : extra;
        float extraHeight = highlightOpenSides
            .contains(SubstanceConstants.Side.TOP) ? 0.0f : extra;
        Rectangle highlightRect = new Rectangle(highlightCellRect.x
            - (int) extraWidth, highlightCellRect.y
            - (int) extraHeight, highlightCellRect.width
            + (int) extraWidth, highlightCellRect.height
            + (int) extraHeight);
        if (activeStates == null) {
          float alpha = this.updateInfo.getHighlightAlpha(currState);
          if (alpha > 0.0f) {
            SubstanceColorScheme fillScheme = this.updateInfo
                .getHighlightColorScheme(currState);
            SubstanceColorScheme borderScheme = this.updateInfo
                .getHighlightBorderColorScheme(currState);
            g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
                this.table, alpha, g));
            HighlightPainterUtils.paintHighlight(g2d,
                this.rendererPane, component, highlightRect,
                highlightBorderAlpha, highlightOpenSides,
                fillScheme, borderScheme);
            g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
                this.table, g));
          }
        } else {
          for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
              .entrySet()) {
            ComponentState activeState = stateEntry.getKey();
            float alpha = this.updateInfo
                .getHighlightAlpha(activeState)
                * stateEntry.getValue().getContribution();
            if (alpha == 0.0f)
              continue;
            SubstanceColorScheme fillScheme = this.updateInfo
                .getHighlightColorScheme(activeState);
            SubstanceColorScheme borderScheme = this.updateInfo
                .getHighlightBorderColorScheme(activeState);
            g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
                this.table, alpha, g));
            HighlightPainterUtils.paintHighlight(g2d,
                this.rendererPane, component, highlightRect,
                highlightBorderAlpha, highlightOpenSides,
                fillScheme, borderScheme);
            g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
                this.table, g));
          }
        }
      }

      component.setBounds(cellRect);
      component.validate();
    } else {
      boolean isWatermarkBleed = this.updateInfo.toDrawWatermark;
      if (rendererComponent != null) {
        if (!isWatermarkBleed) {
          Color background = rendererComponent.getBackground();
          // optimization - only render background if it's different
          // from the table background
          if ((background != null)
              && (!table.getBackground().equals(background) || this.updateInfo.isInDecorationArea)) {
            // fill with the renderer background color
            g2d.setColor(background);
            g2d.fillRect(highlightCellRect.x, highlightCellRect.y,
                highlightCellRect.width,
                highlightCellRect.height);
          }
        } else {
          BackgroundPaintingUtils.fillAndWatermark(g2d, this.table,
              rendererComponent.getBackground(),
              highlightCellRect);
        }
      }

      if (hasHighlights) {
        JTable.DropLocation dropLocation = table.getDropLocation();
        if (dropLocation != null && !dropLocation.isInsertRow()
            && !dropLocation.isInsertColumn()
            && dropLocation.getRow() == row
            && dropLocation.getColumn() == column) {
          // mark drop location
          SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
              .getColorScheme(table,
                  ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
                  currState);
          SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
              .getColorScheme(table,
                  ColorSchemeAssociationKind.BORDER,
                  currState);
          float extra = SubstanceSizeUtils
              .getBorderStrokeWidth(SubstanceSizeUtils
                  .getComponentFontSize(this.table
                      .getTableHeader()));
          HighlightPainterUtils.paintHighlight(g2d,
              this.rendererPane, rendererComponent,
              new Rectangle(highlightCellRect.x - (int) extra,
                  highlightCellRect.y - (int) extra,
                  highlightCellRect.width + (int) extra,
                  highlightCellRect.height + (int) extra),
              0.8f, null, scheme, borderScheme);
        } else {
          float extra = SubstanceSizeUtils
              .getBorderStrokeWidth(SubstanceSizeUtils
                  .getComponentFontSize(this.table
                      .getTableHeader()));
          float extraWidth = highlightOpenSides
              .contains(SubstanceConstants.Side.LEFT) ? 0.0f
              : extra;
          float extraHeight = highlightOpenSides
              .contains(SubstanceConstants.Side.TOP) ? 0.0f
              : extra;
          Rectangle highlightRect = new Rectangle(highlightCellRect.x
              - (int) extraWidth, highlightCellRect.y
              - (int) extraHeight, highlightCellRect.width
              + (int) extraWidth, highlightCellRect.height
              + (int) extraHeight);
          if (activeStates == null) {
            SubstanceColorScheme fillScheme = this.updateInfo
                .getHighlightColorScheme(currState);
            SubstanceColorScheme borderScheme = this.updateInfo
                .getHighlightBorderColorScheme(currState);
            float alpha = this.updateInfo
                .getHighlightAlpha(currState);
            if (alpha > 0.0f) {
              g2d.setComposite(LafWidgetUtilities
                  .getAlphaComposite(this.table, alpha, g));
              HighlightPainterUtils.paintHighlight(g2d,
                  this.rendererPane, rendererComponent,
                  highlightRect, highlightBorderAlpha,
                  highlightOpenSides, fillScheme,
                  borderScheme);
              g2d.setComposite(LafWidgetUtilities
                  .getAlphaComposite(this.table, g));
            }
          } else {
            for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
                .entrySet()) {
              ComponentState activeState = stateEntry.getKey();
              SubstanceColorScheme fillScheme = this.updateInfo
                  .getHighlightColorScheme(activeState);
              SubstanceColorScheme borderScheme = this.updateInfo
                  .getHighlightBorderColorScheme(activeState);
              float alpha = this.updateInfo
                  .getHighlightAlpha(activeState)
                  * stateEntry.getValue().getContribution();
              if (alpha > 0.0f) {
                g2d.setComposite(LafWidgetUtilities
                    .getAlphaComposite(this.table, alpha, g));
                HighlightPainterUtils.paintHighlight(g2d,
                    this.rendererPane, rendererComponent,
                    highlightRect, highlightBorderAlpha,
                    highlightOpenSides, fillScheme,
                    borderScheme);
                g2d.setComposite(LafWidgetUtilities
                    .getAlphaComposite(this.table, g));
              }
            }
          }
        }
      }

      rendererComponent.applyComponentOrientation(this.table
          .getComponentOrientation());
      if (rendererComponent instanceof JComponent) {
        // Play with opacity to make our own gradient background
        // on selected elements to show.
        JComponent jRenderer = (JComponent) rendererComponent;
        // Compute the selection status to prevent flicker - JTable
        // registers a listener on selection changes and repaints
        // the relevant cell before our listener (in TableUI) gets
        // the chance to start the fade sequence. The result is that
        // the first frame uses full opacity, and the next frame
        // starts the fade sequence. So, we use the UI delegate to
        // compute the selection status.
        boolean isSelected = updateInfo.hasSelectionAnimations ? this.selectedIndices
            .containsKey(cellId) : this.table.isCellSelected(row,
            column);
        boolean newOpaque = !(isSelected || isRollover || hasHighlights);

        if (this.updateInfo.toDrawWatermark)
          newOpaque = false;

        Map<Component, Boolean> opacity = new HashMap<Component, Boolean>();
        if (!newOpaque)
          SubstanceCoreUtilities.makeNonOpaque(jRenderer, opacity);
        this.rendererPane.paintComponent(g2d, rendererComponent,
            this.table, cellRect.x, cellRect.y, cellRect.width,
            cellRect.height, true);
        if (!newOpaque)
          SubstanceCoreUtilities.restoreOpaque(jRenderer, opacity);
      } else {
        this.rendererPane.paintComponent(g2d, rendererComponent,
            this.table, cellRect.x, cellRect.y, cellRect.width,
            cellRect.height, true);
      }
    }
    g2d.dispose();
  }

  protected void paintDropLines(Graphics g) {
    JTable.DropLocation loc = table.getDropLocation();
    if (loc == null) {
      return;
    }

    Color color = UIManager.getColor("Table.dropLineColor");
    Color shortColor = UIManager.getColor("Table.dropLineShortColor");
    if (color == null && shortColor == null) {
      return;
    }

    Rectangle rect;

    rect = getHDropLineRect(loc);
    if (rect != null) {
      int x = rect.x;
      int w = rect.width;
      if (color != null) {
        extendRect(rect, true);
        g.setColor(color);
        g.fillRect(rect.x, rect.y, rect.width, rect.height);
      }
      if (!loc.isInsertColumn() && shortColor != null) {
        g.setColor(shortColor);
        g.fillRect(x, rect.y, w, rect.height);
      }
    }

    rect = getVDropLineRect(loc);
    if (rect != null) {
      int y = rect.y;
      int h = rect.height;
      if (color != null) {
        extendRect(rect, false);
        g.setColor(color);
        g.fillRect(rect.x, rect.y, rect.width, rect.height);
      }
      if (!loc.isInsertRow() && shortColor != null) {
        g.setColor(shortColor);
        g.fillRect(rect.x, y, rect.width, h);
      }
    }
  }

  private Rectangle getHDropLineRect(JTable.DropLocation loc) {
    if (!loc.isInsertRow()) {
      return null;
    }

    int row = loc.getRow();
    int col = loc.getColumn();
    if (col >= table.getColumnCount()) {
      col--;
    }

    Rectangle rect = table.getCellRect(row, col, true);

    if (row >= table.getRowCount()) {
      row--;
      Rectangle prevRect = table.getCellRect(row, col, true);
      rect.y = prevRect.y + prevRect.height;
    }

    if (rect.y == 0) {
      rect.y = -1;
    } else {
      rect.y -= 2;
    }

    rect.height = 3;

    return rect;
  }

  private Rectangle getVDropLineRect(JTable.DropLocation loc) {
    if (!loc.isInsertColumn()) {
      return null;
    }

    boolean ltr = table.getComponentOrientation().isLeftToRight();
    int col = loc.getColumn();
    Rectangle rect = table.getCellRect(loc.getRow(), col, true);

    if (col >= table.getColumnCount()) {
      col--;
      rect = table.getCellRect(loc.getRow(), col, true);
      if (ltr) {
        rect.x = rect.x + rect.width;
      }
    } else if (!ltr) {
      rect.x = rect.x + rect.width;
    }

    if (rect.x == 0) {
      rect.x = -1;
    } else {
      rect.x -= 2;
    }

    rect.width = 3;

    return rect;
  }

  private Rectangle extendRect(Rectangle rect, boolean horizontal) {
    if (rect == null) {
      return rect;
    }

    if (horizontal) {
      rect.x = 0;
      rect.width = table.getWidth();
    } else {
      rect.y = 0;

      if (table.getRowCount() != 0) {
        Rectangle lastRect = table.getCellRect(table.getRowCount() - 1,
            0, true);
        rect.height = lastRect.y + lastRect.height;
      } else {
        rect.height = table.getHeight();
      }
    }

    return rect;
  }

  /**
   * Repaints a single cell during the fade animation cycle.
   *
   * @author Kirill Grouchnikov
   */
  protected class CellRepaintCallback extends UIThreadTimelineCallbackAdapter {
    /**
     * Associated table.
     */
    protected JTable table;

    /**
     * Associated (animated) row index.
     */
    protected int rowIndex;

    /**
     * Associated (animated) column index.
     */
    protected int columnIndex;

    /**
     * Creates a new animation repaint callback.
     *
     * @param table
     *            Associated table.
     * @param rowIndex
     *            Associated (animated) row index.
     * @param columnIndex
     *            Associated (animated) column index.
     */
    public CellRepaintCallback(JTable table, int rowIndex, int columnIndex) {
      super();
      this.table = table;
      this.rowIndex = rowIndex;
      this.columnIndex = columnIndex;
    }

    @Override
    public void onTimelinePulse(float durationFraction,
        float timelinePosition) {
      this.repaintCell();
    }

    @Override
    public void onTimelineStateChanged(TimelineState oldState,
        TimelineState newState, float durationFraction,
        float timelinePosition) {
      this.repaintCell();
    }

    /**
     * Repaints the associated cell.
     */
    private void repaintCell() {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          if (SubstanceTableUI.this.table == null) {
            // may happen if the LAF was switched in the meantime
            return;
          }
          int rowCount = table.getRowCount();
          int colCount = table.getColumnCount();
          if ((rowCount > 0) && (rowIndex < rowCount)
              && (colCount > 0) && (columnIndex < colCount)) {
            // need to retrieve the cell rectangle since the cells
            // can be moved while animating
            Rectangle rect = getCellRectangleForRepaint(rowIndex,
                columnIndex);
            // System.out.println("Cell Repainting " + rowIndex +
            // ":"
            // + columnIndex + ":" + rect);
            CellRepaintCallback.this.table.repaint(rect);
          }
        }
      });
    }
  }

  /**
   * Repaints a single row during the fade animation cycle.
   *
   * @author Kirill Grouchnikov
   */
  protected class RowRepaintCallback extends UIThreadTimelineCallbackAdapter {
    /**
     * Associated table.
     */
    protected JTable table;

    /**
     * Associated (animated) row index.
     */
    protected int rowIndex;

    /**
     * Creates a new animation repaint callback.
     *
     * @param table
     *            Associated table.
     * @param rowIndex
     *            Associated (animated) row index.
     */
    public RowRepaintCallback(JTable table, int rowIndex) {
      super();
      this.table = table;
      this.rowIndex = rowIndex;
    }

    @Override
    public void onTimelinePulse(float durationFraction,
        float timelinePosition) {
      this.repaintRow();
    }

    @Override
    public void onTimelineStateChanged(TimelineState oldState,
        TimelineState newState, float durationFraction,
        float timelinePosition) {
      this.repaintRow();
    }

    /**
     * Repaints the associated row.
     */
    private void repaintRow() {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          if (SubstanceTableUI.this.table == null) {
            // may happen if the LAF was switched in the meantime
            return;
          }
          int rowCount = RowRepaintCallback.this.table.getRowCount();
          if ((rowCount > 0)
              && (RowRepaintCallback.this.rowIndex < rowCount)) {
            // need to retrieve the cell rectangle since the cells
            // can be moved while animating
            Rectangle rect = RowRepaintCallback.this.table
                .getCellRect(RowRepaintCallback.this.rowIndex,
                    0, true);
            for (int i = 1; i < RowRepaintCallback.this.table
                .getColumnCount(); i++) {
              rect = rect.union(RowRepaintCallback.this.table
                  .getCellRect(
                      RowRepaintCallback.this.rowIndex,
                      i, true));
            }
            if (!table.getShowHorizontalLines()
                && !table.getShowVerticalLines()) {
              float extra = SubstanceSizeUtils
                  .getBorderStrokeWidth(SubstanceSizeUtils
                      .getComponentFontSize(table
                          .getTableHeader()));
              rect.y -= (int) extra;
              rect.height += 2 * (int) extra;
            }
            // System.out.println("Repainting row " + rowIndex
            // + " at " + rect);
            RowRepaintCallback.this.table.repaint(rect);
          }
        }
      });
    }
  }

  /**
   * Repaints a single column during the fade animation cycle.
   *
   * @author Kirill Grouchnikov
   */
  protected class ColumnRepaintCallback extends
      UIThreadTimelineCallbackAdapter {
    /**
     * Associated table.
     */
    protected JTable table;

    /**
     * Associated (animated) column index.
     */
    protected int columnIndex;

    /**
     * Creates a new animation repaint callback.
     *
     * @param table
     *            Associated table.
     * @param columnIndex
     *            Associated (animated) column index.
     */
    public ColumnRepaintCallback(JTable table, int columnIndex) {
      super();
      this.table = table;
      this.columnIndex = columnIndex;
    }

    @Override
    public void onTimelinePulse(float durationFraction,
        float timelinePosition) {
      this.repaintColumn();
    }

    @Override
    public void onTimelineStateChanged(TimelineState oldState,
        TimelineState newState, float durationFraction,
        float timelinePosition) {
      this.repaintColumn();
    }

    /**
     * Repaints the associated row.
     */
    private void repaintColumn() {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          if (SubstanceTableUI.this.table == null) {
            // may happen if the LAF was switched in the meantime
            return;
          }
          int columnCount = ColumnRepaintCallback.this.table
              .getColumnCount();
          if ((columnCount > 0)
              && (ColumnRepaintCallback.this.columnIndex < columnCount)) {
            // need to retrieve the cell rectangle since the cells
            // can be moved while animating
            Rectangle rect = ColumnRepaintCallback.this.table
                .getCellRect(0,
                    ColumnRepaintCallback.this.columnIndex,
                    true);
            for (int i = 1; i < ColumnRepaintCallback.this.table
                .getRowCount(); i++) {
              rect = rect.union(ColumnRepaintCallback.this.table
                  .getCellRect(
                      i,
                      ColumnRepaintCallback.this.columnIndex,
                      true));
            }
            if (!table.getShowHorizontalLines()
                && !table.getShowVerticalLines()) {
              float extra = SubstanceSizeUtils
                  .getBorderStrokeWidth(SubstanceSizeUtils
                      .getComponentFontSize(table
                          .getTableHeader()));
              rect.x -= (int) extra;
              rect.width += 2 * (int) extra;
            }
            ColumnRepaintCallback.this.table.repaint(rect);
          }
        }
      });
    }
  }

  /**
   * ID of a single table cell.
   *
   * @author Kirill Grouchnikov
   */
  public static class TableCellId implements Comparable<TableCellId> {
    /**
     * Cell row.
     */
    protected int row;

    /**
     * Cell column.
     */
    protected int column;

    /**
     * Creates a new cell ID.
     *
     * @param row
     *            Cell row.
     * @param column
     *            Cell column.
     */
    public TableCellId(int row, int column) {
      this.row = row;
      this.column = column;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    @Override
    public int compareTo(TableCellId o) {
      if ((this.row == o.row) && (this.column == o.column))
        return 0;
      return 1;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
      if (obj instanceof TableCellId) {
        return this.compareTo((TableCellId) obj) == 0;
      }
      return false;
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
      return (this.row ^ (this.row >>> 32))
          & (this.column ^ (this.column >>> 32));
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
      return "Row " + this.row + ", Column " + this.column;
    }
  }

  /**
   * State listener for tracking the selection changes.
   *
   * @author Kirill Grouchnikov
   */
  protected class TableStateListener implements ListSelectionListener,
      TableModelListener, RowSorterListener {
    List<SortKey> oldSortKeys = null;

    private boolean isSameSorter(List<? extends SortKey> sortKeys1,
        List<? extends SortKey> sortKeys2) {
      int size1 = (sortKeys1 == null) ? 0 : sortKeys1.size();
      int size2 = (sortKeys2 == null) ? 0 : sortKeys2.size();
      if ((size1 == 0) && (size2 == 0)) {
        return true;
      }
      if ((sortKeys1 == null) && (sortKeys2 == null))
        return true;
      if ((sortKeys1 == null) || (sortKeys2 == null))
        return false;
      if (size1 != size2)
        return false;
      for (int i = 0; i < size1; i++) {
        SortKey sortKey1 = sortKeys1.get(i);
        SortKey sortKey2 = sortKeys2.get(i);
        if ((sortKey1.getColumn() != sortKey2.getColumn())
            || (sortKey1.getSortOrder() != sortKey2.getSortOrder())) {
          return false;
        }
      }
      return true;
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * javax.swing.event.ListSelectionListener#valueChanged(javax.swing.
     * event.ListSelectionEvent)
     */
    @SuppressWarnings("unchecked")
    public void valueChanged(final ListSelectionEvent e) {
      // fix for issue 478 - no animations when sorter has changed
      List<? extends SortKey> sortKeys = (table.getRowSorter() == null) ? null
          : table.getRowSorter().getSortKeys();
      boolean isDifferentSorter = !isSameSorter(sortKeys, oldSortKeys);
      if (e.getValueIsAdjusting() && isDifferentSorter)
        return;
      if (sortKeys == null) {
        oldSortKeys = null;
      } else {
        oldSortKeys = new ArrayList<SortKey>();
        for (SortKey sortKey : sortKeys) {
          SortKey copy = new SortKey(sortKey.getColumn(),
              sortKey.getSortOrder());
          oldSortKeys.add(copy);
        }
      }
      syncSelection(isDifferentSorter);
    }

    /*
     * (non-Javadoc)
     *
     * @see
     * javax.swing.event.TableModelListener#tableChanged(javax.swing.event
     * .TableModelEvent)
     */
    public void tableChanged(final TableModelEvent e) {
      // fix for defect 291 - tracking changes to the table.
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          // fix for defect 350 - font might have been
          // switched in the middle of update
          if (table == null)
            return;

          // fix for defect 328 - do not clear the
          // internal selection and focus tracking
          // when the event is table update.
          if (e.getType() != TableModelEvent.UPDATE) {
            selectedIndices.clear();
            stateTransitionMultiTracker.clear();
            focusedCellId = null;
          }
          syncSelection(true);
          table.repaint();
        }
      });
    }

    @Override
    public void sorterChanged(RowSorterEvent e) {
      SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
          // fix for issue 479 - cancel selection animations
          // that are happening due to changes in sorter
          stateTransitionMultiTracker.clear();
        }
      });
    }
  }

  /**
   * Listener for fade animations on table rollovers.
   *
   * @author Kirill Grouchnikov
   */
  private class RolloverFadeListener implements MouseListener,
      MouseMotionListener {
    public void mouseClicked(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mousePressed(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
      // if (SubstanceCoreUtilities.toBleedWatermark(list))
      // return;

      if (table == null)
        return;

      if (!table.isEnabled())
        return;

      // check the mouse location. If the cell editor has been shown
      // or hidden, we will get a mouseExited() event, but shouldn't
      // be changing the rollover indication if the mouse is still
      // over the table
      Point mouseLoc = MouseInfo.getPointerInfo().getLocation();
      Window windowAncestor = SwingUtilities.getWindowAncestor(table);
      SwingUtilities.convertPointFromScreen(mouseLoc, windowAncestor);
      Component deepest = SwingUtilities.getDeepestComponentAt(
          windowAncestor, mouseLoc.x, mouseLoc.y);

      while (deepest != null) {
        if (deepest == table) {
          // still in table
          return;
        }
        deepest = deepest.getParent();
      }

      fadeOutAllRollovers();
      this.fadeOutTableHeader();
      rolledOverIndices.clear();
      rolledOverColumn = -1;
    }

    public void mouseMoved(MouseEvent e) {
      if (!SubstanceTableUI.this.table.isEnabled())
        return;
      handleMouseMove(e.getPoint());
      this.handleMoveForHeader(e);
    }

    public void mouseDragged(MouseEvent e) {
      if (!SubstanceTableUI.this.table.isEnabled())
        return;
      handleMouseMove(e.getPoint());
      this.handleMoveForHeader(e);
    }

    /**
     * Handles various mouse move events and initiates the fade animation if
     * necessary.
     *
     * @param e
     *            Mouse event.
     */
    private void handleMoveForHeader(MouseEvent e) {
      if (!SubstanceTableUI.this.table.getColumnSelectionAllowed())
        return;
      JTableHeader header = SubstanceTableUI.this.table.getTableHeader();
      if ((header == null) || (!header.isVisible()))
        return;

      TableHeaderUI ui = header.getUI();
      if (!(ui instanceof SubstanceTableHeaderUI))
        return;

      SubstanceTableHeaderUI sthui = (SubstanceTableHeaderUI) ui;

      // synchronized (SubstanceTableUI.this.table) {
      int row = SubstanceTableUI.this.table.rowAtPoint(e.getPoint());
      int column = SubstanceTableUI.this.table
          .columnAtPoint(e.getPoint());
      if ((row < 0) || (row >= SubstanceTableUI.this.table.getRowCount())
          || (column < 0)
          || (column >= SubstanceTableUI.this.table.getColumnCount())) {
        this.fadeOutTableHeader();
        // System.out.println("Nulling RO column index");
        SubstanceTableUI.this.rolledOverColumn = -1;
      } else {
        // check if this is the same column
        if (SubstanceTableUI.this.rolledOverColumn == column)
          return;

        this.fadeOutTableHeader();

        TableColumnModel columnModel = header.getColumnModel();
        StateTransitionTracker columnTransitionTracker = sthui
            .getTracker(column, false,
                columnModel.getColumnSelectionAllowed()
                    && columnModel.getSelectionModel()
                        .isSelectedIndex(column));
        columnTransitionTracker.getModel().setRollover(true);

        SubstanceTableUI.this.rolledOverColumn = column;
      }
      // }
    }

    /**
     * Initiates the fade out effect.
     */
    private void fadeOutTableHeader() {
      if (SubstanceTableUI.this.rolledOverColumn >= 0) {
        JTableHeader header = SubstanceTableUI.this.table
            .getTableHeader();
        if ((header == null) || (!header.isVisible()))
          return;
        SubstanceTableHeaderUI ui = (SubstanceTableHeaderUI) header
            .getUI();

        TableColumnModel columnModel = header.getColumnModel();
        StateTransitionTracker columnTransitionTracker = ui.getTracker(
            rolledOverColumn, true,
            columnModel.getColumnSelectionAllowed()
                && columnModel.getSelectionModel()
                    .isSelectedIndex(rolledOverColumn));
        columnTransitionTracker.getModel().setRollover(false);
      }
    }

    /**
     * Handles various mouse move events and initiates the fade animation if
     * necessary.
     *
     * @param e
     *            Mouse event.
     */
    private void handleMouseMove(Point mousePoint) {
      // synchronized (SubstanceTableUI.this.table) {
      int row = table.rowAtPoint(mousePoint);
      int column = table.columnAtPoint(mousePoint);
      if ((row < 0) || (row >= table.getRowCount()) || (column < 0)
          || (column >= table.getColumnCount())) {
        this.fadeOutAllRollovers();
        // System.out.println("Nulling RO index in handleMove()");
        // table.putClientProperty(ROLLED_OVER_INDEX, null);
        rolledOverIndices.clear();
      } else {
        // check if this is the same index
        boolean hasRowSelection = table.getRowSelectionAllowed();
        boolean hasColumnSelection = table.getColumnSelectionAllowed();
        int startRolloverRow = row;
        int endRolloverRow = row;
        int startRolloverColumn = column;
        int endRolloverColumn = column;
        if (hasRowSelection && !hasColumnSelection) {
          startRolloverColumn = 0;
          endRolloverColumn = table.getColumnCount() - 1;
        }
        if (!hasRowSelection && hasColumnSelection) {
          startRolloverRow = 0;
          endRolloverRow = table.getRowCount() - 1;
        }
        Set<TableCellId> toRemove = new HashSet<TableCellId>();
        for (TableCellId currRolloverId : rolledOverIndices) {
          if ((currRolloverId.row < startRolloverRow)
              || (currRolloverId.row > endRolloverRow)
              || (currRolloverId.column < startRolloverColumn)
              || (currRolloverId.column > endRolloverColumn)) {
            fadeOutRollover(currRolloverId);
            toRemove.add(currRolloverId);
          }
        }
        for (TableCellId id : toRemove) {
          rolledOverIndices.remove(id);
        }

        int totalRolloverCount = (endRolloverRow - startRolloverRow + 1)
            * (endRolloverColumn - startRolloverColumn + 1);
        if (totalRolloverCount > 20) {
          for (int i = startRolloverRow; i <= endRolloverRow; i++) {
            for (int j = startRolloverColumn; j <= endRolloverColumn; j++) {
              rolledOverIndices.add(new TableCellId(i, j));
            }
          }
          table.repaint();
        } else {
          for (int i = startRolloverRow; i <= endRolloverRow; i++) {
            for (int j = startRolloverColumn; j <= endRolloverColumn; j++) {
              TableCellId currCellId = new TableCellId(i, j);
              if (rolledOverIndices.contains(currCellId))
                continue;
              // System.out
              // .println("Getting rollover/in tracker for "
              // + currCellId);
              StateTransitionTracker tracker = getTracker(
                  currCellId,
                  false,
                  getCellState(currCellId).isFacetActive(
                      ComponentStateFacet.SELECTION));
              tracker.getModel().setRollover(true);

              rolledOverIndices.add(currCellId);
            }
          }
        }
      }
    }

    /**
     * Initiates the fade out effect.
     */
    private void fadeOutRollover(TableCellId tableCellId) {
      if (rolledOverIndices.contains(tableCellId)) {
        // System.out
        // .println("Getting rollover/out tracker for " + cellId);
        StateTransitionTracker tracker = getTracker(
            tableCellId,
            true,
            getCellState(tableCellId).isFacetActive(
                ComponentStateFacet.SELECTION));
        tracker.getModel().setRollover(false);
      }
    }

    private void fadeOutAllRollovers() {
      if (rolledOverIndices.size() < 20) {
        for (TableCellId tcid : rolledOverIndices) {
          fadeOutRollover(tcid);
        }
      }
    }
  }

  /**
   * Returns a comparable ID for the specified location.
   *
   * @param row
   *            Row index.
   * @param column
   *            Column index.
   * @return Comparable ID for the specified location.
   */
  public TableCellId getId(int row, int column) {
    cellId.column = column;
    cellId.row = row;
    return cellId;
  }

  TableCellId cellId;

  /**
   * Synchronizes the current selection state.
   *
   * @param e
   *            Selection event.
   */
  // @SuppressWarnings("unchecked")
  protected void syncSelection(boolean enforceNoAnimations) {
    if (this.table == null) {
      // fix for defect 270 - if the UI delegate is updated
      // by another selection listener, ignore this
      return;
    }

    int rows = this.table.getRowCount();
    int cols = this.table.getColumnCount();

    int rowLeadIndex = this.table.getSelectionModel()
        .getLeadSelectionIndex();
    int colLeadIndex = this.table.getColumnModel().getSelectionModel()
        .getLeadSelectionIndex();
    boolean isFocusOwner = this.table.isFocusOwner();

    // fix for defect 209 - selection very slow on large tables with
    // column selection set to true and row selection set to false.
    // Solution - no selection animations on tables with more than 1000
    // cells.
    if (!this._hasSelectionAnimations()) {
      stateTransitionMultiTracker.clear();
      // this.prevStateMap.clear();
      table.repaint();

      // fix for issue 414 - track focus on tables
      // without selection animations
      if (isFocusOwner) {
        this.focusedCellId = new TableCellId(rowLeadIndex, colLeadIndex);
      }
      return;
    }

    Set<StateTransitionTracker> initiatedTrackers = new HashSet<StateTransitionTracker>();

    for (int i = 0; i < rows; i++) {
      for (int j = 0; j < cols; j++) {
        TableCellId cellId = new TableCellId(i, j);
        if (this.table.isCellSelected(i, j)) {
          // check if was selected before
          if (!this.selectedIndices.containsKey(cellId)) {
            // start fading in
            if (!enforceNoAnimations) {
              // System.out
              // .println("Getting selection/in tracker for "
              // + cellId);
              StateTransitionTracker tracker = getTracker(
                  cellId,
                  getCellState(cellId).isFacetActive(
                      ComponentStateFacet.ROLLOVER),
                  false);
              tracker.getModel().setSelected(true);
              // System.out
              // .println("Selecting previously unselected "
              // + i + ":" + j);
              initiatedTrackers.add(tracker);
              if (initiatedTrackers.size() > 20) {
                stateTransitionMultiTracker.clear();
                initiatedTrackers.clear();
                enforceNoAnimations = true;
              }
            }

            this.selectedIndices.put(cellId,
                this.table.getValueAt(i, j));
          }
        } else {
          // check if was selected before and still points
          // to the same element
          if (this.selectedIndices.containsKey(cellId)) {
            // corner case when the model returns null
            Object oldValue = this.selectedIndices.get(cellId);
            if ((i >= this.table.getModel().getRowCount())
                || (j >= this.table.getModel().getColumnCount())) {
              // not only the content changed, but the model
              // dimensions as well
              continue;
            }
            Object currValue = this.table.getValueAt(i, j);
            boolean isSame = false;
            if (oldValue == null) {
              isSame = (currValue == null);
            } else {
              isSame = oldValue.equals(currValue);
            }
            if (isSame) {
              // start fading out
              if (!enforceNoAnimations) {
                // System.out
                // .println("Getting selection/out tracker for "
                // + cellId);
                StateTransitionTracker tracker = getTracker(
                    cellId,
                    getCellState(cellId).isFacetActive(
                        ComponentStateFacet.ROLLOVER),
                    true);
                tracker.getModel().setSelected(false);
                // System.out
                // .println("Unselecting previously selected "
                // + i + ":" + j);

                initiatedTrackers.add(tracker);
                if (initiatedTrackers.size() > 20) {
                  stateTransitionMultiTracker.clear();
                  initiatedTrackers.clear();
                  enforceNoAnimations = true;
                }
              }
            }
            this.selectedIndices.remove(cellId);
          }
        }

        // handle focus animations
        boolean cellHasFocus = isFocusOwner && (i == rowLeadIndex)
            && (j == colLeadIndex);
        if (cellHasFocus) {
          // check if it's a different cell
          if ((this.focusedCellId == null)
              || !this.focusedCellId.equals(cellId)) {
            if (!enforceNoAnimations) {
              if (this.focusedCellId != null) {
                // fade out the previous focus holder

                ComponentState cellState = getCellState(this.focusedCellId);
                // System.out.println("Getting focus/out tracker for "
                // + cellId);
                StateTransitionTracker tracker = getTracker(
                    this.focusedCellId,
                    cellState
                        .isFacetActive(ComponentStateFacet.ROLLOVER),
                    cellState
                        .isFacetActive(ComponentStateFacet.SELECTION));
                tracker.setFocusState(false);
              }

              // fade in the current cell (new focus holder)
              ComponentState cellState = getCellState(cellId);

              // System.out.println("Getting focus/in tracker for "
              // + currId);
              StateTransitionTracker tracker = getTracker(
                  cellId,
                  cellState
                      .isFacetActive(ComponentStateFacet.ROLLOVER),
                  cellState
                      .isFacetActive(ComponentStateFacet.SELECTION));
              tracker.setFocusState(true);
            }

            if (AnimationConfigurationManager.getInstance()
                .isAnimationAllowed(AnimationFacet.FOCUS,
                    this.table)) {
              // and store it for future checks
              this.focusedCellId = new TableCellId(i, j);
            }
          }
        } else {
          // check if previously it held focus
          if (cellId.equals(this.focusedCellId)) {
            if (!enforceNoAnimations) {
              // fade it out
              ComponentState cellState = getCellState(cellId);
              // System.out.println("Getting focus/out tracker for "
              // + cellId);
              StateTransitionTracker tracker = getTracker(
                  cellId,
                  cellState
                      .isFacetActive(ComponentStateFacet.ROLLOVER),
                  cellState
                      .isFacetActive(ComponentStateFacet.SELECTION));
              tracker.setFocusState(false);
            }

            this.focusedCellId = null;
          }
        }
      }
    }
  }

  /**
   * Returns the current state for the specified cell.
   *
   * @param cellIndex
   *            Cell index.
   * @return The current state for the specified cell.
   */
  public ComponentState getCellState(TableCellId cellIndex) {
    boolean isEnabled = this.table.isEnabled();

    StateTransitionTracker tracker = this.stateTransitionMultiTracker
        .getTracker(cellIndex);
    if (tracker == null) {
      int row = cellIndex.row;
      int column = cellIndex.column;
      TableCellId cellId = this.getId(row, column);
      boolean isRollover = rolledOverIndices.contains(cellId);
      boolean isSelected = false;
      boolean hasSelectionAnimations = (this.updateInfo != null) ? this.updateInfo.hasSelectionAnimations
          : this._hasSelectionAnimations();
      if (hasSelectionAnimations
          && AnimationConfigurationManager
              .getInstance()
              .isAnimationAllowed(AnimationFacet.SELECTION, table))
        isSelected = this.selectedIndices.containsKey(cellId);
      else {
        isSelected = this.table.isCellSelected(row, column);
      }
      return ComponentState.getState(isEnabled, isRollover, isSelected);
    } else {
      ComponentState fromTracker = tracker.getModelStateInfo()
          .getCurrModelState();
      return ComponentState.getState(isEnabled,
          fromTracker.isFacetActive(ComponentStateFacet.ROLLOVER),
          fromTracker.isFacetActive(ComponentStateFacet.SELECTION));
    }
  }

  /**
   * Returns the current state for the specified cell.
   *
   * @param cellId
   *            Cell index.
   * @return The current state for the specified cell.
   */
  public StateTransitionTracker.ModelStateInfo getModelStateInfo(
      TableCellId cellId) {
    if (this.stateTransitionMultiTracker.size() == 0)
      return null;
    StateTransitionTracker tracker = this.stateTransitionMultiTracker
        .getTracker(cellId);
    if (tracker == null) {
      return null;
    } else {
      return tracker.getModelStateInfo();
    }
  }

  /**
   * Checks whether the table has animations.
   *
   * @return <code>true</code> if the table has animations, <code>false</code>
   *         otherwise.
   */
  protected boolean _hasAnimations() {
    // fix for defects 164 and 209 - selection
    // and deletion are very slow on large tables.
    int rowCount = this.table.getRowCount();
    int colCount = this.table.getColumnCount();
    if (rowCount * colCount >= 500)
      return false;
    if (this.table.getColumnSelectionAllowed()
        && !this.table.getRowSelectionAllowed()) {
      if (!this.table.getShowHorizontalLines()
          && !this.table.getShowVerticalLines())
        return rowCount <= 10;
      return rowCount <= 25;
    }
    if (!this.table.getColumnSelectionAllowed()
        && this.table.getRowSelectionAllowed()) {
      if (!this.table.getShowHorizontalLines()
          && !this.table.getShowVerticalLines())
        return colCount <= 10;
      return colCount <= 25;
    }
    return true;
  }

  /**
   * Checks whether the table has selection animations.
   *
   * @return <code>true</code> if the table has selection animations,
   *         <code>false</code> otherwise.
   */
  protected boolean _hasSelectionAnimations() {
    return this._hasAnimations()
        && !LafWidgetUtilities.hasNoAnimations(this.table,
            AnimationFacet.SELECTION);
  }

  /**
   * Checks whether the table has rollover animations.
   *
   * @return <code>true</code> if the table has rollover animations,
   *         <code>false</code> otherwise.
   */
  protected boolean _hasRolloverAnimations() {
    return this._hasAnimations()
        && !LafWidgetUtilities.hasNoAnimations(this.table,
            AnimationFacet.ROLLOVER);
  }

  /**
   * Returns the index of the rollover column.
   *
   * @return The index of the rollover column.
   */
  public int getRolloverColumnIndex() {
    return this.rolledOverColumn;
  }

  /**
   * Returns indication whether the specified cell has focus.
   *
   * @param row
   *            Cell row index.
   * @param column
   *            Cell column index.
   * @return <code>true</code> If the focus is on the specified cell,
   *         <code>false</code> otherwise.
   */
  public boolean isFocusedCell(int row, int column) {
    return (this.focusedCellId != null) && (this.focusedCellId.row == row)
        && (this.focusedCellId.column == column);
  }

  /*
   * (non-Javadoc)
   *
   * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
   * javax.swing.JComponent)
   */
  @Override
  public void update(Graphics g, JComponent c) {
    BackgroundPaintingUtils.updateIfOpaque(g, c);
    Graphics2D g2d = (Graphics2D) g.create();
    SubstanceStripingUtils.setup(c);
    this.updateInfo = new TableUpdateOptimizationInfo();
    this.paint(g2d, c);
    SubstanceStripingUtils.tearDown(c);
    g2d.dispose();
    this.updateInfo = null;
  }

  /**
   * Returns the cell renderer insets of this table. Is for internal use only.
   *
   * @return The cell renderer insets of this table.
   */
  public Insets getCellRendererInsets() {
    return this.cellRendererInsets;
  }

  // public SubstanceColorScheme getDefaultColorScheme() {
  // if (this.updateInfo != null)
  // return this.updateInfo.defaultScheme;
  // return null;
  // }
  //
  // public SubstanceColorScheme getHighlightColorScheme(ComponentState state)
  // {
  // if (this.updateInfo != null)
  // return updateInfo.getHighlightColorScheme(state);
  // return null;
  // }

  public boolean hasSelectionAnimations() {
    if (this.updateInfo != null)
      return this.updateInfo.hasSelectionAnimations;
    return this._hasSelectionAnimations();
  }

  public boolean hasRolloverAnimations() {
    if (this.updateInfo != null)
      return this.updateInfo.hasRolloverAnimations;
    return this._hasRolloverAnimations();
  }

  private TableUpdateOptimizationInfo updateInfo;

  private class TableUpdateOptimizationInfo extends UpdateOptimizationInfo {
    public boolean hasSelectionAnimations;

    public boolean hasRolloverAnimations;

    public TableUpdateOptimizationInfo() {
      super(table);
      this.hasSelectionAnimations = _hasSelectionAnimations();
      this.hasRolloverAnimations = _hasRolloverAnimations();
    }
  }

  @Override
  public UpdateOptimizationInfo getUpdateOptimizationInfo() {
    return this.updateInfo;
  }

  private boolean isSubstanceDefaultRenderer(Object instance) {
    return (instance instanceof SubstanceDefaultTableCellRenderer)
        || (instance instanceof SubstanceDefaultTableCellRenderer.BooleanRenderer);
  }

  private boolean isSubstanceDefaultEditor(TableCellEditor editor) {
    return (editor instanceof BooleanEditor);
  }

  private Rectangle getCellRectangleForRepaint(int row, int column) {
    Rectangle rect = this.table.getCellRect(row, column, true);

    if (!table.getShowHorizontalLines() && !table.getShowVerticalLines()) {
      float extra = SubstanceSizeUtils
          .getBorderStrokeWidth(SubstanceSizeUtils
              .getComponentFontSize(table.getTableHeader()));
      rect.x -= (int) extra;
      rect.width += 2 * (int) extra;
      rect.y -= (int) extra;
      rect.height += 2 * (int) extra;
    }
    return rect;
  }

  private StateTransitionTracker getTracker(final TableCellId tableCellId,
      boolean initialRollover, boolean initialSelected) {
    StateTransitionTracker tracker = stateTransitionMultiTracker
        .getTracker(tableCellId);
    // System.out.println("TableID " + tableCellId + " has tracker "
    // + ((tracker == null) ? "null" : ("@" + tracker.hashCode())));
    if (tracker == null) {
      ButtonModel model = new DefaultButtonModel();
      model.setSelected(initialSelected);
      model.setRollover(initialRollover);
      tracker = new StateTransitionTracker(table, model);
      tracker.registerModelListeners();
      tracker.setRepaintCallback(new RepaintCallback() {
        @Override
        public TimelineCallback getRepaintCallback() {
          return new CellRepaintCallback(table, tableCellId.row,
              tableCellId.column);
        }
      });
      tracker.setName("row " + tableCellId.row + ", col "
          + tableCellId.column);
      // System.out.println("TableID " + tableCellId +
      // " has new tracker @"
      // + tracker.hashCode());
      stateTransitionMultiTracker.addTracker(tableCellId, tracker);
    }
    return tracker;
  }

  public StateTransitionTracker getStateTransitionTracker(TableCellId tableId) {
    return this.stateTransitionMultiTracker.getTracker(tableId);
  }
}
TOP

Related Classes of org.pushingpixels.substance.internal.ui.SubstanceTableUI$TableUpdateOptimizationInfo

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.