/* * 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.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.Line2D; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import javax.swing.ButtonModel; import javax.swing.DefaultButtonModel; import javax.swing.JComponent; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.JViewport; import javax.swing.ListSelectionModel; import javax.swing.SwingUtilities; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.TableUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTableHeaderUI; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableColumnModel; 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.renderers.SubstanceDefaultTableHeaderCellRenderer; import org.pushingpixels.substance.internal.animation.StateTransitionMultiTracker; import org.pushingpixels.substance.internal.animation.StateTransitionTracker; import org.pushingpixels.substance.internal.painter.HighlightPainterUtils; import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities; import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities; import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils; import org.pushingpixels.trident.Timeline.TimelineState; import org.pushingpixels.trident.callback.UIThreadTimelineCallbackAdapter; /** * UI for table headers in <b>Substance</b> look and feel. * * @author Kirill Grouchnikov */ public class SubstanceTableHeaderUI extends BasicTableHeaderUI { /** * Repaints the header on column selection. */ protected TableHeaderListener substanceHeaderListener; /** * The default renderer. */ protected TableCellRenderer defaultHeaderRenderer; /** * Holds the list of currently selected indices. */ protected Map<Integer, Object> selectedIndices; /** * Listener for transition animations on list selections. */ protected ListSelectionListener substanceFadeSelectionListener; private StateTransitionMultiTracker<Integer> stateTransitionMultiTracker; /** * Property change listener. */ protected PropertyChangeListener substancePropertyChangeListener; /** * Listener for table header. * * @author Kirill Grouchnikov */ private static class TableHeaderListener implements ListSelectionListener { /** * The associated table header UI. */ private SubstanceTableHeaderUI ui; /** * Simple constructor. * * @param ui * The associated table header UI */ public TableHeaderListener(SubstanceTableHeaderUI ui) { this.ui = ui; } /* * (non-Javadoc) * * @see * javax.swing.event.ListSelectionListener#valueChanged(javax.swing. * event.ListSelectionEvent) */ public void valueChanged(ListSelectionEvent e) { if (ui.header == null) return; if (ui.header.isValid()) ui.header.repaint(); } } /* * (non-Javadoc) * * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent) */ public static ComponentUI createUI(JComponent comp) { SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp); return new SubstanceTableHeaderUI(); } /** * Creates a new UI delegate. */ public SubstanceTableHeaderUI() { this.stateTransitionMultiTracker = new StateTransitionMultiTracker<Integer>(); selectedIndices = new HashMap<Integer, Object>(); } /* * (non-Javadoc) * * @see javax.swing.plaf.basic.BasicTableHeaderUI#installListeners() */ @Override protected void installListeners() { super.installListeners(); TableColumnModel columnModel = header.getColumnModel(); if (columnModel != null) { ListSelectionModel lsm = columnModel.getSelectionModel(); if (lsm != null) { // fix for defect 109 - memory leak on skin switch substanceHeaderListener = new TableHeaderListener(this); lsm.addListSelectionListener(substanceHeaderListener); } } // Add listener for the selection animation this.substanceFadeSelectionListener = new ListSelectionListener() { public void valueChanged(ListSelectionEvent e) { if (header == null) return; if (!header.getColumnModel().getColumnSelectionAllowed()) return; // fix for issue 367 - check that there is an associated // table and that it has a substance UI delegate JTable table = header.getTable(); if (table == null) return; TableUI ui = table.getUI(); if (!(ui instanceof SubstanceTableUI)) { stateTransitionMultiTracker.clear(); return; } // optimization on large tables and large selections // and syncing the fade presence with the table // (issue 309) SubstanceTableUI tableUI = (SubstanceTableUI) ui; // System.out.println("Sel anim: " // + tableUI.hasSelectionAnimations()); if (!tableUI._hasSelectionAnimations()) { stateTransitionMultiTracker.clear(); return; } // Set<Long> initiatedFadeSequences = new HashSet<Long>(); Set<StateTransitionTracker> initiatedTrackers = new HashSet<StateTransitionTracker>(); boolean fadeCanceled = false; // if (SubstanceCoreUtilities.toBleedWatermark(list)) // return; // FadeTracker fadeTrackerInstance = FadeTracker.getInstance(); TableColumnModel columnModel = header.getColumnModel(); int size = columnModel.getColumnCount(); ListSelectionModel lsm = columnModel.getSelectionModel(); for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) { if (i >= size) continue; if (lsm.isSelectedIndex(i)) { // check if was selected before if (!selectedIndices.containsKey(i)) { // start fading in // System.out.println("Fade in on index " + i); if (!fadeCanceled) { StateTransitionTracker tracker = getTracker(i, getColumnState(i) .isFacetActive(ComponentStateFacet.ROLLOVER), false); tracker.getModel().setSelected(true); initiatedTrackers.add(tracker); if (initiatedTrackers.size() > 15) { stateTransitionMultiTracker.clear(); initiatedTrackers.clear(); fadeCanceled = true; } } selectedIndices.put(i, columnModel.getColumn(i)); } } else { // check if was selected before and still points to the // same element if (selectedIndices.containsKey(i)) { if (selectedIndices.get(i) == columnModel.getColumn(i)) { // start fading out // System.out.println("Fade out on index " + i); if (!fadeCanceled) { StateTransitionTracker tracker = getTracker(i, getColumnState(i) .isFacetActive(ComponentStateFacet.ROLLOVER), true); tracker.getModel().setSelected(false); initiatedTrackers.add(tracker); if (initiatedTrackers.size() > 15) { stateTransitionMultiTracker.clear(); initiatedTrackers.clear(); fadeCanceled = true; } } } selectedIndices.remove(i); } } } } }; if (columnModel != null) { ListSelectionModel lsm = columnModel.getSelectionModel(); if (lsm != null) { lsm.addListSelectionListener(substanceFadeSelectionListener); } } this.substancePropertyChangeListener = (PropertyChangeEvent evt) -> { if ("table".equals(evt.getPropertyName())) { // track changes to the table and re-register the // column model listener to the new table. TableColumnModel oldModel = (evt.getOldValue() instanceof JTable) ? ((JTable) evt.getOldValue()).getColumnModel() : null; TableColumnModel newModel = (evt.getNewValue() instanceof JTable) ? ((JTable) evt.getNewValue()).getColumnModel() : null; processColumnModelChangeEvent(oldModel, newModel); } }; this.header.addPropertyChangeListener(this.substancePropertyChangeListener); } /* * (non-Javadoc) * * @see javax.swing.plaf.basic.BasicTableHeaderUI#installDefaults() */ @Override protected void installDefaults() { super.installDefaults(); defaultHeaderRenderer = header.getDefaultRenderer(); if (defaultHeaderRenderer instanceof UIResource) { header.setDefaultRenderer(new SubstanceDefaultTableHeaderCellRenderer()); } for (int i = 0; i < header.getColumnModel().getColumnCount(); i++) { if (header.getColumnModel().getSelectionModel().isSelectedIndex(i)) { selectedIndices.put(i, header.getColumnModel().getColumn(i)); } } } /* * (non-Javadoc) * * @see javax.swing.plaf.basic.BasicTableHeaderUI#uninstallListeners() */ @Override protected void uninstallListeners() { // fix for defect 109 - memory leak on skin switch TableColumnModel columnModel = header.getColumnModel(); if (columnModel != null) { ListSelectionModel lsm = columnModel.getSelectionModel(); if (lsm != null) { lsm.removeListSelectionListener(substanceHeaderListener); substanceHeaderListener = null; } } this.header.removePropertyChangeListener(this.substancePropertyChangeListener); this.substancePropertyChangeListener = null; super.uninstallListeners(); } /* * (non-Javadoc) * * @see javax.swing.plaf.basic.BasicTableHeaderUI#uninstallDefaults() */ @Override protected void uninstallDefaults() { super.uninstallDefaults(); selectedIndices.clear(); if (header.getDefaultRenderer() instanceof SubstanceDefaultTableHeaderCellRenderer) { header.setDefaultRenderer(defaultHeaderRenderer); if (defaultHeaderRenderer instanceof Component) SwingUtilities.updateComponentTreeUI((Component) defaultHeaderRenderer); } } /* * (non-Javadoc) * * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics, * javax.swing.JComponent) */ @Override public void paint(Graphics g, JComponent c) { if (header.getColumnModel().getColumnCount() <= 0) { return; } boolean ltr = header.getComponentOrientation().isLeftToRight(); Rectangle clip = g.getClipBounds(); Point left = clip.getLocation(); Point right = new Point(clip.x + clip.width - 1, clip.y); TableColumnModel cm = header.getColumnModel(); int[] selectedColumns = cm.getSelectedColumns(); Set<Integer> selected = new HashSet<Integer>(); for (int sel : selectedColumns) selected.add(sel); int cMin = header.columnAtPoint(ltr ? left : right); int cMax = header.columnAtPoint(ltr ? right : left); // 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 = cm.getColumnCount() - 1; } TableColumn draggedColumn = header.getDraggedColumn(); int columnWidth; Rectangle cellRect = header.getHeaderRect(ltr ? cMin : cMax); TableColumn aColumn; if (ltr) { for (int column = cMin; column <= cMax; column++) { aColumn = cm.getColumn(column); columnWidth = aColumn.getWidth(); cellRect.width = columnWidth; if (aColumn != draggedColumn) { this.paintCell(g, cellRect, column, selected.contains(column)); } cellRect.x += columnWidth; } } else { for (int column = cMax; column >= cMin; column--) { aColumn = cm.getColumn(column); columnWidth = aColumn.getWidth(); cellRect.width = columnWidth; if (aColumn != draggedColumn) { this.paintCell(g, cellRect, column, selected.contains(column)); } cellRect.x += columnWidth; } } this.paintGrid(g, c); // Paint the dragged column if we are dragging. if (draggedColumn != null) { int draggedColumnIndex = viewIndexForColumn(draggedColumn); Rectangle draggedCellRect = header.getHeaderRect(draggedColumnIndex); // Draw a gray well in place of the moving column. g.setColor(header.getParent().getBackground()); g.fillRect(draggedCellRect.x, draggedCellRect.y, draggedCellRect.width, draggedCellRect.height); draggedCellRect.x += header.getDraggedDistance(); // Fill the background. g.setColor(header.getBackground()); g.fillRect(draggedCellRect.x, draggedCellRect.y, draggedCellRect.width, draggedCellRect.height); this.paintCell(g, draggedCellRect, draggedColumnIndex, selected.contains(draggedColumnIndex)); } // Remove all components in the rendererPane. rendererPane.removeAll(); } /** * Retrieves renderer for the specified column header. * * @param columnIndex * Column index. * @return Renderer for the specified column header. */ private Component getHeaderRenderer(int columnIndex) { TableColumn aColumn = header.getColumnModel().getColumn(columnIndex); TableCellRenderer renderer = aColumn.getHeaderRenderer(); if (renderer == null) { renderer = header.getDefaultRenderer(); } return renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false, -1, columnIndex); } /** * * * @param g * @param c */ protected void paintGrid(Graphics g, JComponent c) { boolean ltr = header.getComponentOrientation().isLeftToRight(); Graphics2D g2d = (Graphics2D) g.create(); Rectangle clip = g.getClipBounds(); Point left = clip.getLocation(); // tweak the points for issue 378 - making sure that the // grid lines are repainted correctly on scroll. int lineWeight = (int) Math.ceil(SubstanceSizeUtils.getBorderStrokeWidth()); left = new Point(left.x - 2 * lineWeight, left.y); Point right = new Point(clip.x + clip.width + 2 * lineWeight, clip.y); TableColumnModel cm = header.getColumnModel(); int cMin = header.columnAtPoint(ltr ? left : right); int cMax = header.columnAtPoint(ltr ? right : left); // This should never happen. if (cMin == -1) { cMin = 0; } Rectangle cellRect0 = header.getHeaderRect(cMin); // int top = cellRect0.y; int bottom = cellRect0.y + cellRect0.height; Color gridColor = getGridColor(this.header); float strokeWidth = SubstanceSizeUtils.getBorderStrokeWidth(); g2d.setStroke(new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setColor(gridColor); g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.header, 0.7f, g)); float bottomLineY = bottom - strokeWidth / 2; g2d.draw(new Line2D.Float((float) left.getX(), bottomLineY, (float) right.getX(), bottomLineY)); // 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 = cm.getColumnCount() - 1; } TableColumn draggedColumn = this.header.getDraggedColumn(); int columnWidth; Rectangle cellRect = this.header.getHeaderRect(ltr ? cMin : cMax); TableColumn aColumn; if (ltr) { for (int column = cMin; column <= cMax; column++) { aColumn = cm.getColumn(column); columnWidth = aColumn.getWidth(); cellRect.width = columnWidth; if (aColumn != draggedColumn) { if (hasLeadingVerticalGridLine(header, cellRect, column)) { g2d.drawLine(cellRect.x, cellRect.y, cellRect.x, bottom); } if (hasTrailingVerticalGridLine(header, cellRect, column)) { g2d.drawLine(cellRect.x + cellRect.width - 1, cellRect.y, cellRect.x + cellRect.width - 1, bottom); } } cellRect.x += columnWidth; } } else { for (int column = cMax; column >= cMin; column--) { aColumn = cm.getColumn(column); columnWidth = aColumn.getWidth(); cellRect.width = columnWidth; if (aColumn != draggedColumn) { if (hasLeadingVerticalGridLine(header, cellRect, column)) { g2d.drawLine(cellRect.x + cellRect.width - 1, cellRect.y, cellRect.x + cellRect.width - 1, bottom); } if (hasTrailingVerticalGridLine(header, cellRect, column)) { g2d.drawLine(cellRect.x, cellRect.y, cellRect.x, bottom); } } cellRect.x += columnWidth; } } g2d.dispose(); } private boolean hasTrailingVerticalGridLine(JTableHeader tableHeader, Rectangle cellRect, int column) { boolean toDrawLine = (column != (tableHeader.getColumnModel().getColumnCount() - 1)); if (!toDrawLine) { Container parent = this.header.getParent(); if (tableHeader.getComponentOrientation().isLeftToRight()) { toDrawLine = (parent != null) && (parent.getWidth() > (cellRect.x + cellRect.width)); } else { toDrawLine = (parent != null) && (cellRect.x > 0); } } return toDrawLine; } private boolean hasLeadingVerticalGridLine(JTableHeader tableHeader, Rectangle cellRect, int column) { if (column != 0) { return false; } Container parent = tableHeader.getParent(); if (parent instanceof JViewport) { Container grand = parent.getParent(); if (grand instanceof JScrollPane) { return (((JScrollPane) grand).getRowHeader() != null); } } return false; } /** * Returns the grid color for the table header. * * @param header * Table header. * @return Grid color. */ protected static Color getGridColor(JTableHeader header) { boolean isEnabled = header.isEnabled(); if (header.getTable() != null) { // fix for issue 472 - handle standalone table headers isEnabled = isEnabled && header.getTable().isEnabled(); } ComponentState currState = isEnabled ? ComponentState.ENABLED : ComponentState.DISABLED_UNSELECTED; Color gridColor = SubstanceColorSchemeUtilities .getColorScheme(header, ColorSchemeAssociationKind.BORDER, currState) .getLineColor(); return gridColor; } /** * Paints cell. * * @param g * Graphic context. * @param cellRect * Cell rectangle. * @param columnIndex * Column index. * @param isSelected * Selection indication. */ private void paintCell(Graphics g, Rectangle cellRect, int columnIndex, boolean isSelected) { Graphics2D g2d = (Graphics2D) g.create(); g2d.setComposite(LafWidgetUtilities.getAlphaComposite(header, g)); // paint default background Component component = getHeaderRenderer(columnIndex); StateTransitionTracker.ModelStateInfo modelStateInfo = getModelStateInfo(columnIndex); Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = ((modelStateInfo == null) ? null : modelStateInfo.getStateContributionMap()); ComponentState currState = ((modelStateInfo == null) ? getColumnState(columnIndex) : modelStateInfo.getCurrModelState()); boolean hasHighlights = false; if (activeStates != null) { for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates .entrySet()) { hasHighlights = (SubstanceColorSchemeUtilities.getHighlightAlpha(this.header, stateEntry.getKey()) * stateEntry.getValue().getContribution() > 0.0f); if (hasHighlights) break; } } else { hasHighlights = (SubstanceColorSchemeUtilities.getHighlightAlpha(this.header, currState) > 0.0f); } // System.out.println(row + ":" + prevTheme.getDisplayName() + "[" // + alphaForPrevBackground + "]:" + currTheme.getDisplayName() // + "[" + alphaForCurrBackground + "]"); if (hasHighlights) { if (activeStates == null) { float alpha = SubstanceColorSchemeUtilities.getHighlightAlpha(this.header, currState); if (alpha > 0.0f) { SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities.getColorScheme( this.header, ColorSchemeAssociationKind.HIGHLIGHT_TEXT, currState); SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities .getColorScheme(this.header, ColorSchemeAssociationKind.HIGHLIGHT_BORDER, currState); g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.header, alpha, g)); HighlightPainterUtils.paintHighlight(g2d, this.rendererPane, rendererPane, cellRect, 0.8f, null, fillScheme, borderScheme); g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.header, g)); } } else { for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> stateEntry : activeStates .entrySet()) { ComponentState activeState = stateEntry.getKey(); float alpha = SubstanceColorSchemeUtilities.getHighlightAlpha(this.header, activeState) * stateEntry.getValue().getContribution(); if (alpha == 0.0f) continue; SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities.getColorScheme( this.header, ColorSchemeAssociationKind.HIGHLIGHT_TEXT, activeState); SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities .getColorScheme(this.header, ColorSchemeAssociationKind.HIGHLIGHT_BORDER, activeState); g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.header, alpha, g)); HighlightPainterUtils.paintHighlight(g2d, this.rendererPane, rendererPane, cellRect, 0.8f, null, fillScheme, borderScheme); g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.header, g)); } } } rendererPane.paintComponent(g2d, component, header, cellRect.x, cellRect.y, cellRect.width, cellRect.height, true); g2d.dispose(); } /** * Retrieves view index for the specified column. * * @param aColumn * Table column. * @return View index for the specified column. */ private int viewIndexForColumn(TableColumn aColumn) { TableColumnModel cm = header.getColumnModel(); for (int column = 0; column < cm.getColumnCount(); column++) { if (cm.getColumn(column) == aColumn) { return column; } } return -1; } /* * (non-Javadoc) * * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics, * javax.swing.JComponent) */ @Override public void update(Graphics g, JComponent c) { // fix for issue 175 - table header under resize mode off // was painted in color scheme-agnostic (gray) color. boolean isEnabled = this.header.isEnabled(); if (this.header.getTable() != null) { // fix for issue 472 - handle standalone table headers isEnabled = isEnabled && this.header.getTable().isEnabled(); } ComponentState backgroundState = isEnabled ? ComponentState.ENABLED : ComponentState.DISABLED_UNSELECTED; // fix for issue 360 - respect the clip bounds of the // table header Rectangle clip = g.getClipBounds(); if (clip == null) clip = c.getBounds(); // do not use the highlight scheme for painting the // table header background SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities.getColorScheme(c, backgroundState); SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities.getColorScheme(c, ColorSchemeAssociationKind.HIGHLIGHT_BORDER, backgroundState); HighlightPainterUtils.paintHighlight(g, null, c, clip, 0.0f, null, fillScheme, borderScheme); Graphics2D g2d = (Graphics2D) g.create(); RenderingUtils.installDesktopHints(g2d, c); paint(g2d, c); g2d.dispose(); } /* * (non-Javadoc) * * @see javax.swing.plaf.basic.BasicTableHeaderUI#uninstallUI(javax.swing. * JComponent ) */ @Override public void uninstallUI(JComponent c) { for (int i = 0; i < header.getColumnModel().getColumnCount(); i++) { TableColumn aColumn = header.getColumnModel().getColumn(i); TableCellRenderer renderer = aColumn.getHeaderRenderer(); if (renderer == null) { renderer = header.getDefaultRenderer(); } Component rendComp = renderer.getTableCellRendererComponent(header.getTable(), aColumn.getHeaderValue(), false, false, -1, i); SwingUtilities.updateComponentTreeUI(rendComp); } super.uninstallUI(c); } /** * Returns the current state for the specified cell. * * @param columnIndex * Column index. * @return The current state for the specified column. */ public ComponentState getColumnState(int columnIndex) { // ButtonModel synthModel = new DefaultButtonModel(); boolean toEnable = header.isEnabled(); // get the rollover column index from the table UI delegate JTable table = this.header.getTable(); if (table != null) { toEnable = toEnable && table.isEnabled(); } StateTransitionTracker tracker = this.stateTransitionMultiTracker.getTracker(columnIndex); if (tracker == null) { boolean isRollover = false; TableColumnModel columnModel = header.getColumnModel(); boolean isSelected = false; if (columnModel.getColumnSelectionAllowed()) { isSelected = columnModel.getSelectionModel().isSelectedIndex(columnIndex); if ((table != null) && (table.getUI() instanceof SubstanceTableUI)) { SubstanceTableUI tableUI = (SubstanceTableUI) table.getUI(); int rolledOverIndex = tableUI.getRolloverColumnIndex(); isRollover = (rolledOverIndex >= 0) && (rolledOverIndex == columnIndex); boolean hasSelectionAnimations = tableUI.hasSelectionAnimations(); if (hasSelectionAnimations && AnimationConfigurationManager.getInstance() .isAnimationAllowed(AnimationFacet.SELECTION, table)) isSelected = this.selectedIndices.containsKey(columnIndex); } } return ComponentState.getState(toEnable, isRollover, isSelected); } else { ComponentState fromTracker = tracker.getModelStateInfo().getCurrModelState(); return ComponentState.getState(toEnable, fromTracker.isFacetActive(ComponentStateFacet.ROLLOVER), fromTracker.isFacetActive(ComponentStateFacet.SELECTION)); } } public StateTransitionTracker.ModelStateInfo getModelStateInfo(int columnIndex) { if (this.stateTransitionMultiTracker.size() == 0) return null; StateTransitionTracker tracker = this.stateTransitionMultiTracker.getTracker(columnIndex); if (tracker == null) { return null; } else { return tracker.getModelStateInfo(); } } public StateTransitionTracker getStateTransitionTracker(int columnIndex) { return this.stateTransitionMultiTracker.getTracker(columnIndex); } /** * Returns the scroll pane corner filler component. This method is used in * {@link SubstanceScrollPaneUI} to put a consistent filler for tables. * * @return Scroll pane corner filler. */ public JComponent getScrollPaneCornerFiller() { return new ScrollPaneCornerFiller(this.header); } /** * Corner filler for tables wrapped in scroll panes. * * @author Kirill Grouchnikov */ protected static class ScrollPaneCornerFiller extends JComponent implements UIResource { /** * Associated table header. */ protected JTableHeader header; /** * Creates a new corner filler. * * @param header * Table header. */ public ScrollPaneCornerFiller(JTableHeader header) { this.header = header; } @Override protected void paintComponent(Graphics g) { Graphics2D g2d = (Graphics2D) g.create(); // System.err.println("Painting " + this.hashCode() + " from " // + ((header == null) ? "null" : header.hashCode())); boolean ltr = header.getComponentOrientation().isLeftToRight(); final ComponentState backgroundState = (header.isEnabled() && header.getTable().isEnabled()) ? ComponentState.ENABLED : ComponentState.DISABLED_UNSELECTED; SubstanceColorScheme fillScheme = SubstanceColorSchemeUtilities.getColorScheme( this.header, ColorSchemeAssociationKind.HIGHLIGHT_TEXT, backgroundState); SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities.getColorScheme( this.header, ColorSchemeAssociationKind.HIGHLIGHT_BORDER, backgroundState); HighlightPainterUtils.paintHighlight(g2d, null, this.header, new Rectangle(0, 0, this.getWidth(), this.getHeight()), 0.0f, null, fillScheme, borderScheme); g2d.setColor(getGridColor(this.header)); float strokeWidth = SubstanceSizeUtils.getBorderStrokeWidth(); g2d.setStroke( new BasicStroke(strokeWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL)); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g2d.setComposite(LafWidgetUtilities.getAlphaComposite(this.header, 0.7f, g)); int x = ltr ? (int) strokeWidth / 2 : getWidth() - 1 - (int) strokeWidth / 2; g2d.drawLine(x, 0, x, getHeight()); g2d.dispose(); } } /** * Processes the events on model changes on the table column model. * * @param oldModel * Old column model. * @param newModel * New column model. */ public void processColumnModelChangeEvent(TableColumnModel oldModel, TableColumnModel newModel) { if (oldModel != null) { oldModel.getSelectionModel() .removeListSelectionListener(substanceFadeSelectionListener); } if (newModel != null) { newModel.getSelectionModel().addListSelectionListener(substanceFadeSelectionListener); } selectedIndices.clear(); stateTransitionMultiTracker.clear(); } /** * Repaints a single column header during the fade animation cycle. * * @author Kirill Grouchnikov */ protected class ColumnHeaderRepaintCallback extends UIThreadTimelineCallbackAdapter { /** * Associated table header. */ protected JTableHeader header; /** * Associated (animated) column index. */ protected int columnIndex; /** * Creates a new animation repaint callback. * * @param header * Associated table header. * @param columnIndex * Associated (animated) column index. */ public ColumnHeaderRepaintCallback(JTableHeader header, int columnIndex) { this.header = header; this.columnIndex = columnIndex; } @Override public void onTimelinePulse(float durationFraction, float timelinePosition) { repaintColumnHeader(); } @Override public void onTimelineStateChanged(TimelineState oldState, TimelineState newState, float durationFraction, float timelinePosition) { repaintColumnHeader(); } /** * Repaints the associated cell. */ private void repaintColumnHeader() { SwingUtilities.invokeLater(() -> { if (header == null) { // may happen if the LAF was switched in the meantime return; } try { // maybeUpdateLayoutState(); int cellCount = header.getColumnModel().getColumnCount(); if ((cellCount > 0) && (columnIndex < cellCount)) { // need to retrieve the cell rectangle since the // cells can be moved while animating Rectangle rect = header.getHeaderRect(columnIndex); Rectangle damaged = new Rectangle(rect.x - 5, rect.y, rect.width + 10, rect.height); header.repaint(damaged); } } catch (RuntimeException re) { return; } }); } } public StateTransitionTracker getTracker(final int columnIndex, boolean initialRollover, boolean initialSelected) { StateTransitionTracker tracker = stateTransitionMultiTracker.getTracker(columnIndex); if (tracker == null) { ButtonModel model = new DefaultButtonModel(); model.setSelected(initialSelected); model.setRollover(initialRollover); tracker = new StateTransitionTracker(header, model); tracker.registerModelListeners(); tracker.setRepaintCallback(() -> new ColumnHeaderRepaintCallback(header, columnIndex)); stateTransitionMultiTracker.addTracker(columnIndex, tracker); } return tracker; } }