2
* Copyright (c) 2005-2010 Substance Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Substance Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.substance.internal.ui;
33
import java.awt.event.*;
34
import java.beans.PropertyChangeEvent;
35
import java.beans.PropertyChangeListener;
37
import java.util.List;
40
import javax.swing.RowSorter.SortKey;
41
import javax.swing.border.Border;
42
import javax.swing.event.*;
43
import javax.swing.plaf.*;
44
import javax.swing.plaf.basic.BasicTableUI;
45
import javax.swing.table.*;
47
import org.pushingpixels.lafwidget.LafWidgetUtilities;
48
import org.pushingpixels.lafwidget.animation.AnimationConfigurationManager;
49
import org.pushingpixels.lafwidget.animation.AnimationFacet;
50
import org.pushingpixels.substance.api.*;
51
import org.pushingpixels.substance.api.SubstanceConstants.Side;
52
import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableCellRenderer;
53
import org.pushingpixels.substance.api.renderers.SubstanceDefaultTableHeaderCellRenderer;
54
import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker;
55
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
56
import org.pushingpixels.substance.internal.animation.StateTransitionTracker.RepaintCallback;
57
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
58
import org.pushingpixels.substance.internal.painter.HighlightPainterUtils;
59
import org.pushingpixels.substance.internal.utils.*;
60
import org.pushingpixels.trident.Timeline.TimelineState;
61
import org.pushingpixels.trident.callback.TimelineCallback;
62
import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter;
65
* UI for tables in <b>Substance</b> look and feel. Unfortunately, the entire
66
* painting stack has been copied from {@link BasicTableUI} since the methods
67
* are private. The animation effects are implemented in the
68
* {@link #paintCell(Graphics, Rectangle, int, int)}.
70
* @author Kirill Grouchnikov
72
public class SubstanceTableUI extends BasicTableUI implements
73
UpdateOptimizationAware {
75
* Holds the list of currently selected row-column indexes.
77
protected Map<TableCellId, Object> selectedIndices;
80
* Holds the currently rolled-over row-column index, or <code>null</code> if
83
protected Set<TableCellId> rolledOverIndices;
85
protected TableCellId focusedCellId;
88
* Holds the currently rolled-over column index, or <code>-1</code> if none
89
* such. This is used for the table header animations.
91
protected int rolledOverColumn;
94
* Map of default renderers.
96
protected Map<Class<?>, TableCellRenderer> defaultRenderers;
99
* Map of default editors.
101
protected Map<Class<?>, TableCellEditor> defaultEditors;
104
* Listener that listens to changes on table properties.
106
protected PropertyChangeListener substancePropertyChangeListener;
109
* Listener for transition animations on list selections.
111
protected TableStateListener substanceTableStateListener;
114
* Listener for transition animations on table rollovers.
116
protected RolloverFadeListener substanceFadeRolloverListener;
118
protected FocusListener substanceFocusListener;
120
private StateTransitionMultiTracker<TableCellId> stateTransitionMultiTracker;
122
protected Boolean drawLeadingVerticalLine;
123
protected Boolean drawTrailingVerticalLine;
126
* Cell renderer insets. Is computed in {@link #installDefaults()} and
128
* {@link SubstanceDefaultTableCellRenderer#getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)}
130
* {@link SubstanceDefaultTableHeaderCellRenderer#getTableCellRendererComponent(JTable, Object, boolean, boolean, int, int)}
131
* for performance optimizations.
133
private Insets cellRendererInsets;
138
* @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
140
public static ComponentUI createUI(JComponent comp) {
141
SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
142
return new SubstanceTableUI();
146
* Creates a UI delegate for table.
148
public SubstanceTableUI() {
150
this.selectedIndices = new HashMap<TableCellId, Object>();
151
this.rolledOverIndices = new HashSet<TableCellId>();
152
this.stateTransitionMultiTracker = new StateTransitionMultiTracker<TableCellId>();
153
this.rolledOverColumn = -1;
155
this.cellId = new TableCellId(-1, -1);
158
static class BooleanEditor extends DefaultCellEditor {
159
private static class SubstanceEditorCheckBox extends JCheckBox {
161
public void setOpaque(boolean isOpaque) {
163
super.setOpaque(isOpaque);
168
public boolean isOpaque() {
173
public void setBorder(Border border) {
177
public BooleanEditor() {
178
super(new SubstanceEditorCheckBox());
179
JCheckBox checkBox = (JCheckBox) getComponent();
180
checkBox.setOpaque(false);
181
checkBox.setHorizontalAlignment(JCheckBox.CENTER);
188
* @see javax.swing.plaf.basic.BasicTableUI#installDefaults()
191
protected void installDefaults() {
192
super.installDefaults();
193
if (SubstanceCoreUtilities.toDrawWatermark(this.table))
194
this.table.setOpaque(false);
196
// fix for defect 117 - need to restore default table cell
197
// renderers when Substance is unset
198
this.defaultRenderers = new HashMap<Class<?>, TableCellRenderer>();
200
Class<?>[] defClasses = new Class[] { Object.class, Icon.class,
201
ImageIcon.class, Number.class, Float.class, Double.class,
202
Date.class, Boolean.class };
203
for (Class<?> clazz : defClasses) {
204
this.defaultRenderers.put(clazz,
205
this.table.getDefaultRenderer(clazz));
208
// Override default renderers - note fix for issue 194
209
// that doesn't override user-specific renderers (those that don't come
210
// from JTable class).
211
this.installRendererIfNecessary(Object.class,
212
new SubstanceDefaultTableCellRenderer());
213
this.installRendererIfNecessary(Icon.class,
214
new SubstanceDefaultTableCellRenderer.IconRenderer());
215
this.installRendererIfNecessary(ImageIcon.class,
216
new SubstanceDefaultTableCellRenderer.IconRenderer());
217
this.installRendererIfNecessary(Number.class,
218
new SubstanceDefaultTableCellRenderer.NumberRenderer());
219
this.installRendererIfNecessary(Float.class,
220
new SubstanceDefaultTableCellRenderer.DoubleRenderer());
221
this.installRendererIfNecessary(Double.class,
222
new SubstanceDefaultTableCellRenderer.DoubleRenderer());
223
this.installRendererIfNecessary(Date.class,
224
new SubstanceDefaultTableCellRenderer.DateRenderer());
226
// fix for bug 56 - making default renderer for Boolean a check box.
227
this.installRendererIfNecessary(Boolean.class,
228
new SubstanceDefaultTableCellRenderer.BooleanRenderer());
230
this.defaultEditors = new HashMap<Class<?>, TableCellEditor>();
232
Class<?>[] defEditorClasses = new Class[] { Boolean.class };
233
for (Class<?> clazz : defEditorClasses) {
234
this.defaultEditors.put(clazz, this.table.getDefaultEditor(clazz));
236
this.installEditorIfNecessary(Boolean.class, new BooleanEditor());
238
int rows = this.table.getRowCount();
239
int cols = this.table.getColumnCount();
240
for (int i = 0; i < rows; i++) {
241
for (int j = 0; j < cols; j++) {
242
if (this.table.isCellSelected(i, j)) {
243
TableCellId cellId = new TableCellId(i, j);
244
this.selectedIndices.put(cellId,
245
this.table.getValueAt(i, j));
250
// This is a little tricky, and hopefully will not
251
// interfere with existing applications. The row height in tables
252
// is computed differently from trees and lists. While lists
253
// trees respect the current renderers and their insets, the
254
// JTable uses hard-code value of 16 pixels as the default
255
// row height. This, obviously, doesn't sit well with the support
256
// for custom fonts and high-DPI monitors.
258
// The current solution first checks whether all the renderers
259
// come from Substance. If not, it does nothing. If they do, it
260
// creates a dummy label, computes its preferred height and apply
261
// insets. There's no need to go over each cell and compute its
262
// preferred height - since at this moment the cell renderers
263
// *are* Substance labels.
264
boolean areAllRenderersFromSubstance = true;
265
TableColumnModel columnModel = table.getColumnModel();
266
for (int i = 0; i < columnModel.getColumnCount(); i++) {
267
TableColumn column = columnModel.getColumn(i);
268
TableCellRenderer renderer = column.getCellRenderer();
269
if (renderer == null) {
270
renderer = table.getDefaultRenderer(table.getColumnClass(i));
272
if ((renderer instanceof SubstanceDefaultTableCellRenderer)
273
|| (renderer instanceof SubstanceDefaultTableCellRenderer.BooleanRenderer))
275
areAllRenderersFromSubstance = false;
278
if (areAllRenderersFromSubstance) {
279
Insets rendererInsets = SubstanceSizeUtils
280
.getTableCellRendererInsets(SubstanceSizeUtils
281
.getComponentFontSize(table));
282
JLabel dummy = new JLabel("dummy");
283
dummy.setFont(table.getFont());
284
int rowHeight = dummy.getPreferredSize().height
285
+ rendererInsets.bottom + rendererInsets.top;
286
table.setRowHeight(rowHeight);
289
// instead of computing the cell renderer insets on
290
// every cell rendering, compute it once and expose to the
291
// SubstanceDefaultTableCellRenderer
292
this.cellRendererInsets = SubstanceSizeUtils
293
.getTableCellRendererInsets(SubstanceSizeUtils
294
.getComponentFontSize(table));
296
drawLeadingVerticalLine = (Boolean) table.getClientProperty(SubstanceLookAndFeel.TABLE_LEADING_VERTICAL_LINE);
297
drawTrailingVerticalLine = (Boolean) table.getClientProperty(SubstanceLookAndFeel.TABLE_TRAILING_VERTICAL_LINE);
301
* Installs Substance-specific renderers for column classes that don't have
302
* application-specific renderers installed by the user code.
307
* Default renderer for the specified column class.
309
protected void installRendererIfNecessary(Class<?> clazz,
310
TableCellRenderer renderer) {
311
TableCellRenderer currRenderer = this.table.getDefaultRenderer(clazz);
312
if (currRenderer != null) {
313
boolean isCore = (currRenderer instanceof DefaultTableCellRenderer.UIResource)
314
|| (currRenderer.getClass().getName()
315
.startsWith("javax.swing.JTable"));
319
this.table.setDefaultRenderer(clazz, renderer);
323
* Installs Substance-specific renderers for column classes that don't have
324
* application-specific renderers installed by the user code.
329
* Default renderer for the specified column class.
331
protected void installEditorIfNecessary(Class<?> clazz,
332
TableCellEditor editor) {
333
TableCellEditor currEditor = this.table.getDefaultEditor(clazz);
334
if (currEditor != null) {
335
boolean isCore = currEditor.getClass().getName()
336
.startsWith("javax.swing.JTable");
340
this.table.setDefaultEditor(clazz, editor);
346
* @see javax.swing.plaf.basic.BasicTableUI#uninstallDefaults()
349
protected void uninstallDefaults() {
350
// fix for defect 117 - need to restore default table cell
351
// renderers when Substance is unset
352
for (Map.Entry<Class<?>, TableCellRenderer> entry : this.defaultRenderers
354
// fix for issue 194 - restore only those renderers that were
355
// overriden by Substance.
356
this.uninstallRendererIfNecessary(entry.getKey(), entry.getValue());
359
for (Map.Entry<Class<?>, TableCellEditor> entry : this.defaultEditors
361
this.uninstallEditorIfNecessary(entry.getKey(), entry.getValue());
364
this.selectedIndices.clear();
365
// this.table.putClientProperty(SubstanceTableUI.SELECTED_INDICES,
368
super.uninstallDefaults();
372
* Uninstalls default Substance renderers that were installed in
373
* {@link #installRendererIfNecessary(Class, TableCellRenderer)}.
378
* Renderer to restore.
380
protected void uninstallRendererIfNecessary(Class<?> clazz,
381
TableCellRenderer renderer) {
382
TableCellRenderer currRenderer = this.table.getDefaultRenderer(clazz);
383
if (currRenderer != null) {
384
boolean isSubstanceRenderer = isSubstanceDefaultRenderer(currRenderer);
385
if (!isSubstanceRenderer)
388
if (renderer instanceof Component)
389
SwingUtilities.updateComponentTreeUI((Component) renderer);
390
this.table.setDefaultRenderer(clazz, renderer);
394
* Uninstalls default Substance editors that were installed in
395
* {@link #installEditorIfNecessary(Class, TableCellEditor)}.
402
protected void uninstallEditorIfNecessary(Class<?> clazz,
403
TableCellEditor editor) {
404
TableCellEditor currEditor = this.table.getDefaultEditor(clazz);
405
if (currEditor != null) {
406
boolean isSubstanceEditor = isSubstanceDefaultEditor(currEditor);
407
if (!isSubstanceEditor)
410
if (editor instanceof Component)
411
SwingUtilities.updateComponentTreeUI((Component) editor);
412
this.table.setDefaultEditor(clazz, editor);
418
* @see javax.swing.plaf.basic.BasicTableUI#installListeners()
421
protected void installListeners() {
422
super.installListeners();
423
this.substancePropertyChangeListener = new PropertyChangeListener() {
425
public void propertyChange(PropertyChangeEvent evt) {
426
if (SubstanceLookAndFeel.WATERMARK_VISIBLE.equals(evt
427
.getPropertyName())) {
428
SubstanceTableUI.this.table
429
.setOpaque(!SubstanceCoreUtilities
430
.toDrawWatermark(SubstanceTableUI.this.table));
433
if ("columnSelectionAllowed".equals(evt.getPropertyName())
434
|| "rowSelectionAllowed".equals(evt.getPropertyName())) {
435
SubstanceTableUI.this.syncSelection(true);
438
if ("model".equals(evt.getPropertyName())) {
439
TableModel old = (TableModel) evt.getOldValue();
441
old.removeTableModelListener(substanceTableStateListener);
443
// fix for defect 291 - track changes to the table.
444
table.getModel().addTableModelListener(
445
substanceTableStateListener);
446
selectedIndices.clear();
447
stateTransitionMultiTracker.clear();
448
SubstanceTableUI.this.syncSelection(true);
451
if ("columnModel".equals(evt.getPropertyName())) {
452
TableColumnModel old = (TableColumnModel) evt.getOldValue();
454
old.getSelectionModel().removeListSelectionListener(
455
substanceTableStateListener);
457
table.getColumnModel()
459
.addListSelectionListener(
460
substanceTableStateListener);
461
selectedIndices.clear();
462
stateTransitionMultiTracker.clear();
463
SubstanceTableUI.this.syncSelection(true);
465
JTableHeader tableHeader = table.getTableHeader();
466
// fix for issue 408 - table header may be null.
467
if (tableHeader != null) {
468
// fix for issue 309 - syncing animations on tables
469
// and table headers.
470
SubstanceTableHeaderUI headerUI = (SubstanceTableHeaderUI) tableHeader
472
headerUI.processColumnModelChangeEvent(
473
(TableColumnModel) evt.getOldValue(),
474
(TableColumnModel) evt.getNewValue());
478
// fix for defect 243 - not tracking changes to selection
479
// model results in incorrect selection painting on JXTreeTable
480
// component from SwingX.
481
if ("selectionModel".equals(evt.getPropertyName())) {
482
ListSelectionModel old = (ListSelectionModel) evt
485
old.removeListSelectionListener(substanceTableStateListener);
487
table.getSelectionModel().addListSelectionListener(
488
substanceTableStateListener);
489
selectedIndices.clear();
490
stateTransitionMultiTracker.clear();
491
SubstanceTableUI.this.syncSelection(true);
494
// fix for issue 479 - tracking sort / filter changes and
495
// canceling selection animations
496
if ("rowSorter".equals(evt.getPropertyName())) {
497
RowSorter old = (RowSorter) evt.getOldValue();
499
old.removeRowSorterListener(substanceTableStateListener);
501
RowSorter newSorter = (RowSorter) evt.getNewValue();
502
if (newSorter != null) {
504
.addRowSorterListener(substanceTableStateListener);
506
selectedIndices.clear();
507
stateTransitionMultiTracker.clear();
508
SubstanceTableUI.this.syncSelection(true);
511
if ("font".equals(evt.getPropertyName())) {
512
SwingUtilities.invokeLater(new Runnable() {
523
if ("background".equals(evt.getPropertyName())) {
524
// propagate application-specific background color to the
526
Color newBackgr = (Color) evt.getNewValue();
527
JTableHeader header = table.getTableHeader();
528
if (header != null) {
529
Color headerBackground = header.getBackground();
530
if (SubstanceCoreUtilities
531
.canReplaceChildBackgroundColor(headerBackground)) {
532
if (!(newBackgr instanceof UIResource)) {
533
if (newBackgr == null) {
534
header.setBackground(null);
536
// Issue 450 - wrap the color in
537
// SubstanceColorResource to
538
// mark that it can be replaced.
539
header.setBackground(new SubstanceColorResource(
543
header.setBackground(newBackgr);
549
// fix for issue 361 - track enabled status of the table
550
// and propagate to the table header
551
if ("enabled".equals(evt.getPropertyName())) {
552
JTableHeader header = table.getTableHeader();
553
if (header != null) {
554
header.setEnabled(table.isEnabled());
558
if ("dropLocation".equals(evt.getPropertyName())) {
559
JTable.DropLocation oldValue = (JTable.DropLocation) evt
561
if (oldValue != null) {
562
Rectangle oldRect = getCellRectangleForRepaint(
563
oldValue.getRow(), oldValue.getColumn());
564
table.repaint(oldRect);
566
JTable.DropLocation newValue = table.getDropLocation();
567
if (newValue != null) {
568
Rectangle newRect = getCellRectangleForRepaint(table
569
.getDropLocation().getRow(), table
570
.getDropLocation().getColumn());
571
table.repaint(newRect);
575
if ("tableCellEditor".equals(evt.getPropertyName())) {
576
// fix for issue 481 - rollovers on cell editing
577
TableCellEditor newEditor = (TableCellEditor) evt
579
TableCellEditor oldEditor = (TableCellEditor) evt
581
if (oldEditor != null) {
582
if (table.getEditorComponent() != null) {
583
table.getEditorComponent().removeMouseListener(
584
substanceFadeRolloverListener);
587
if (newEditor != null) {
588
if (table.getEditorComponent() != null) {
589
table.getEditorComponent().addMouseListener(
590
substanceFadeRolloverListener);
595
if (SubstanceLookAndFeel.TABLE_LEADING_VERTICAL_LINE.equals(evt.getPropertyName())) {
596
drawLeadingVerticalLine = (Boolean) evt.getNewValue();
598
if (SubstanceLookAndFeel.TABLE_TRAILING_VERTICAL_LINE.equals(evt.getPropertyName())) {
599
drawTrailingVerticalLine = (Boolean) evt.getNewValue();
604
.addPropertyChangeListener(this.substancePropertyChangeListener);
606
// Add listener for the selection animation
607
this.substanceTableStateListener = new TableStateListener();
608
this.table.getSelectionModel().addListSelectionListener(
609
this.substanceTableStateListener);
610
TableColumnModel columnModel = this.table.getColumnModel();
611
columnModel.getSelectionModel().addListSelectionListener(
612
this.substanceTableStateListener);
613
this.table.getModel().addTableModelListener(
614
this.substanceTableStateListener);
615
if (this.table.getRowSorter() != null) {
616
this.table.getRowSorter().addRowSorterListener(
617
this.substanceTableStateListener);
620
// Add listener for the transition animation
621
this.substanceFadeRolloverListener = new RolloverFadeListener();
622
this.table.addMouseMotionListener(this.substanceFadeRolloverListener);
623
this.table.addMouseListener(this.substanceFadeRolloverListener);
625
// fix for issue 481 - tracking focus events on the table
626
this.substanceFocusListener = new FocusListener() {
628
public void focusLost(FocusEvent e) {
629
if (focusedCellId == null)
632
ComponentState cellState = getCellState(focusedCellId);
633
StateTransitionTracker tracker = getTracker(focusedCellId,
634
cellState.isFacetActive(ComponentStateFacet.ROLLOVER),
635
cellState.isFacetActive(ComponentStateFacet.SELECTION));
636
tracker.setFocusState(false);
638
focusedCellId = null;
642
public void focusGained(FocusEvent e) {
643
int rowLead = table.getSelectionModel().getLeadSelectionIndex();
644
int colLead = table.getColumnModel().getSelectionModel()
645
.getLeadSelectionIndex();
646
if ((rowLead >= 0) && (colLead >= 0)) {
647
TableCellId toFocus = new TableCellId(rowLead, colLead);
648
if (toFocus.equals(focusedCellId))
650
ComponentState cellState = getCellState(toFocus);
651
StateTransitionTracker tracker = getTracker(
654
.isFacetActive(ComponentStateFacet.ROLLOVER),
656
.isFacetActive(ComponentStateFacet.SELECTION));
657
tracker.setFocusState(true);
659
focusedCellId = toFocus;
663
this.table.addFocusListener(this.substanceFocusListener);
669
* @see javax.swing.plaf.basic.BasicTableUI#uninstallListeners()
672
protected void uninstallListeners() {
674
.removePropertyChangeListener(this.substancePropertyChangeListener);
675
this.substancePropertyChangeListener = null;
677
this.table.getSelectionModel().removeListSelectionListener(
678
this.substanceTableStateListener);
679
this.table.getColumnModel().getSelectionModel()
680
.removeListSelectionListener(this.substanceTableStateListener);
681
this.table.getModel().removeTableModelListener(
682
this.substanceTableStateListener);
683
if (this.table.getRowSorter() != null) {
684
this.table.getRowSorter().removeRowSorterListener(
685
this.substanceTableStateListener);
687
this.substanceTableStateListener = null;
689
// Remove listener for the fade animation
691
.removeMouseMotionListener(this.substanceFadeRolloverListener);
692
this.table.removeMouseListener(this.substanceFadeRolloverListener);
693
this.substanceFadeRolloverListener = null;
695
this.table.removeFocusListener(this.substanceFocusListener);
696
this.substanceFocusListener = null;
698
super.uninstallListeners();
702
* Paint a representation of the <code>table</code> instance that was set in
706
public void paint(Graphics g, JComponent c) {
707
Rectangle clip = g.getClipBounds();
709
Rectangle bounds = this.table.getBounds();
710
// account for the fact that the graphics has already been translated
711
// into the table's bounds
712
bounds.x = bounds.y = 0;
714
if (this.table.getRowCount() <= 0 || this.table.getColumnCount() <= 0 ||
715
// this check prevents us from painting the entire table
716
// when the clip doesn't intersect our bounds at all
717
!bounds.intersects(clip)) {
722
Point upperLeft = clip.getLocation();
723
Point lowerRight = new Point(clip.x + clip.width - 1, clip.y
725
int rMin = this.table.rowAtPoint(upperLeft);
726
int rMax = this.table.rowAtPoint(lowerRight);
727
// This should never happen (as long as our bounds intersect the clip,
728
// which is why we bail above if that is the case).
732
// If the table does not have enough rows to fill the view we'll get -1.
733
// (We could also get -1 if our bounds don't intersect the clip,
734
// which is why we bail above if that is the case).
735
// Replace this with the index of the last row.
737
rMax = this.table.getRowCount() - 1;
740
boolean ltr = this.table.getComponentOrientation().isLeftToRight();
741
int cMin = this.table.columnAtPoint(ltr ? upperLeft : lowerRight);
742
int cMax = this.table.columnAtPoint(ltr ? lowerRight : upperLeft);
743
// This should never happen.
747
// If the table does not have enough columns to fill the view we'll get
749
// Replace this with the index of the last column.
751
cMax = this.table.getColumnCount() - 1;
755
this.paintCells(g, rMin, rMax, cMin, cMax);
758
this.paintGrid(g, rMin, rMax, cMin, cMax);
760
// Paint the drop lines
761
this.paintDropLines(g);
765
* Paints the grid lines within <I>aRect</I>, using the grid color set with
766
* <I>setGridColor</I>. Paints vertical lines if
767
* <code>getShowVerticalLines()</code> returns true and paints horizontal
768
* lines if <code>getShowHorizontalLines()</code> returns true.
770
protected void paintGrid(Graphics g, int rMin, int rMax, int cMin, int cMax) {
771
Graphics2D g2d = (Graphics2D) g.create();
772
ComponentState currState = this.table.isEnabled() ? ComponentState.ENABLED
773
: ComponentState.DISABLED_UNSELECTED;
774
float alpha = SubstanceColorSchemeUtilities.getAlpha(this.table,
776
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.table,
779
Color gridColor = this.table.getGridColor();
780
if (gridColor instanceof UIResource) {
781
SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
782
.getColorScheme(this.table,
783
ColorSchemeAssociationKind.BORDER, this.table
784
.isEnabled() ? ComponentState.ENABLED
785
: ComponentState.DISABLED_UNSELECTED);
786
gridColor = scheme.getLineColor();
788
g2d.setColor(gridColor);
790
Rectangle minCell = this.table.getCellRect(rMin, cMin, true);
791
Rectangle maxCell = this.table.getCellRect(rMax, cMax, true);
792
Rectangle damagedArea = minCell.union(maxCell);
794
float strokeWidth = SubstanceSizeUtils
795
.getBorderStrokeWidth(SubstanceSizeUtils
796
.getComponentFontSize(this.table));
797
g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND,
798
BasicStroke.JOIN_BEVEL));
799
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
800
RenderingHints.VALUE_ANTIALIAS_ON);
802
if (this.table.getShowHorizontalLines()) {
803
int tableWidth = damagedArea.x + damagedArea.width;
804
int y = damagedArea.y;
805
for (int row = rMin; row <= rMax; row++) {
806
y += this.table.getRowHeight(row);
807
g2d.drawLine(damagedArea.x, y - 1, tableWidth - 1, y - 1);
810
if (this.table.getShowVerticalLines()) {
811
TableColumnModel cm = this.table.getColumnModel();
812
int tableHeight = damagedArea.y + damagedArea.height;
814
if (this.table.getComponentOrientation().isLeftToRight()) {
816
for (int column = cMin; column <= cMax; column++) {
817
int w = cm.getColumn(column).getWidth();
818
if (hasLeadingVerticalGridLine(cm, column)) {
819
g2d.drawLine(x, 0, x, tableHeight - 1);
822
if (hasTrailingVerticalGridLine(cm, column)) {
823
g2d.drawLine(x - 1, 0, x - 1, tableHeight - 1);
827
x = damagedArea.x + damagedArea.width;
828
// fix for defect 196 - proper grid painting on RTL tables
829
for (int column = cMin; column <= cMax; column++) {
830
int w = cm.getColumn(column).getWidth();
831
if (hasLeadingVerticalGridLine(cm, column)) {
832
g2d.drawLine(x - 1, 0, x - 1, tableHeight - 1);
835
if (hasTrailingVerticalGridLine(cm, column)) {
836
g2d.drawLine(x, 0, x, tableHeight - 1);
844
private boolean hasTrailingVerticalGridLine(TableColumnModel cm, int column) {
845
boolean toDrawLine = (column != (cm.getColumnCount() - 1));
847
if (drawTrailingVerticalLine != null) {
848
toDrawLine = drawTrailingVerticalLine;
850
Container parent = this.table.getParent();
851
toDrawLine = (parent != null)
852
&& (parent.getWidth() >= this.table.getWidth());
858
private boolean hasLeadingVerticalGridLine(TableColumnModel cm, int column) {
863
if (drawLeadingVerticalLine != null) {
864
return drawLeadingVerticalLine;
867
Container parent = this.table.getParent();
868
if (parent instanceof JViewport) {
869
Container grand = parent.getParent();
870
if (grand instanceof JScrollPane) {
871
return (((JScrollPane) grand).getRowHeader() != null);
877
private int viewIndexForColumn(TableColumn aColumn) {
878
TableColumnModel cm = this.table.getColumnModel();
879
for (int column = 0; column < cm.getColumnCount(); column++) {
880
if (cm.getColumn(column) == aColumn) {
887
protected void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
888
JTableHeader header = this.table.getTableHeader();
889
TableColumn draggedColumn = (header == null) ? null : header
892
TableColumnModel cm = this.table.getColumnModel();
893
int columnMargin = cm.getColumnMargin();
894
int rowMargin = this.table.getRowMargin();
897
Rectangle highlightCellRect;
900
if (this.table.getComponentOrientation().isLeftToRight()) {
901
for (int row = rMin; row <= rMax; row++) {
902
cellRect = this.table.getCellRect(row, cMin, false);
904
highlightCellRect = new Rectangle(cellRect);
905
highlightCellRect.y -= rowMargin / 2;
906
highlightCellRect.height += rowMargin;
908
for (int column = cMin; column <= cMax; column++) {
909
aColumn = cm.getColumn(column);
910
columnWidth = aColumn.getWidth();
912
cellRect.width = columnWidth - columnMargin;
913
highlightCellRect.x = cellRect.x - columnMargin / 2;
914
highlightCellRect.width = columnWidth;
915
//if (!hasTrailingVerticalGridLine(cm, column)) {
917
// highlightCellRect.width++;
920
if (aColumn != draggedColumn) {
921
this.paintCell(g, cellRect, highlightCellRect, row,
924
cellRect.x += columnWidth;
928
for (int row = rMin; row <= rMax; row++) {
929
cellRect = this.table.getCellRect(row, cMin, false);
930
highlightCellRect = new Rectangle(cellRect);
931
highlightCellRect.y -= rowMargin / 2;
932
highlightCellRect.height += rowMargin;
934
for (int column = cMin; column <= cMax; column++) {
935
aColumn = cm.getColumn(column);
936
columnWidth = aColumn.getWidth();
937
cellRect.width = columnWidth - columnMargin;
939
highlightCellRect.x = cellRect.x - columnMargin / 2;
940
highlightCellRect.width = columnWidth;
941
if (aColumn != draggedColumn) {
942
this.paintCell(g, cellRect, highlightCellRect, row,
945
cellRect.x -= columnWidth;
950
// Paint the dragged column if we are dragging.
951
if (draggedColumn != null) {
952
Graphics2D g2d = (Graphics2D) g.create();
953
// enhancement 331 - translucent dragged column
954
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.table,
956
this.paintDraggedArea(g2d, rMin, rMax, draggedColumn,
957
header.getDraggedDistance());
961
// Remove any renderers that may be left in the rendererPane.
962
this.rendererPane.removeAll();
965
protected void paintDraggedArea(Graphics g, int rMin, int rMax,
966
TableColumn draggedColumn, int distance) {
967
int draggedColumnIndex = this.viewIndexForColumn(draggedColumn);
969
Rectangle minCell = this.table.getCellRect(rMin, draggedColumnIndex,
971
Rectangle maxCell = this.table.getCellRect(rMax, draggedColumnIndex,
974
Rectangle vacatedColumnRect = minCell.union(maxCell);
976
// Paint a gray well in place of the moving column.
977
g.setColor(this.table.getParent().getBackground());
978
g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
979
vacatedColumnRect.width, vacatedColumnRect.height);
981
// Move to the where the cell has been dragged.
982
vacatedColumnRect.x += distance;
984
// Fill the background.
985
g.setColor(this.table.getBackground());
986
g.fillRect(vacatedColumnRect.x, vacatedColumnRect.y,
987
vacatedColumnRect.width, vacatedColumnRect.height);
989
// Paint the vertical grid lines if necessary.
990
if (this.table.getShowVerticalLines()) {
991
g.setColor(this.table.getGridColor());
992
int x1 = vacatedColumnRect.x;
993
int y1 = vacatedColumnRect.y;
994
int x2 = x1 + vacatedColumnRect.width - 1;
995
int y2 = y1 + vacatedColumnRect.height - 1;
997
g.drawLine(x1 - 1, y1, x1 - 1, y2);
999
g.drawLine(x2, y1, x2, y2);
1002
for (int row = rMin; row <= rMax; row++) {
1003
// Render the cell value
1004
Rectangle r = this.table
1005
.getCellRect(row, draggedColumnIndex, false);
1007
this.paintCell(g, r, r, row, draggedColumnIndex);
1009
// Paint the (lower) horizontal grid line if necessary.
1010
if (this.table.getShowHorizontalLines()) {
1011
g.setColor(this.table.getGridColor());
1012
Rectangle rcr = this.table.getCellRect(row, draggedColumnIndex,
1017
int x2 = x1 + rcr.width - 1;
1018
int y2 = y1 + rcr.height - 1;
1019
g.drawLine(x1, y2, x2, y2);
1024
protected void paintCell(Graphics g, Rectangle cellRect,
1025
Rectangle highlightCellRect, int row, int column) {
1026
// System.out.println("Painting " + row + ":" + column);
1027
Component rendererComponent = null;
1028
if (!this.table.isEditing() || this.table.getEditingRow() != row
1029
|| this.table.getEditingColumn() != column) {
1030
TableCellRenderer renderer = this.table
1031
.getCellRenderer(row, column);
1032
boolean isSubstanceRenderer = isSubstanceDefaultRenderer(renderer);
1033
rendererComponent = this.table.prepareRenderer(renderer, row,
1035
boolean isSubstanceRendererComponent = isSubstanceDefaultRenderer(rendererComponent);
1036
if (isSubstanceRenderer && !isSubstanceRendererComponent) {
1037
if (!Boolean.getBoolean("insubstantial.looseTableCellRenderers")) {
1038
throw new IllegalArgumentException(
1039
"Renderer extends the SubstanceDefaultTableCellRenderer but does not return one in its getTableCellRendererComponent() method");
1043
if (!isSubstanceRenderer) {
1044
rendererPane.paintComponent(g, rendererComponent, table,
1045
cellRect.x, cellRect.y, cellRect.width,
1046
cellRect.height, true);
1051
Graphics2D g2d = (Graphics2D) g.create();
1052
// fix for issue 183 - passing the original Graphics context
1053
// to compute the alpha composite. If the table is in a JXPanel
1054
// (component from SwingX) and it has custom alpha value set,
1055
// then the original graphics context will have a SRC_OVER
1056
// alpha composite applied to it.
1057
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.table, g));
1059
TableCellId cellId = new TableCellId(row, column);
1061
StateTransitionTracker.ModelStateInfo modelStateInfo = this
1062
.getModelStateInfo(cellId);
1063
Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null
1064
: modelStateInfo.getStateContributionMap());
1065
// optimize for tables that don't initiate rollover
1066
// or selection animations
1067
if (!updateInfo.hasRolloverAnimations
1068
&& !updateInfo.hasSelectionAnimations)
1069
activeStates = null;
1070
ComponentState currState = ((modelStateInfo == null) ? this
1071
.getCellState(cellId) : modelStateInfo.getCurrModelState());
1073
boolean hasHighlights = (currState != ComponentState.ENABLED)
1074
|| (activeStates != null);
1075
if (activeStates != null) {
1076
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
1078
hasHighlights = (this.updateInfo.getHighlightAlpha(stateEntry
1079
.getKey()) * stateEntry.getValue().getContribution() > 0.0f);
1084
hasHighlights = (this.updateInfo.getHighlightAlpha(currState) > 0.0f);
1087
Object highlightProperty = table.getClientProperty("substancelaf.highlightCells");
1088
hasHighlights = (highlightProperty instanceof Boolean) ? (Boolean)highlightProperty && hasHighlights : hasHighlights;
1090
Set<SubstanceConstants.Side> highlightOpenSides = null;
1091
float highlightBorderAlpha = 0.0f;
1093
if (hasHighlights) {
1094
// compute the highlight visuals, but only if there are
1095
// highlights on this cell (optimization)
1096
highlightOpenSides = EnumSet.noneOf(Side.class);
1097
// show highlight border only when the table grid is not shown
1098
highlightBorderAlpha = (table.getShowHorizontalLines() || table
1099
.getShowVerticalLines()) ? 0.0f : 0.8f;
1100
if (!table.getColumnSelectionAllowed()
1101
&& table.getRowSelectionAllowed()) {
1102
// if row selection is on and column selection is off, we
1103
// will show the highlight for the entire row
1105
// all cells have open left side
1106
highlightOpenSides.add(SubstanceConstants.Side.LEFT);
1107
// all cells have open right side
1108
highlightOpenSides.add(SubstanceConstants.Side.RIGHT);
1110
if (table.getColumnSelectionAllowed()
1111
&& !table.getRowSelectionAllowed()) {
1112
// if row selection is off and column selection is on, we
1113
// will show the highlight for the entire column
1115
// the top side is open for all rows except the
1116
// first, or when the table header is visible
1117
highlightOpenSides.add(SubstanceConstants.Side.TOP);
1118
// all cells but the last have open bottom side
1119
highlightOpenSides.add(SubstanceConstants.Side.BOTTOM);
1122
ComponentState upperNeighbourState = this
1123
.getCellState(new TableCellId(row - 1, column));
1124
if (currState == upperNeighbourState) {
1125
// the cell above it is in the same state
1126
highlightOpenSides.add(SubstanceConstants.Side.TOP);
1130
ComponentState leftNeighbourState = this
1131
.getCellState(new TableCellId(row, column - 1));
1132
if (currState == leftNeighbourState) {
1133
// the cell to the left is in the same state
1134
highlightOpenSides.add(SubstanceConstants.Side.LEFT);
1138
highlightOpenSides.add(SubstanceConstants.Side.TOP);
1140
if (row == (table.getRowCount() - 1)) {
1141
highlightOpenSides.add(SubstanceConstants.Side.BOTTOM);
1144
highlightOpenSides.add(SubstanceConstants.Side.LEFT);
1146
if (column == (table.getColumnCount() - 1)) {
1147
highlightOpenSides.add(SubstanceConstants.Side.RIGHT);
1151
boolean isRollover = this.rolledOverIndices.contains(cellId);
1152
if (this.table.isEditing() && this.table.getEditingRow() == row
1153
&& this.table.getEditingColumn() == column) {
1154
Component component = this.table.getEditorComponent();
1155
component.applyComponentOrientation(this.table
1156
.getComponentOrientation());
1158
if (hasHighlights) {
1159
float extra = SubstanceSizeUtils
1160
.getBorderStrokeWidth(SubstanceSizeUtils
1161
.getComponentFontSize(this.table
1162
.getTableHeader()));
1163
float extraWidth = highlightOpenSides
1164
.contains(SubstanceConstants.Side.LEFT) ? 0.0f : extra;
1165
float extraHeight = highlightOpenSides
1166
.contains(SubstanceConstants.Side.TOP) ? 0.0f : extra;
1167
Rectangle highlightRect = new Rectangle(highlightCellRect.x
1168
- (int) extraWidth, highlightCellRect.y
1169
- (int) extraHeight, highlightCellRect.width
1170
+ (int) extraWidth, highlightCellRect.height
1171
+ (int) extraHeight);
1172
if (activeStates == null) {
1173
float alpha = this.updateInfo.getHighlightAlpha(currState);
1175
SubstanceColorScheme fillScheme = this.updateInfo
1176
.getHighlightColorScheme(currState);
1177
SubstanceColorScheme borderScheme = this.updateInfo
1178
.getHighlightBorderColorScheme(currState);
1179
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
1180
this.table, alpha, g));
1181
HighlightPainterUtils.paintHighlight(g2d,
1182
this.rendererPane, component, highlightRect,
1183
highlightBorderAlpha, highlightOpenSides,
1184
fillScheme, borderScheme);
1185
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
1189
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
1191
ComponentState activeState = stateEntry.getKey();
1192
float alpha = this.updateInfo
1193
.getHighlightAlpha(activeState)
1194
* stateEntry.getValue().getContribution();
1197
SubstanceColorScheme fillScheme = this.updateInfo
1198
.getHighlightColorScheme(activeState);
1199
SubstanceColorScheme borderScheme = this.updateInfo
1200
.getHighlightBorderColorScheme(activeState);
1201
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
1202
this.table, alpha, g));
1203
HighlightPainterUtils.paintHighlight(g2d,
1204
this.rendererPane, component, highlightRect,
1205
highlightBorderAlpha, highlightOpenSides,
1206
fillScheme, borderScheme);
1207
g2d.setComposite(LafWidgetUtilities.getAlphaComposite(
1213
component.setBounds(cellRect);
1214
component.validate();
1216
boolean isWatermarkBleed = this.updateInfo.toDrawWatermark;
1217
if (rendererComponent != null) {
1218
if (!isWatermarkBleed) {
1219
Color background = rendererComponent.getBackground();
1220
// optimization - only render background if it's different
1221
// from the table background
1222
if ((background != null)
1223
&& (!table.getBackground().equals(background) || this.updateInfo.isInDecorationArea)) {
1224
// fill with the renderer background color
1225
g2d.setColor(background);
1226
g2d.fillRect(highlightCellRect.x, highlightCellRect.y,
1227
highlightCellRect.width,
1228
highlightCellRect.height);
1231
BackgroundPaintingUtils.fillAndWatermark(g2d, this.table,
1232
rendererComponent.getBackground(),
1237
if (hasHighlights) {
1238
JTable.DropLocation dropLocation = table.getDropLocation();
1239
if (dropLocation != null && !dropLocation.isInsertRow()
1240
&& !dropLocation.isInsertColumn()
1241
&& dropLocation.getRow() == row
1242
&& dropLocation.getColumn() == column) {
1243
// mark drop location
1244
SubstanceColorScheme scheme = SubstanceColorSchemeUtilities
1245
.getColorScheme(table,
1246
ColorSchemeAssociationKind.TEXT_HIGHLIGHT,
1248
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
1249
.getColorScheme(table,
1250
ColorSchemeAssociationKind.BORDER,
1252
float extra = SubstanceSizeUtils
1253
.getBorderStrokeWidth(SubstanceSizeUtils
1254
.getComponentFontSize(this.table
1255
.getTableHeader()));
1256
HighlightPainterUtils.paintHighlight(g2d,
1257
this.rendererPane, rendererComponent,
1258
new Rectangle(highlightCellRect.x - (int) extra,
1259
highlightCellRect.y - (int) extra,
1260
highlightCellRect.width + (int) extra,
1261
highlightCellRect.height + (int) extra),
1262
0.8f, null, scheme, borderScheme);
1264
float extra = SubstanceSizeUtils
1265
.getBorderStrokeWidth(SubstanceSizeUtils
1266
.getComponentFontSize(this.table
1267
.getTableHeader()));
1268
float extraWidth = highlightOpenSides
1269
.contains(SubstanceConstants.Side.LEFT) ? 0.0f
1271
float extraHeight = highlightOpenSides
1272
.contains(SubstanceConstants.Side.TOP) ? 0.0f
1274
Rectangle highlightRect = new Rectangle(highlightCellRect.x
1275
- (int) extraWidth, highlightCellRect.y
1276
- (int) extraHeight, highlightCellRect.width
1277
+ (int) extraWidth, highlightCellRect.height
1278
+ (int) extraHeight);
1279
if (activeStates == null) {
1280
SubstanceColorScheme fillScheme = this.updateInfo
1281
.getHighlightColorScheme(currState);
1282
SubstanceColorScheme borderScheme = this.updateInfo
1283
.getHighlightBorderColorScheme(currState);
1284
float alpha = this.updateInfo
1285
.getHighlightAlpha(currState);
1287
g2d.setComposite(LafWidgetUtilities
1288
.getAlphaComposite(this.table, alpha, g));
1289
HighlightPainterUtils.paintHighlight(g2d,
1290
this.rendererPane, rendererComponent,
1291
highlightRect, highlightBorderAlpha,
1292
highlightOpenSides, fillScheme,
1294
g2d.setComposite(LafWidgetUtilities
1295
.getAlphaComposite(this.table, g));
1298
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates
1300
ComponentState activeState = stateEntry.getKey();
1301
SubstanceColorScheme fillScheme = this.updateInfo
1302
.getHighlightColorScheme(activeState);
1303
SubstanceColorScheme borderScheme = this.updateInfo
1304
.getHighlightBorderColorScheme(activeState);
1305
float alpha = this.updateInfo
1306
.getHighlightAlpha(activeState)
1307
* stateEntry.getValue().getContribution();
1309
g2d.setComposite(LafWidgetUtilities
1310
.getAlphaComposite(this.table, alpha, g));
1311
HighlightPainterUtils.paintHighlight(g2d,
1312
this.rendererPane, rendererComponent,
1313
highlightRect, highlightBorderAlpha,
1314
highlightOpenSides, fillScheme,
1316
g2d.setComposite(LafWidgetUtilities
1317
.getAlphaComposite(this.table, g));
1324
rendererComponent.applyComponentOrientation(this.table
1325
.getComponentOrientation());
1326
if (rendererComponent instanceof JComponent) {
1327
// Play with opacity to make our own gradient background
1328
// on selected elements to show.
1329
JComponent jRenderer = (JComponent) rendererComponent;
1330
// Compute the selection status to prevent flicker - JTable
1331
// registers a listener on selection changes and repaints
1332
// the relevant cell before our listener (in TableUI) gets
1333
// the chance to start the fade sequence. The result is that
1334
// the first frame uses full opacity, and the next frame
1335
// starts the fade sequence. So, we use the UI delegate to
1336
// compute the selection status.
1337
boolean isSelected = updateInfo.hasSelectionAnimations ? this.selectedIndices
1338
.containsKey(cellId) : this.table.isCellSelected(row,
1340
boolean newOpaque = !(isSelected || isRollover || hasHighlights);
1342
if (this.updateInfo.toDrawWatermark)
1345
Map<Component, Boolean> opacity = new HashMap<Component, Boolean>();
1347
SubstanceCoreUtilities.makeNonOpaque(jRenderer, opacity);
1348
this.rendererPane.paintComponent(g2d, rendererComponent,
1349
this.table, cellRect.x, cellRect.y, cellRect.width,
1350
cellRect.height, true);
1352
SubstanceCoreUtilities.restoreOpaque(jRenderer, opacity);
1354
this.rendererPane.paintComponent(g2d, rendererComponent,
1355
this.table, cellRect.x, cellRect.y, cellRect.width,
1356
cellRect.height, true);
1362
protected void paintDropLines(Graphics g) {
1363
JTable.DropLocation loc = table.getDropLocation();
1368
Color color = UIManager.getColor("Table.dropLineColor");
1369
Color shortColor = UIManager.getColor("Table.dropLineShortColor");
1370
if (color == null && shortColor == null) {
1376
rect = getHDropLineRect(loc);
1380
if (color != null) {
1381
extendRect(rect, true);
1383
g.fillRect(rect.x, rect.y, rect.width, rect.height);
1385
if (!loc.isInsertColumn() && shortColor != null) {
1386
g.setColor(shortColor);
1387
g.fillRect(x, rect.y, w, rect.height);
1391
rect = getVDropLineRect(loc);
1394
int h = rect.height;
1395
if (color != null) {
1396
extendRect(rect, false);
1398
g.fillRect(rect.x, rect.y, rect.width, rect.height);
1400
if (!loc.isInsertRow() && shortColor != null) {
1401
g.setColor(shortColor);
1402
g.fillRect(rect.x, y, rect.width, h);
1407
private Rectangle getHDropLineRect(JTable.DropLocation loc) {
1408
if (!loc.isInsertRow()) {
1412
int row = loc.getRow();
1413
int col = loc.getColumn();
1414
if (col >= table.getColumnCount()) {
1418
Rectangle rect = table.getCellRect(row, col, true);
1420
if (row >= table.getRowCount()) {
1422
Rectangle prevRect = table.getCellRect(row, col, true);
1423
rect.y = prevRect.y + prevRect.height;
1437
private Rectangle getVDropLineRect(JTable.DropLocation loc) {
1438
if (!loc.isInsertColumn()) {
1442
boolean ltr = table.getComponentOrientation().isLeftToRight();
1443
int col = loc.getColumn();
1444
Rectangle rect = table.getCellRect(loc.getRow(), col, true);
1446
if (col >= table.getColumnCount()) {
1448
rect = table.getCellRect(loc.getRow(), col, true);
1450
rect.x = rect.x + rect.width;
1453
rect.x = rect.x + rect.width;
1467
private Rectangle extendRect(Rectangle rect, boolean horizontal) {
1474
rect.width = table.getWidth();
1478
if (table.getRowCount() != 0) {
1479
Rectangle lastRect = table.getCellRect(table.getRowCount() - 1,
1481
rect.height = lastRect.y + lastRect.height;
1483
rect.height = table.getHeight();
1491
* Repaints a single cell during the fade animation cycle.
1493
* @author Kirill Grouchnikov
1495
protected class CellRepaintCallback extends UIThreadTimelineCallbackAdapter {
1499
protected JTable table;
1502
* Associated (animated) row index.
1504
protected int rowIndex;
1507
* Associated (animated) column index.
1509
protected int columnIndex;
1512
* Creates a new animation repaint callback.
1517
* Associated (animated) row index.
1518
* @param columnIndex
1519
* Associated (animated) column index.
1521
public CellRepaintCallback(JTable table, int rowIndex, int columnIndex) {
1524
this.rowIndex = rowIndex;
1525
this.columnIndex = columnIndex;
1529
public void onTimelinePulse(float durationFraction,
1530
float timelinePosition) {
1535
public void onTimelineStateChanged(TimelineState oldState,
1536
TimelineState newState, float durationFraction,
1537
float timelinePosition) {
1542
* Repaints the associated cell.
1544
private void repaintCell() {
1545
SwingUtilities.invokeLater(new Runnable() {
1548
if (SubstanceTableUI.this.table == null) {
1549
// may happen if the LAF was switched in the meantime
1552
int rowCount = table.getRowCount();
1553
int colCount = table.getColumnCount();
1554
if ((rowCount > 0) && (rowIndex < rowCount)
1555
&& (colCount > 0) && (columnIndex < colCount)) {
1556
// need to retrieve the cell rectangle since the cells
1557
// can be moved while animating
1558
Rectangle rect = getCellRectangleForRepaint(rowIndex,
1560
// System.out.println("Cell Repainting " + rowIndex +
1562
// + columnIndex + ":" + rect);
1563
CellRepaintCallback.this.table.repaint(rect);
1571
* Repaints a single row during the fade animation cycle.
1573
* @author Kirill Grouchnikov
1575
protected class RowRepaintCallback extends UIThreadTimelineCallbackAdapter {
1579
protected JTable table;
1582
* Associated (animated) row index.
1584
protected int rowIndex;
1587
* Creates a new animation repaint callback.
1592
* Associated (animated) row index.
1594
public RowRepaintCallback(JTable table, int rowIndex) {
1597
this.rowIndex = rowIndex;
1601
public void onTimelinePulse(float durationFraction,
1602
float timelinePosition) {
1607
public void onTimelineStateChanged(TimelineState oldState,
1608
TimelineState newState, float durationFraction,
1609
float timelinePosition) {
1614
* Repaints the associated row.
1616
private void repaintRow() {
1617
SwingUtilities.invokeLater(new Runnable() {
1620
if (SubstanceTableUI.this.table == null) {
1621
// may happen if the LAF was switched in the meantime
1624
int rowCount = RowRepaintCallback.this.table.getRowCount();
1626
&& (RowRepaintCallback.this.rowIndex < rowCount)) {
1627
// need to retrieve the cell rectangle since the cells
1628
// can be moved while animating
1629
Rectangle rect = RowRepaintCallback.this.table
1630
.getCellRect(RowRepaintCallback.this.rowIndex,
1632
for (int i = 1; i < RowRepaintCallback.this.table
1633
.getColumnCount(); i++) {
1634
rect = rect.union(RowRepaintCallback.this.table
1636
RowRepaintCallback.this.rowIndex,
1639
if (!table.getShowHorizontalLines()
1640
&& !table.getShowVerticalLines()) {
1641
float extra = SubstanceSizeUtils
1642
.getBorderStrokeWidth(SubstanceSizeUtils
1643
.getComponentFontSize(table
1644
.getTableHeader()));
1645
rect.y -= (int) extra;
1646
rect.height += 2 * (int) extra;
1648
// System.out.println("Repainting row " + rowIndex
1649
// + " at " + rect);
1650
RowRepaintCallback.this.table.repaint(rect);
1658
* Repaints a single column during the fade animation cycle.
1660
* @author Kirill Grouchnikov
1662
protected class ColumnRepaintCallback extends
1663
UIThreadTimelineCallbackAdapter {
1667
protected JTable table;
1670
* Associated (animated) column index.
1672
protected int columnIndex;
1675
* Creates a new animation repaint callback.
1679
* @param columnIndex
1680
* Associated (animated) column index.
1682
public ColumnRepaintCallback(JTable table, int columnIndex) {
1685
this.columnIndex = columnIndex;
1689
public void onTimelinePulse(float durationFraction,
1690
float timelinePosition) {
1691
this.repaintColumn();
1695
public void onTimelineStateChanged(TimelineState oldState,
1696
TimelineState newState, float durationFraction,
1697
float timelinePosition) {
1698
this.repaintColumn();
1702
* Repaints the associated row.
1704
private void repaintColumn() {
1705
SwingUtilities.invokeLater(new Runnable() {
1708
if (SubstanceTableUI.this.table == null) {
1709
// may happen if the LAF was switched in the meantime
1712
int columnCount = ColumnRepaintCallback.this.table
1714
if ((columnCount > 0)
1715
&& (ColumnRepaintCallback.this.columnIndex < columnCount)) {
1716
// need to retrieve the cell rectangle since the cells
1717
// can be moved while animating
1718
Rectangle rect = ColumnRepaintCallback.this.table
1720
ColumnRepaintCallback.this.columnIndex,
1722
for (int i = 1; i < ColumnRepaintCallback.this.table
1723
.getRowCount(); i++) {
1724
rect = rect.union(ColumnRepaintCallback.this.table
1727
ColumnRepaintCallback.this.columnIndex,
1730
if (!table.getShowHorizontalLines()
1731
&& !table.getShowVerticalLines()) {
1732
float extra = SubstanceSizeUtils
1733
.getBorderStrokeWidth(SubstanceSizeUtils
1734
.getComponentFontSize(table
1735
.getTableHeader()));
1736
rect.x -= (int) extra;
1737
rect.width += 2 * (int) extra;
1739
ColumnRepaintCallback.this.table.repaint(rect);
1747
* ID of a single table cell.
1749
* @author Kirill Grouchnikov
1751
public static class TableCellId implements Comparable<TableCellId> {
1760
protected int column;
1763
* Creates a new cell ID.
1770
public TableCellId(int row, int column) {
1772
this.column = column;
1778
* @see java.lang.Comparable#compareTo(java.lang.Object)
1781
public int compareTo(TableCellId o) {
1783
return (column < o.column) ? -1 : ((column == o.column) ? 0 : 1);
1785
return (row < o.row) ? -1 : ((row == o.row) ? 0 : 1);
1792
* @see java.lang.Object#equals(java.lang.Object)
1795
public boolean equals(Object obj) {
1796
if (obj instanceof TableCellId) {
1797
return this.compareTo((TableCellId) obj) == 0;
1805
* @see java.lang.Object#hashCode()
1808
public int hashCode() {
1809
return (this.row ^ (this.row << 16))
1810
& (this.column ^ (this.column << 16));
1816
* @see java.lang.Object#toString()
1819
public String toString() {
1820
return "Row " + this.row + ", Column " + this.column;
1825
* State listener for tracking the selection changes.
1827
* @author Kirill Grouchnikov
1829
protected class TableStateListener implements ListSelectionListener,
1830
TableModelListener, RowSorterListener {
1831
List<SortKey> oldSortKeys = null;
1833
private boolean isSameSorter(List<? extends SortKey> sortKeys1,
1834
List<? extends SortKey> sortKeys2) {
1835
int size1 = (sortKeys1 == null) ? 0 : sortKeys1.size();
1836
int size2 = (sortKeys2 == null) ? 0 : sortKeys2.size();
1837
if ((size1 == 0) && (size2 == 0)) {
1840
if ((sortKeys1 == null) && (sortKeys2 == null))
1842
if ((sortKeys1 == null) || (sortKeys2 == null))
1846
for (int i = 0; i < size1; i++) {
1847
SortKey sortKey1 = sortKeys1.get(i);
1848
SortKey sortKey2 = sortKeys2.get(i);
1849
if ((sortKey1.getColumn() != sortKey2.getColumn())
1850
|| (sortKey1.getSortOrder() != sortKey2.getSortOrder())) {
1861
* javax.swing.event.ListSelectionListener#valueChanged(javax.swing.
1862
* event.ListSelectionEvent)
1865
@SuppressWarnings("unchecked")
1866
public void valueChanged(final ListSelectionEvent e) {
1867
// fix for issue 478 - no animations when sorter has changed
1868
List<? extends SortKey> sortKeys = (table.getRowSorter() == null) ? null
1869
: table.getRowSorter().getSortKeys();
1870
boolean isDifferentSorter = !isSameSorter(sortKeys, oldSortKeys);
1871
if (e.getValueIsAdjusting() && isDifferentSorter)
1873
if (sortKeys == null) {
1876
oldSortKeys = new ArrayList<SortKey>();
1877
for (SortKey sortKey : sortKeys) {
1878
SortKey copy = new SortKey(sortKey.getColumn(),
1879
sortKey.getSortOrder());
1880
oldSortKeys.add(copy);
1883
syncSelection(isDifferentSorter);
1890
* javax.swing.event.TableModelListener#tableChanged(javax.swing.event
1894
public void tableChanged(final TableModelEvent e) {
1895
// fix for defect 291 - tracking changes to the table.
1896
SwingUtilities.invokeLater(new Runnable() {
1899
// fix for defect 350 - font might have been
1900
// switched in the middle of update
1904
// fix for defect 328 - do not clear the
1905
// internal selection and focus tracking
1906
// when the event is table update.
1907
if (e.getType() != TableModelEvent.UPDATE) {
1908
selectedIndices.clear();
1909
stateTransitionMultiTracker.clear();
1910
focusedCellId = null;
1912
syncSelection(true);
1919
public void sorterChanged(RowSorterEvent e) {
1920
SwingUtilities.invokeLater(new Runnable() {
1923
// fix for issue 479 - cancel selection animations
1924
// that are happening due to changes in sorter
1925
stateTransitionMultiTracker.clear();
1932
* Listener for fade animations on table rollovers.
1934
* @author Kirill Grouchnikov
1936
private class RolloverFadeListener implements MouseListener,
1937
MouseMotionListener {
1939
public void mouseClicked(MouseEvent e) {
1943
public void mouseEntered(MouseEvent e) {
1947
public void mousePressed(MouseEvent e) {
1951
public void mouseReleased(MouseEvent e) {
1955
public void mouseExited(MouseEvent e) {
1956
// if (SubstanceCoreUtilities.toBleedWatermark(list))
1962
if (!table.isEnabled())
1965
// check the mouse location. If the cell editor has been shown
1966
// or hidden, we will get a mouseExited() event, but shouldn't
1967
// be changing the rollover indication if the mouse is still
1969
PointerInfo pi = MouseInfo.getPointerInfo();
1970
Point mouseLoc = pi == null ? null : pi.getLocation();
1971
Window windowAncestor = SwingUtilities.getWindowAncestor(table);
1972
if ((mouseLoc != null) && (windowAncestor != null)) {
1973
SwingUtilities.convertPointFromScreen(mouseLoc, windowAncestor);
1974
Component deepest = SwingUtilities.getDeepestComponentAt(
1975
windowAncestor, mouseLoc.x, mouseLoc.y);
1977
while (deepest != null) {
1978
if (deepest == table) {
1982
deepest = deepest.getParent();
1986
fadeOutAllRollovers();
1987
this.fadeOutTableHeader();
1988
rolledOverIndices.clear();
1989
rolledOverColumn = -1;
1993
public void mouseMoved(MouseEvent e) {
1994
if (!SubstanceTableUI.this.table.isEnabled())
1996
handleMouseMove(e.getPoint());
1997
this.handleMoveForHeader(e);
2001
public void mouseDragged(MouseEvent e) {
2002
if (!SubstanceTableUI.this.table.isEnabled())
2004
handleMouseMove(e.getPoint());
2005
this.handleMoveForHeader(e);
2009
* Handles various mouse move events and initiates the fade animation if
2015
private void handleMoveForHeader(MouseEvent e) {
2016
if (!SubstanceTableUI.this.table.getColumnSelectionAllowed())
2018
JTableHeader header = SubstanceTableUI.this.table.getTableHeader();
2019
if ((header == null) || (!header.isVisible()))
2022
TableHeaderUI ui = header.getUI();
2023
if (!(ui instanceof SubstanceTableHeaderUI))
2026
SubstanceTableHeaderUI sthui = (SubstanceTableHeaderUI) ui;
2028
// synchronized (SubstanceTableUI.this.table) {
2029
int row = SubstanceTableUI.this.table.rowAtPoint(e.getPoint());
2030
int column = SubstanceTableUI.this.table
2031
.columnAtPoint(e.getPoint());
2032
if ((row < 0) || (row >= SubstanceTableUI.this.table.getRowCount())
2034
|| (column >= SubstanceTableUI.this.table.getColumnCount())) {
2035
this.fadeOutTableHeader();
2036
// System.out.println("Nulling RO column index");
2037
SubstanceTableUI.this.rolledOverColumn = -1;
2039
// check if this is the same column
2040
if (SubstanceTableUI.this.rolledOverColumn == column)
2043
this.fadeOutTableHeader();
2045
TableColumnModel columnModel = header.getColumnModel();
2046
StateTransitionTracker columnTransitionTracker = sthui
2047
.getTracker(column, false,
2048
columnModel.getColumnSelectionAllowed()
2049
&& columnModel.getSelectionModel()
2050
.isSelectedIndex(column));
2051
columnTransitionTracker.getModel().setRollover(true);
2053
SubstanceTableUI.this.rolledOverColumn = column;
2059
* Initiates the fade out effect.
2061
private void fadeOutTableHeader() {
2062
if (SubstanceTableUI.this.rolledOverColumn >= 0) {
2063
JTableHeader header = SubstanceTableUI.this.table
2065
if ((header == null) || (!header.isVisible()))
2067
SubstanceTableHeaderUI ui = (SubstanceTableHeaderUI) header
2070
TableColumnModel columnModel = header.getColumnModel();
2071
StateTransitionTracker columnTransitionTracker = ui.getTracker(
2072
rolledOverColumn, true,
2073
columnModel.getColumnSelectionAllowed()
2074
&& columnModel.getSelectionModel()
2075
.isSelectedIndex(rolledOverColumn));
2076
columnTransitionTracker.getModel().setRollover(false);
2081
* Handles various mouse move events and initiates the fade animation if
2087
private void handleMouseMove(Point mousePoint) {
2088
// synchronized (SubstanceTableUI.this.table) {
2089
int row = table.rowAtPoint(mousePoint);
2090
int column = table.columnAtPoint(mousePoint);
2091
if ((row < 0) || (row >= table.getRowCount()) || (column < 0)
2092
|| (column >= table.getColumnCount())) {
2093
this.fadeOutAllRollovers();
2094
// System.out.println("Nulling RO index in handleMove()");
2095
// table.putClientProperty(ROLLED_OVER_INDEX, null);
2096
rolledOverIndices.clear();
2098
// check if this is the same index
2099
boolean hasRowSelection = table.getRowSelectionAllowed();
2100
boolean hasColumnSelection = table.getColumnSelectionAllowed();
2101
int startRolloverRow = row;
2102
int endRolloverRow = row;
2103
int startRolloverColumn = column;
2104
int endRolloverColumn = column;
2105
if (hasRowSelection && !hasColumnSelection) {
2106
startRolloverColumn = 0;
2107
endRolloverColumn = table.getColumnCount() - 1;
2109
if (!hasRowSelection && hasColumnSelection) {
2110
startRolloverRow = 0;
2111
endRolloverRow = table.getRowCount() - 1;
2113
Set<TableCellId> toRemove = new HashSet<TableCellId>();
2114
for (TableCellId currRolloverId : rolledOverIndices) {
2115
if ((currRolloverId.row < startRolloverRow)
2116
|| (currRolloverId.row > endRolloverRow)
2117
|| (currRolloverId.column < startRolloverColumn)
2118
|| (currRolloverId.column > endRolloverColumn)) {
2119
fadeOutRollover(currRolloverId);
2120
toRemove.add(currRolloverId);
2123
for (TableCellId id : toRemove) {
2124
rolledOverIndices.remove(id);
2127
int totalRolloverCount = (endRolloverRow - startRolloverRow + 1)
2128
* (endRolloverColumn - startRolloverColumn + 1);
2129
if (totalRolloverCount > 20) {
2130
for (int i = startRolloverRow; i <= endRolloverRow; i++) {
2131
for (int j = startRolloverColumn; j <= endRolloverColumn; j++) {
2132
rolledOverIndices.add(new TableCellId(i, j));
2137
for (int i = startRolloverRow; i <= endRolloverRow; i++) {
2138
for (int j = startRolloverColumn; j <= endRolloverColumn; j++) {
2139
TableCellId currCellId = new TableCellId(i, j);
2140
if (rolledOverIndices.contains(currCellId))
2143
// .println("Getting rollover/in tracker for "
2145
StateTransitionTracker tracker = getTracker(
2148
getCellState(currCellId).isFacetActive(
2149
ComponentStateFacet.SELECTION));
2150
tracker.getModel().setRollover(true);
2152
rolledOverIndices.add(currCellId);
2160
* Initiates the fade out effect.
2162
private void fadeOutRollover(TableCellId tableCellId) {
2163
if (rolledOverIndices.contains(tableCellId)) {
2165
// .println("Getting rollover/out tracker for " + cellId);
2166
StateTransitionTracker tracker = getTracker(
2169
getCellState(tableCellId).isFacetActive(
2170
ComponentStateFacet.SELECTION));
2171
tracker.getModel().setRollover(false);
2175
private void fadeOutAllRollovers() {
2176
if (rolledOverIndices.size() < 20) {
2177
for (TableCellId tcid : rolledOverIndices) {
2178
fadeOutRollover(tcid);
2185
* Returns a comparable ID for the specified location.
2191
* @return Comparable ID for the specified location.
2193
public TableCellId getId(int row, int column) {
2194
cellId.column = column;
2202
* Synchronizes the current selection state.
2204
* @param enforceNoAnimations
2205
* Whether to force no animations.
2207
// @SuppressWarnings("unchecked")
2208
protected void syncSelection(boolean enforceNoAnimations) {
2209
if (this.table == null) {
2210
// fix for defect 270 - if the UI delegate is updated
2211
// by another selection listener, ignore this
2215
int rows = this.table.getRowCount();
2216
int cols = this.table.getColumnCount();
2218
int rowLeadIndex = this.table.getSelectionModel()
2219
.getLeadSelectionIndex();
2220
int colLeadIndex = this.table.getColumnModel().getSelectionModel()
2221
.getLeadSelectionIndex();
2222
boolean isFocusOwner = this.table.isFocusOwner();
2224
// fix for defect 209 - selection very slow on large tables with
2225
// column selection set to true and row selection set to false.
2226
// Solution - no selection animations on tables with more than 1000
2228
if (!this._hasSelectionAnimations()) {
2229
stateTransitionMultiTracker.clear();
2230
// this.prevStateMap.clear();
2233
// fix for issue 414 - track focus on tables
2234
// without selection animations
2236
this.focusedCellId = new TableCellId(rowLeadIndex, colLeadIndex);
2241
Set<StateTransitionTracker> initiatedTrackers = new HashSet<StateTransitionTracker>();
2243
for (int i = 0; i < rows; i++) {
2244
for (int j = 0; j < cols; j++) {
2245
TableCellId cellId = new TableCellId(i, j);
2246
if (this.table.isCellSelected(i, j)) {
2247
// check if was selected before
2248
if (!this.selectedIndices.containsKey(cellId)) {
2250
if (!enforceNoAnimations) {
2252
// .println("Getting selection/in tracker for "
2254
StateTransitionTracker tracker = getTracker(
2256
getCellState(cellId).isFacetActive(
2257
ComponentStateFacet.ROLLOVER),
2259
tracker.getModel().setSelected(true);
2261
// .println("Selecting previously unselected "
2263
initiatedTrackers.add(tracker);
2264
if (initiatedTrackers.size() > 20) {
2265
stateTransitionMultiTracker.clear();
2266
initiatedTrackers.clear();
2267
enforceNoAnimations = true;
2271
this.selectedIndices.put(cellId,
2272
this.table.getValueAt(i, j));
2275
// check if was selected before and still points
2276
// to the same element
2277
if (this.selectedIndices.containsKey(cellId)) {
2278
// corner case when the model returns null
2279
Object oldValue = this.selectedIndices.get(cellId);
2280
if ((i >= this.table.getModel().getRowCount())
2281
|| (j >= this.table.getModel().getColumnCount())) {
2282
// not only the content changed, but the model
2283
// dimensions as well
2286
Object currValue = this.table.getValueAt(i, j);
2288
if (oldValue == null) {
2289
isSame = (currValue == null);
2291
isSame = oldValue.equals(currValue);
2295
if (!enforceNoAnimations) {
2297
// .println("Getting selection/out tracker for "
2299
StateTransitionTracker tracker = getTracker(
2301
getCellState(cellId).isFacetActive(
2302
ComponentStateFacet.ROLLOVER),
2304
tracker.getModel().setSelected(false);
2306
// .println("Unselecting previously selected "
2309
initiatedTrackers.add(tracker);
2310
if (initiatedTrackers.size() > 20) {
2311
stateTransitionMultiTracker.clear();
2312
initiatedTrackers.clear();
2313
enforceNoAnimations = true;
2317
this.selectedIndices.remove(cellId);
2321
// handle focus animations
2322
boolean cellHasFocus = isFocusOwner && (i == rowLeadIndex)
2323
&& (j == colLeadIndex);
2325
// check if it's a different cell
2326
if ((this.focusedCellId == null)
2327
|| !this.focusedCellId.equals(cellId)) {
2328
if (!enforceNoAnimations) {
2329
if (this.focusedCellId != null) {
2330
// fade out the previous focus holder
2332
ComponentState cellState = getCellState(this.focusedCellId);
2333
// System.out.println("Getting focus/out tracker for "
2335
StateTransitionTracker tracker = getTracker(
2338
.isFacetActive(ComponentStateFacet.ROLLOVER),
2340
.isFacetActive(ComponentStateFacet.SELECTION));
2341
tracker.setFocusState(false);
2344
// fade in the current cell (new focus holder)
2345
ComponentState cellState = getCellState(cellId);
2347
// System.out.println("Getting focus/in tracker for "
2349
StateTransitionTracker tracker = getTracker(
2352
.isFacetActive(ComponentStateFacet.ROLLOVER),
2354
.isFacetActive(ComponentStateFacet.SELECTION));
2355
tracker.setFocusState(true);
2358
if (AnimationConfigurationManager.getInstance()
2359
.isAnimationAllowed(AnimationFacet.FOCUS,
2361
// and store it for future checks
2362
this.focusedCellId = new TableCellId(i, j);
2366
// check if previously it held focus
2367
if (cellId.equals(this.focusedCellId)) {
2368
if (!enforceNoAnimations) {
2370
ComponentState cellState = getCellState(cellId);
2371
// System.out.println("Getting focus/out tracker for "
2373
StateTransitionTracker tracker = getTracker(
2376
.isFacetActive(ComponentStateFacet.ROLLOVER),
2378
.isFacetActive(ComponentStateFacet.SELECTION));
2379
tracker.setFocusState(false);
2382
this.focusedCellId = null;
2390
* Returns the current state for the specified cell.
2394
* @return The current state for the specified cell.
2396
public ComponentState getCellState(TableCellId cellIndex) {
2397
boolean isEnabled = this.table.isEnabled();
2399
StateTransitionTracker tracker = this.stateTransitionMultiTracker
2400
.getTracker(cellIndex);
2401
if (tracker == null) {
2402
int row = cellIndex.row;
2403
int column = cellIndex.column;
2404
TableCellId cellId = this.getId(row, column);
2405
boolean isRollover = rolledOverIndices.contains(cellId);
2407
boolean hasSelectionAnimations = (this.updateInfo != null) ? this.updateInfo.hasSelectionAnimations
2408
: this._hasSelectionAnimations();
2409
if (hasSelectionAnimations
2410
&& AnimationConfigurationManager
2412
.isAnimationAllowed(AnimationFacet.SELECTION, table))
2413
isSelected = this.selectedIndices.containsKey(cellId);
2415
isSelected = this.table.isCellSelected(row, column);
2417
return ComponentState.getState(isEnabled, isRollover, isSelected);
2419
ComponentState fromTracker = tracker.getModelStateInfo()
2420
.getCurrModelState();
2421
return ComponentState.getState(isEnabled,
2422
fromTracker.isFacetActive(ComponentStateFacet.ROLLOVER),
2423
fromTracker.isFacetActive(ComponentStateFacet.SELECTION));
2428
* Returns the current state for the specified cell.
2432
* @return The current state for the specified cell.
2434
public StateTransitionTracker.ModelStateInfo getModelStateInfo(
2435
TableCellId cellId) {
2436
if (this.stateTransitionMultiTracker.size() == 0)
2438
StateTransitionTracker tracker = this.stateTransitionMultiTracker
2439
.getTracker(cellId);
2440
if (tracker == null) {
2443
return tracker.getModelStateInfo();
2448
* Checks whether the table has animations.
2450
* @return <code>true</code> if the table has animations, <code>false</code>
2453
protected boolean _hasAnimations() {
2454
// fix for defects 164 and 209 - selection
2455
// and deletion are very slow on large tables.
2456
int rowCount = this.table.getRowCount();
2457
int colCount = this.table.getColumnCount();
2458
if (rowCount * colCount >= 500)
2460
if (this.table.getColumnSelectionAllowed()
2461
&& !this.table.getRowSelectionAllowed()) {
2462
if (!this.table.getShowHorizontalLines()
2463
&& !this.table.getShowVerticalLines())
2464
return rowCount <= 10;
2465
return rowCount <= 25;
2467
if (!this.table.getColumnSelectionAllowed()
2468
&& this.table.getRowSelectionAllowed()) {
2469
if (!this.table.getShowHorizontalLines()
2470
&& !this.table.getShowVerticalLines())
2471
return colCount <= 10;
2472
return colCount <= 25;
2478
* Checks whether the table has selection animations.
2480
* @return <code>true</code> if the table has selection animations,
2481
* <code>false</code> otherwise.
2483
protected boolean _hasSelectionAnimations() {
2484
return this._hasAnimations()
2485
&& !LafWidgetUtilities.hasNoAnimations(this.table,
2486
AnimationFacet.SELECTION);
2490
* Checks whether the table has rollover animations.
2492
* @return <code>true</code> if the table has rollover animations,
2493
* <code>false</code> otherwise.
2495
protected boolean _hasRolloverAnimations() {
2496
return this._hasAnimations()
2497
&& !LafWidgetUtilities.hasNoAnimations(this.table,
2498
AnimationFacet.ROLLOVER);
2502
* Returns the index of the rollover column.
2504
* @return The index of the rollover column.
2506
public int getRolloverColumnIndex() {
2507
return this.rolledOverColumn;
2511
* Returns indication whether the specified cell has focus.
2516
* Cell column index.
2517
* @return <code>true</code> If the focus is on the specified cell,
2518
* <code>false</code> otherwise.
2520
public boolean isFocusedCell(int row, int column) {
2521
return (this.focusedCellId != null) && (this.focusedCellId.row == row)
2522
&& (this.focusedCellId.column == column);
2528
* @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
2529
* javax.swing.JComponent)
2532
public void update(Graphics g, JComponent c) {
2533
BackgroundPaintingUtils.updateIfOpaque(g, c);
2534
Graphics2D g2d = (Graphics2D) g.create();
2535
SubstanceStripingUtils.setup(c);
2536
this.updateInfo = new TableUpdateOptimizationInfo();
2538
SubstanceStripingUtils.tearDown(c);
2540
this.updateInfo = null;
2544
* Returns the cell renderer insets of this table. Is for internal use only.
2546
* @return The cell renderer insets of this table.
2548
public Insets getCellRendererInsets() {
2549
return this.cellRendererInsets;
2552
// public SubstanceColorScheme getDefaultColorScheme() {
2553
// if (this.updateInfo != null)
2554
// return this.updateInfo.defaultScheme;
2558
// public SubstanceColorScheme getHighlightColorScheme(ComponentState state)
2560
// if (this.updateInfo != null)
2561
// return updateInfo.getHighlightColorScheme(state);
2565
public boolean hasSelectionAnimations() {
2566
if (this.updateInfo != null)
2567
return this.updateInfo.hasSelectionAnimations;
2568
return this._hasSelectionAnimations();
2571
public boolean hasRolloverAnimations() {
2572
if (this.updateInfo != null)
2573
return this.updateInfo.hasRolloverAnimations;
2574
return this._hasRolloverAnimations();
2577
private TableUpdateOptimizationInfo updateInfo;
2579
private class TableUpdateOptimizationInfo extends UpdateOptimizationInfo {
2580
public boolean hasSelectionAnimations;
2582
public boolean hasRolloverAnimations;
2584
public TableUpdateOptimizationInfo() {
2586
this.hasSelectionAnimations = _hasSelectionAnimations();
2587
this.hasRolloverAnimations = _hasRolloverAnimations();
2592
public UpdateOptimizationInfo getUpdateOptimizationInfo() {
2593
return this.updateInfo;
2596
private boolean isSubstanceDefaultRenderer(Object instance) {
2597
return (instance instanceof SubstanceDefaultTableCellRenderer)
2598
|| (instance instanceof SubstanceDefaultTableCellRenderer.BooleanRenderer);
2601
private boolean isSubstanceDefaultEditor(TableCellEditor editor) {
2602
return (editor instanceof BooleanEditor);
2605
private Rectangle getCellRectangleForRepaint(int row, int column) {
2606
Rectangle rect = this.table.getCellRect(row, column, true);
2608
if (!table.getShowHorizontalLines() && !table.getShowVerticalLines()) {
2609
float extra = SubstanceSizeUtils
2610
.getBorderStrokeWidth(SubstanceSizeUtils
2611
.getComponentFontSize(table.getTableHeader()));
2612
rect.x -= (int) extra;
2613
rect.width += 2 * (int) extra;
2614
rect.y -= (int) extra;
2615
rect.height += 2 * (int) extra;
2620
private StateTransitionTracker getTracker(final TableCellId tableCellId,
2621
boolean initialRollover, boolean initialSelected) {
2622
StateTransitionTracker tracker = stateTransitionMultiTracker
2623
.getTracker(tableCellId);
2624
// System.out.println("TableID " + tableCellId + " has tracker "
2625
// + ((tracker == null) ? "null" : ("@" + tracker.hashCode())));
2626
if (tracker == null) {
2627
ButtonModel model = new DefaultButtonModel();
2628
model.setSelected(initialSelected);
2629
model.setRollover(initialRollover);
2630
tracker = new StateTransitionTracker(table, model);
2631
tracker.registerModelListeners();
2632
tracker.setRepaintCallback(new RepaintCallback() {
2634
public TimelineCallback getRepaintCallback() {
2635
return new CellRepaintCallback(table, tableCellId.row,
2636
tableCellId.column);
2639
tracker.setName("row " + tableCellId.row + ", col "
2640
+ tableCellId.column);
2641
// System.out.println("TableID " + tableCellId +
2642
// " has new tracker @"
2643
// + tracker.hashCode());
2644
stateTransitionMultiTracker.addTracker(tableCellId, tracker);
2649
public StateTransitionTracker getStateTransitionTracker(TableCellId tableId) {
2650
return this.stateTransitionMultiTracker.getTracker(tableId);