TableHeader: Moved table header column border painting from FlatTableHeaderUI to new border FlatTableHeaderBorder to improve compatibility with custom table header implementations (issue #228)

This commit is contained in:
Karl Tauber
2021-04-14 19:34:44 +02:00
parent 5167cd368f
commit 4f2256f713
10 changed files with 219 additions and 138 deletions

View File

@@ -13,6 +13,9 @@ FlatLaf Change Log
- If frame is deiconified, dock is compacted (icons move to the left).
- If dock is wider than desktop width, additional rows are used.
- If desktop pane is resized, layout of dock is updated.
- TableHeader: Moved table header column border painting from
`FlatTableHeaderUI` to new border `FlatTableHeaderBorder` to improve
compatibility with custom table header implementations. (issue #228)
- IntelliJ Themes: Added "Material Theme UI Lite / GitHub Dark" theme.
- JIDE Common Layer: Improved support for `JideTabbedPane`. (PR #306)

View File

@@ -0,0 +1,124 @@
/*
* Copyright 2021 FormDev Software GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.table.JTableHeader;
import javax.swing.table.TableColumn;
import com.formdev.flatlaf.util.UIScale;
/**
* Cell border for {@code sun.swing.table.DefaultTableCellHeaderRenderer}
* (used by {@link javax.swing.table.JTableHeader}).
* <p>
* Uses separate cell margins from UI defaults to allow easy customizing.
*
* @author Karl Tauber
* @since 1.2
*/
public class FlatTableHeaderBorder
extends FlatEmptyBorder
{
protected Color separatorColor = UIManager.getColor( "TableHeader.separatorColor" );
protected Color bottomSeparatorColor = UIManager.getColor( "TableHeader.bottomSeparatorColor" );
public FlatTableHeaderBorder() {
super( UIManager.getInsets( "TableHeader.cellMargins" ) );
}
@Override
public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) {
JTableHeader header = (JTableHeader) SwingUtilities.getAncestorOfClass( JTableHeader.class, c );
boolean leftToRight = (header != null ? header : c).getComponentOrientation().isLeftToRight();
boolean paintLeft = !leftToRight;
boolean paintRight = leftToRight;
if( header != null ) {
int hx = SwingUtilities.convertPoint( c, x, y, header ).x;
if( isDraggedColumn( header, hx ) )
paintLeft = paintRight = true;
else {
if( hx <= 0 && !leftToRight && hideTrailingVerticalLine( header ) )
paintLeft = false;
if( hx + width >= header.getWidth() && leftToRight && hideTrailingVerticalLine( header ) )
paintRight = false;
}
}
float lineWidth = UIScale.scale( 1f );
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
// paint column separator lines
g2.setColor( separatorColor );
if( paintLeft )
g2.fill( new Rectangle2D.Float( x, y, lineWidth, height - lineWidth ) );
if( paintRight )
g2.fill( new Rectangle2D.Float( x + width - lineWidth, y, lineWidth, height - lineWidth ) );
// paint bottom line
g2.setColor( bottomSeparatorColor );
g2.fill( new Rectangle2D.Float( x, y + height - lineWidth, width, lineWidth ) );
} finally {
g2.dispose();
}
}
protected boolean isDraggedColumn( JTableHeader header, int x ) {
TableColumn draggedColumn = header.getDraggedColumn();
if( draggedColumn == null )
return false;
int draggedDistance = header.getDraggedDistance();
if( draggedDistance == 0 )
return false;
int columnCount = header.getColumnModel().getColumnCount();
for( int i = 0; i < columnCount; i++ ) {
if( header.getHeaderRect( i ).x + draggedDistance == x )
return true;
}
return false;
}
protected boolean hideTrailingVerticalLine( JTableHeader header ) {
Container viewport = header.getParent();
Container viewportParent = (viewport != null) ? viewport.getParent() : null;
if( !(viewportParent instanceof JScrollPane) )
return true;
JScrollBar vsb = ((JScrollPane)viewportParent).getVerticalScrollBar();
if( vsb == null || !vsb.isVisible() )
return true;
// if "ScrollPane.fillUpperCorner" is true, then javax.swing.ScrollPaneLayout
// extends the vertical scrollbar into the upper right/left corner
return vsb.getY() == viewport.getY();
}
}

View File

@@ -18,20 +18,16 @@ package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.geom.Rectangle2D;
import java.util.Objects;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.border.Border;
@@ -39,7 +35,6 @@ import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;
import javax.swing.plaf.basic.BasicTableHeaderUI;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
import com.formdev.flatlaf.util.UIScale;
@@ -54,17 +49,21 @@ import com.formdev.flatlaf.util.UIScale;
*
* <!-- FlatTableHeaderUI -->
*
* @uiDefault TableHeader.separatorColor Color
* @uiDefault TableHeader.bottomSeparatorColor Color
* @uiDefault TableHeader.height int
* @uiDefault TableHeader.sortIconPosition String right (default), left, top or bottom
*
* <!-- FlatTableHeaderBorder -->
*
* @uiDefault TableHeader.cellMargins Insets
* @uiDefault TableHeader.separatorColor Color
* @uiDefault TableHeader.bottomSeparatorColor Color
*
* @author Karl Tauber
*/
public class FlatTableHeaderUI
extends BasicTableHeaderUI
{
protected Color separatorColor;
protected Color bottomSeparatorColor;
protected int height;
protected int sortIconPosition;
@@ -77,7 +76,6 @@ public class FlatTableHeaderUI
protected void installDefaults() {
super.installDefaults();
separatorColor = UIManager.getColor( "TableHeader.separatorColor" );
bottomSeparatorColor = UIManager.getColor( "TableHeader.bottomSeparatorColor" );
height = UIManager.getInt( "TableHeader.height" );
switch( Objects.toString( UIManager.getString( "TableHeader.sortIconPosition" ), "right" ) ) {
@@ -93,27 +91,38 @@ public class FlatTableHeaderUI
protected void uninstallDefaults() {
super.uninstallDefaults();
separatorColor = null;
bottomSeparatorColor = null;
}
@Override
public void paint( Graphics g, JComponent c ) {
if( header.getColumnModel().getColumnCount() <= 0 )
TableColumnModel columnModel = header.getColumnModel();
if( columnModel.getColumnCount() <= 0 )
return;
// do not paint borders if JTableHeader.setDefaultRenderer() was used
TableCellRenderer defaultRenderer = header.getDefaultRenderer();
boolean paintBorders = isSystemDefaultRenderer( defaultRenderer );
if( !paintBorders ) {
// check whether the renderer delegates to the system default renderer
Component rendererComponent = defaultRenderer.getTableCellRendererComponent(
header.getTable(), "", false, false, -1, 0 );
paintBorders = isSystemDefaultRenderer( rendererComponent );
}
// compute total width of all columns
int columnCount = columnModel.getColumnCount();
int totalWidth = 0;
for( int i = 0; i < columnCount; i++ )
totalWidth += columnModel.getColumn( i ).getWidth();
if( paintBorders )
paintColumnBorders( g, c );
if( totalWidth < header.getWidth() ) {
// do not paint bottom separator if JTableHeader.setDefaultRenderer() was used
TableCellRenderer defaultRenderer = header.getDefaultRenderer();
boolean paintBottomSeparator = isSystemDefaultRenderer( defaultRenderer );
if( !paintBottomSeparator && header.getTable() != null ) {
// check whether the renderer delegates to the system default renderer
Component rendererComponent = defaultRenderer.getTableCellRendererComponent(
header.getTable(), "", false, false, -1, 0 );
paintBottomSeparator = isSystemDefaultRenderer( rendererComponent );
}
if( paintBottomSeparator ) {
int w = c.getWidth() - totalWidth;
int x = header.getComponentOrientation().isLeftToRight() ? c.getWidth() - w : 0;
paintBottomSeparator( g, c, x, w );
}
}
// temporary use own default renderer if necessary
FlatTableCellHeaderRenderer sortIconRenderer = null;
@@ -130,9 +139,6 @@ public class FlatTableHeaderUI
sortIconRenderer.reset();
header.setDefaultRenderer( sortIconRenderer.delegate );
}
if( paintBorders )
paintDraggedColumnBorders( g, c );
}
private boolean isSystemDefaultRenderer( Object headerRenderer ) {
@@ -141,17 +147,8 @@ public class FlatTableHeaderUI
rendererClassName.equals( "sun.swing.FilePane$AlignableTableHeaderRenderer" );
}
protected void paintColumnBorders( Graphics g, JComponent c ) {
int width = c.getWidth();
int height = c.getHeight();
protected void paintBottomSeparator( Graphics g, JComponent c, int x, int w ) {
float lineWidth = UIScale.scale( 1f );
float topLineIndent = lineWidth;
float bottomLineIndent = lineWidth * 3;
TableColumnModel columnModel = header.getColumnModel();
int columnCount = columnModel.getColumnCount();
int sepCount = columnCount;
if( hideLastVerticalLine() )
sepCount--;
Graphics2D g2 = (Graphics2D) g.create();
try {
@@ -159,78 +156,7 @@ public class FlatTableHeaderUI
// paint bottom line
g2.setColor( bottomSeparatorColor );
g2.fill( new Rectangle2D.Float( 0, height - lineWidth, width, lineWidth ) );
// paint column separator lines
g2.setColor( separatorColor );
float y = topLineIndent;
float h = height - bottomLineIndent;
if( header.getComponentOrientation().isLeftToRight() ) {
int x = 0;
for( int i = 0; i < sepCount; i++ ) {
x += columnModel.getColumn( i ).getWidth();
g2.fill( new Rectangle2D.Float( x - lineWidth, y, lineWidth, h ) );
}
// paint trailing separator (on right side)
if( !hideTrailingVerticalLine() )
g2.fill( new Rectangle2D.Float( header.getWidth() - lineWidth, y, lineWidth, h ) );
} else {
Rectangle cellRect = header.getHeaderRect( 0 );
int x = cellRect.x + cellRect.width;
for( int i = 0; i < sepCount; i++ ) {
x -= columnModel.getColumn( i ).getWidth();
g2.fill( new Rectangle2D.Float( x - (i < sepCount - 1 ? lineWidth : 0), y, lineWidth, h ) );
}
// paint trailing separator (on left side)
if( !hideTrailingVerticalLine() )
g2.fill( new Rectangle2D.Float( 0, y, lineWidth, h ) );
}
} finally {
g2.dispose();
}
}
private void paintDraggedColumnBorders( Graphics g, JComponent c ) {
TableColumn draggedColumn = header.getDraggedColumn();
if( draggedColumn == null )
return;
// find index of dragged column
TableColumnModel columnModel = header.getColumnModel();
int columnCount = columnModel.getColumnCount();
int draggedColumnIndex = -1;
for( int i = 0; i < columnCount; i++ ) {
if( columnModel.getColumn( i ) == draggedColumn ) {
draggedColumnIndex = i;
break;
}
}
if( draggedColumnIndex < 0 )
return;
float lineWidth = UIScale.scale( 1f );
float topLineIndent = lineWidth;
float bottomLineIndent = lineWidth * 3;
Rectangle r = header.getHeaderRect( draggedColumnIndex );
r.x += header.getDraggedDistance();
Graphics2D g2 = (Graphics2D) g.create();
try {
FlatUIUtils.setRenderingHints( g2 );
// paint dragged bottom line
g2.setColor( bottomSeparatorColor );
g2.fill( new Rectangle2D.Float( r.x, r.y + r.height - lineWidth, r.width, lineWidth ) );
// paint dragged column separator lines
g2.setColor( separatorColor );
g2.fill( new Rectangle2D.Float( r.x, topLineIndent, lineWidth, r.height - bottomLineIndent ) );
g2.fill( new Rectangle2D.Float( r.x + r.width - lineWidth, r.y + topLineIndent, lineWidth, r.height - bottomLineIndent ) );
g2.fill( new Rectangle2D.Float( x, c.getHeight() - lineWidth, w, lineWidth ) );
} finally {
g2.dispose();
}
@@ -244,32 +170,6 @@ public class FlatTableHeaderUI
return size;
}
protected boolean hideLastVerticalLine() {
Container viewport = header.getParent();
Container viewportParent = (viewport != null) ? viewport.getParent() : null;
if( !(viewportParent instanceof JScrollPane) )
return false;
Rectangle cellRect = header.getHeaderRect( header.getColumnModel().getColumnCount() - 1 );
// using component orientation of scroll pane here because it is also used in FlatTableUI
JScrollPane scrollPane = (JScrollPane) viewportParent;
return scrollPane.getComponentOrientation().isLeftToRight()
? cellRect.x + cellRect.width >= viewport.getWidth()
: cellRect.x <= 0;
}
protected boolean hideTrailingVerticalLine() {
Container viewport = header.getParent();
Container viewportParent = (viewport != null) ? viewport.getParent() : null;
if( !(viewportParent instanceof JScrollPane) )
return false;
JScrollPane scrollPane = (JScrollPane) viewportParent;
return viewport == scrollPane.getColumnHeader() &&
scrollPane.getCorner( ScrollPaneConstants.UPPER_TRAILING_CORNER ) == null;
}
//---- class FlatTableCellHeaderRenderer ----------------------------------
/**

View File

@@ -642,7 +642,8 @@ Table.dropLineShortColor = @dropLineShortColor
#---- TableHeader ----
TableHeader.height = 25
TableHeader.cellBorder = 2,3,2,3
TableHeader.cellBorder = com.formdev.flatlaf.ui.FlatTableHeaderBorder
TableHeader.cellMargins = 2,3,2,3
TableHeader.focusCellBackground = $TableHeader.background
TableHeader.background = @textComponentBackground

View File

@@ -1067,7 +1067,8 @@ Table.sortIconColor #adadad javax.swing.plaf.ColorUIResource [UI]
TableHeader.background #45494a javax.swing.plaf.ColorUIResource [UI]
TableHeader.bottomSeparatorColor #5e6364 javax.swing.plaf.ColorUIResource [UI]
TableHeader.cellBorder [lazy] 2,3,2,3 false com.formdev.flatlaf.ui.FlatEmptyBorder [UI]
TableHeader.cellBorder [lazy] 2,3,2,3 false com.formdev.flatlaf.ui.FlatTableHeaderBorder [UI]
TableHeader.cellMargins 2,3,2,3 javax.swing.plaf.InsetsUIResource [UI]
TableHeader.focusCellBackground #45494a javax.swing.plaf.ColorUIResource [UI]
TableHeader.font [active] $defaultFont [UI]
TableHeader.foreground #bbbbbb javax.swing.plaf.ColorUIResource [UI]

View File

@@ -1072,7 +1072,8 @@ Table.sortIconColor #afafaf javax.swing.plaf.ColorUIResource [UI]
TableHeader.background #ffffff javax.swing.plaf.ColorUIResource [UI]
TableHeader.bottomSeparatorColor #e6e6e6 javax.swing.plaf.ColorUIResource [UI]
TableHeader.cellBorder [lazy] 2,3,2,3 false com.formdev.flatlaf.ui.FlatEmptyBorder [UI]
TableHeader.cellBorder [lazy] 2,3,2,3 false com.formdev.flatlaf.ui.FlatTableHeaderBorder [UI]
TableHeader.cellMargins 2,3,2,3 javax.swing.plaf.InsetsUIResource [UI]
TableHeader.focusCellBackground #ffffff javax.swing.plaf.ColorUIResource [UI]
TableHeader.font [active] $defaultFont [UI]
TableHeader.foreground #000000 javax.swing.plaf.ColorUIResource [UI]

View File

@@ -1070,7 +1070,8 @@ Table.sortIconColor #ffff00 javax.swing.plaf.ColorUIResource [UI]
TableHeader.background #4444ff javax.swing.plaf.ColorUIResource [UI]
TableHeader.bottomSeparatorColor #00ff00 javax.swing.plaf.ColorUIResource [UI]
TableHeader.cellBorder [lazy] 2,3,2,3 false com.formdev.flatlaf.ui.FlatEmptyBorder [UI]
TableHeader.cellBorder [lazy] 2,3,2,3 false com.formdev.flatlaf.ui.FlatTableHeaderBorder [UI]
TableHeader.cellMargins 2,3,2,3 javax.swing.plaf.InsetsUIResource [UI]
TableHeader.focusCellBackground #4444ff javax.swing.plaf.ColorUIResource [UI]
TableHeader.font [active] $defaultFont [UI]
TableHeader.foreground #ffffff javax.swing.plaf.ColorUIResource [UI]

View File

@@ -40,6 +40,7 @@ import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeCellRenderer;
import com.formdev.flatlaf.FlatClientProperties;
import com.formdev.flatlaf.FlatLaf;
import com.formdev.flatlaf.icons.FlatMenuArrowIcon;
import com.formdev.flatlaf.util.UIScale;
import com.jidesoft.swing.*;
@@ -193,6 +194,15 @@ public class FlatComponents2Test
table.setAutoResizeMode( autoResizeMode );
}
private void sortIconPositionChanged() {
Object sel = sortIconPositionComboBox.getSelectedItem();
if( "right".equals( sel ) )
sel = null;
UIManager.put( "TableHeader.sortIconPosition", sel );
FlatLaf.updateUILater();
}
private void dndChanged() {
boolean dnd = dndCheckBox.isSelected();
list1.setDragEnabled( dnd );
@@ -442,6 +452,8 @@ public class FlatComponents2Test
JPanel tableOptionsPanel = new JPanel();
JLabel autoResizeModeLabel = new JLabel();
autoResizeModeField = new JComboBox<>();
JLabel sortIconPositionLabel = new JLabel();
sortIconPositionComboBox = new JComboBox<>();
showHorizontalLinesCheckBox = new JCheckBox();
rowSelectionCheckBox = new JCheckBox();
showVerticalLinesCheckBox = new JCheckBox();
@@ -753,6 +765,20 @@ public class FlatComponents2Test
autoResizeModeField.addActionListener(e -> autoResizeModeChanged());
tableOptionsPanel.add(autoResizeModeField, "cell 0 0 2 1");
//---- sortIconPositionLabel ----
sortIconPositionLabel.setText("Sort icon:");
tableOptionsPanel.add(sortIconPositionLabel, "cell 0 0 2 1");
//---- sortIconPositionComboBox ----
sortIconPositionComboBox.setModel(new DefaultComboBoxModel<>(new String[] {
"right",
"left",
"top",
"bottom"
}));
sortIconPositionComboBox.addActionListener(e -> sortIconPositionChanged());
tableOptionsPanel.add(sortIconPositionComboBox, "cell 0 0 2 1");
//---- showHorizontalLinesCheckBox ----
showHorizontalLinesCheckBox.setText("show horizontal lines");
showHorizontalLinesCheckBox.addActionListener(e -> showHorizontalLinesChanged());
@@ -819,6 +845,7 @@ public class FlatComponents2Test
private JCheckBox treeWideSelectionCheckBox;
private JCheckBox treePaintSelectionCheckBox;
private JComboBox<String> autoResizeModeField;
private JComboBox<String> sortIconPositionComboBox;
private JCheckBox showHorizontalLinesCheckBox;
private JCheckBox rowSelectionCheckBox;
private JCheckBox showVerticalLinesCheckBox;

View File

@@ -1,4 +1,4 @@
JFDML JFormDesigner: "7.0.3.1.342" Java: "15" encoding: "UTF-8"
JFDML JFormDesigner: "7.0.3.1.342" Java: "16" encoding: "UTF-8"
new FormModel {
contentType: "form/swing"
@@ -398,6 +398,28 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0 2 1"
} )
add( new FormComponent( "javax.swing.JLabel" ) {
name: "sortIconPositionLabel"
"text": "Sort icon:"
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0 2 1"
} )
add( new FormComponent( "javax.swing.JComboBox" ) {
name: "sortIconPositionComboBox"
"model": new javax.swing.DefaultComboBoxModel {
selectedItem: "right"
addElement( "right" )
addElement( "left" )
addElement( "top" )
addElement( "bottom" )
}
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "sortIconPositionChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 0 2 1"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "showHorizontalLinesCheckBox"
"text": "show horizontal lines"
@@ -484,7 +506,7 @@ new FormModel {
} )
}, new FormLayoutConstraints( null ) {
"location": new java.awt.Point( 0, 0 )
"size": new java.awt.Dimension( 950, 600 )
"size": new java.awt.Dimension( 1000, 600 )
} )
}
}

View File

@@ -795,6 +795,7 @@ TableHeader.ancestorInputMap
TableHeader.background
TableHeader.bottomSeparatorColor
TableHeader.cellBorder
TableHeader.cellMargins
TableHeader.focusCellBackground
TableHeader.font
TableHeader.foreground