/*
* Copyright (c) 2005-2016 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.BasicStroke;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Window;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Date;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.DefaultCellEditor;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JViewport;
import javax.swing.ListSelectionModel;
import javax.swing.RowSorter;
import javax.swing.RowSorter.SortKey;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.RowSorterEvent;
import javax.swing.event.RowSorterListener;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.TableHeaderUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableUI;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
import org.pushingpixels.lafwidget.LafWidgetUtilities;
import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
import org.pushingpixels.lafwidget.animation.AnimationFacet;
import org.pushingpixels.lafwidget.utils.RenderingUtils;
import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
import org.pushingpixels.substance.api.ComponentState;
import org.pushingpixels.substance.api.ComponentStateFacet;
import org.pushingpixels.substance.api.SubstanceColorScheme;
import org.pushingpixels.substance.api.SubstanceConstants;
import org.pushingpixels.substance.api.SubstanceConstants.Side;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
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.painter.BackgroundPaintingUtils;
import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
import org.pushingpixels.substance.internal.utils.SubstanceColorResource;
import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
import org.pushingpixels.substance.internal.utils.SubstanceStripingUtils;
import org.pushingpixels.substance.internal.utils.UpdateOptimizationAware;
import org.pushingpixels.substance.internal.utils.UpdateOptimizationInfo;
import org.pushingpixels.trident.Timeline.TimelineState;
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 = (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(() -> {
// 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();
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();
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.HIGHLIGHT_TEXT,
currState);
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
.getColorScheme(table,
ColorSchemeAssociationKind.HIGHLIGHT_BORDER,
currState);
float extra = SubstanceSizeUtils.getBorderStrokeWidth();
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();
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(() -> {
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();
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();
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 (row == o.row) {
return Integer.compare(column, o.column);
} else {
return Integer.compare(row, o.row);
}
}
/*
* (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 << 16))
& (this.column ^ (this.column << 16));
}
/*
* (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)
*/
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(() -> {
// 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) {
// fix for issue 479 - cancel selection animations
// that are happening due to changes in sorter
SwingUtilities.invokeLater(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();
RenderingUtils.installDesktopHints(g2d, c);
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();
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 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);
}
}