Skip to content

Commit

Permalink
TableHeader: no longer temporary replace header cell renderer while p…
Browse files Browse the repository at this point in the history
…ainting (issue apache/netbeans#6835)
  • Loading branch information
DevCharly committed Dec 22, 2023
1 parent ee01756 commit 4df34b3
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 79 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ FlatLaf Change Log
component is a Table.
- Table: Fixed background of `boolean` columns when using alternating row
colors. (issue #780)
- TableHeader: No longer temporary replace header cell renderer while painting.
This avoids a `StackOverflowError` in case that custom renderer does this too.
(see [NetBeans issue #6835](https://github.com/apache/netbeans/issues/6835)).
This also improves compatibility with custom table header implementations.
- TabbedPane:
- Avoid unnecessary repainting whole tabbed pane content area when layouting
leading/trailing components.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
Expand All @@ -28,16 +29,15 @@
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeListener;
import java.util.Map;
import javax.swing.CellRendererPane;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.Border;
import javax.swing.event.MouseInputListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableCellRenderer;
Expand Down Expand Up @@ -114,6 +114,11 @@ public static ComponentUI createUI( JComponent c ) {
public void installUI( JComponent c ) {
super.installUI( c );

// replace cell renderer pane
header.remove( rendererPane );
rendererPane = new FlatTableHeaderCellRendererPane();
header.add( rendererPane );

installStyle();
}

Expand Down Expand Up @@ -265,16 +270,8 @@ public void paint( Graphics g, JComponent c ) {
}
}

// temporary use own default renderer
FlatTableCellHeaderRenderer tempRenderer = new FlatTableCellHeaderRenderer( header.getDefaultRenderer() );
header.setDefaultRenderer( tempRenderer );

// paint header
super.paint( g, c );

// restore default renderer
tempRenderer.reset();
header.setDefaultRenderer( tempRenderer.delegate );
}

private boolean isSystemDefaultRenderer( Object headerRenderer ) {
Expand Down Expand Up @@ -332,119 +329,129 @@ private static boolean isValidColumn( TableColumnModel cm, TableColumn column )
return false;
}

//---- class FlatTableCellHeaderRenderer ----------------------------------
//---- class FlatTableHeaderCellRendererPane ------------------------------

/**
* A delegating header renderer that is only used to paint hover and pressed
* background/foreground and to paint sort arrows at top, bottom or left position.
* Cell renderer pane that is used to paint hover and pressed background/foreground
* and to paint sort arrows at top, bottom or left position.
*/
private class FlatTableCellHeaderRenderer
implements TableCellRenderer, Border, UIResource
private class FlatTableHeaderCellRendererPane
extends CellRendererPane
{
private final TableCellRenderer delegate;

private JLabel l;
private Color oldBackground;
private Color oldForeground;
private Boolean oldOpaque;
private int oldHorizontalTextPosition = -1;
private Border origBorder;
private Icon sortIcon;

FlatTableCellHeaderRenderer( TableCellRenderer delegate ) {
this.delegate = delegate;
private final Icon ascendingSortIcon;
private final Icon descendingSortIcon;

public FlatTableHeaderCellRendererPane() {
ascendingSortIcon = UIManager.getIcon( "Table.ascendingSortIcon" );
descendingSortIcon = UIManager.getIcon( "Table.descendingSortIcon" );
}

@Override
public Component getTableCellRendererComponent( JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column )
{
Component c = delegate.getTableCellRendererComponent( table, value, isSelected, hasFocus, row, column );
if( !(c instanceof JLabel) )
return c;
public void paintComponent( Graphics g, Component c, Container p, int x, int y, int w, int h, boolean shouldValidate ) {
if( !(c instanceof JLabel) ) {
super.paintComponent( g, c, p, x, y, w, h, shouldValidate );
return;
}

l = (JLabel) c;
JLabel l = (JLabel) c;
Color oldBackground = null;
Color oldForeground = null;
Boolean oldOpaque = null;
Icon oldIcon = null;
int oldHorizontalTextPosition = -1;

// hover and pressed background/foreground
TableColumn draggedColumn = header.getDraggedColumn();
Color background = null;
Color foreground = null;
if( draggedColumn != null && header.getTable().convertColumnIndexToView( draggedColumn.getModelIndex() ) == column ) {
if( draggedColumn != null &&
header.getTable().convertColumnIndexToView( draggedColumn.getModelIndex() )
== getColumn( x - header.getDraggedDistance(), w ) )
{
background = pressedBackground;
foreground = pressedForeground;
} else if( getRolloverColumn() == column ) {
} else if( getRolloverColumn() >= 0 && getRolloverColumn() == getColumn( x, w ) ) {
background = hoverBackground;
foreground = hoverForeground;
}
if( background != null ) {
if( oldBackground == null )
oldBackground = l.getBackground();
if( oldOpaque == null )
oldOpaque = l.isOpaque();
oldBackground = l.getBackground();
oldOpaque = l.isOpaque();
l.setBackground( FlatUIUtils.deriveColor( background, header.getBackground() ) );
l.setOpaque( true );
}
if( foreground != null ) {
if( oldForeground == null )
oldForeground = l.getForeground();
oldForeground = l.getForeground();
l.setForeground( FlatUIUtils.deriveColor( foreground, header.getForeground() ) );
}

// sort icon
if( sortIconPosition == SwingConstants.LEFT ) {
// left
if( oldHorizontalTextPosition < 0 )
// sort icon position
Icon icon = l.getIcon();
boolean isSortIcon = (icon != null && (icon == ascendingSortIcon || icon == descendingSortIcon));
if( isSortIcon ) {
if( sortIconPosition == SwingConstants.LEFT ) {
// left
oldHorizontalTextPosition = l.getHorizontalTextPosition();
l.setHorizontalTextPosition( SwingConstants.RIGHT );
} else if( sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM ) {
// top or bottom
sortIcon = l.getIcon();
origBorder = l.getBorder();
l.setIcon( null );
l.setBorder( this );
l.setHorizontalTextPosition( SwingConstants.RIGHT );
} else if( sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM ) {
// top or bottom
oldIcon = icon;
l.setIcon( null );
}
}

return l;
}
// paint renderer component
super.paintComponent( g, c, p, x, y, w, h, shouldValidate );

void reset() {
if( l == null )
return;
// paint top or bottom sort icon
if( isSortIcon && (sortIconPosition == SwingConstants.TOP || sortIconPosition == SwingConstants.BOTTOM) ) {
int xi = x + ((w - icon.getIconWidth()) / 2);
int yi = (sortIconPosition == SwingConstants.TOP)
? y + UIScale.scale( 1 )
: y + height - icon.getIconHeight()
- 1 // for gap
- (int) (1 * UIScale.getUserScaleFactor()); // for bottom border
icon.paintIcon( c, g, xi, yi );
}

// restore modified renderer component properties
if( oldBackground != null )
l.setBackground( oldBackground );
if( oldForeground != null )
l.setForeground( oldForeground );
if( oldOpaque != null )
l.setOpaque( oldOpaque );
if( oldIcon != null )
l.setIcon( oldIcon );
if( oldHorizontalTextPosition >= 0 )
l.setHorizontalTextPosition( oldHorizontalTextPosition );
}

@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
if( origBorder != null )
origBorder.paintBorder( c, g, x, y, width, height );

if( sortIcon != null ) {
int xi = x + ((width - sortIcon.getIconWidth()) / 2);
int yi = (sortIconPosition == SwingConstants.TOP)
? y + UIScale.scale( 1 )
: y + height - sortIcon.getIconHeight()
- 1 // for gap
- (int) (1 * UIScale.getUserScaleFactor()); // for bottom border
sortIcon.paintIcon( c, g, xi, yi );
/**
* Get column index for given coordinates.
*/
private int getColumn( int x, int width ) {
TableColumnModel columnModel = header.getColumnModel();
int columnCount = columnModel.getColumnCount();
boolean ltr = header.getComponentOrientation().isLeftToRight();
int cx = ltr ? 0 : getWidthInRightToLef();

for( int i = 0; i < columnCount; i++ ) {
int cw = columnModel.getColumn( i ).getWidth();
if( x == cx - (ltr ? 0 : cw) && width == cw )
return i;

cx += ltr ? cw : -cw;
}
return -1;
}

@Override
public Insets getBorderInsets( Component c ) {
return (origBorder != null) ? origBorder.getBorderInsets( c ) : new Insets( 0, 0, 0, 0 );
}

@Override
public boolean isBorderOpaque() {
return (origBorder != null) ? origBorder.isBorderOpaque() : false;
// similar to JTableHeader.getWidthInRightToLeft()
private int getWidthInRightToLef() {
JTable table = header.getTable();
return (table != null && table.getAutoResizeMode() != JTable.AUTO_RESIZE_OFF)
? table.getWidth()
: header.getWidth();
}
}

Expand Down

0 comments on commit 4df34b3

Please sign in to comment.