Table and TableHeader: fixed missing right vertical grid line if using table as row header in scroll pane (issues #152 and #46)

This commit is contained in:
Karl Tauber
2020-12-09 23:04:04 +01:00
parent cfbe44b946
commit eabb1f84f6
5 changed files with 208 additions and 34 deletions

View File

@@ -5,6 +5,8 @@ FlatLaf Change Log
#### Fixed bugs
- Table and TableHeader: Fixed missing right vertical grid line if using table
as row header in scroll pane. (issues #152 and #46)
- TableHeader: Fixed position of column separators in right-to-left component
orientation.
- SwingX: Fixed striping background highlighting color (e.g. alternating table

View File

@@ -31,6 +31,7 @@ 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;
@@ -148,6 +149,9 @@ public class FlatTableHeaderUI
float bottomLineIndent = lineWidth * 3;
TableColumnModel columnModel = header.getColumnModel();
int columnCount = columnModel.getColumnCount();
int sepCount = columnCount;
if( hideLastVerticalLine() )
sepCount--;
Graphics2D g2 = (Graphics2D) g.create();
try {
@@ -160,24 +164,30 @@ public class FlatTableHeaderUI
// paint column separator lines
g2.setColor( separatorColor );
int sepCount = columnCount;
if( header.getTable() != null && header.getTable().getAutoResizeMode() != JTable.AUTO_RESIZE_OFF && !isVerticalScrollBarVisible() )
sepCount--;
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, topLineIndent, lineWidth, height - bottomLineIndent ) );
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),
topLineIndent, lineWidth, height - bottomLineIndent ) );
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();
@@ -234,20 +244,30 @@ public class FlatTableHeaderUI
return size;
}
private boolean isVerticalScrollBarVisible() {
JScrollPane scrollPane = getScrollPane();
return (scrollPane != null && scrollPane.getVerticalScrollBar() != null)
? scrollPane.getVerticalScrollBar().isVisible()
: false;
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;
}
private JScrollPane getScrollPane() {
Container parent = header.getParent();
if( parent == null )
return null;
protected boolean hideTrailingVerticalLine() {
Container viewport = header.getParent();
Container viewportParent = (viewport != null) ? viewport.getParent() : null;
if( !(viewportParent instanceof JScrollPane) )
return false;
parent = parent.getParent();
return (parent instanceof JScrollPane) ? (JScrollPane) parent : null;
JScrollPane scrollPane = (JScrollPane) viewportParent;
return viewport == scrollPane.getColumnHeader() &&
scrollPane.getCorner( ScrollPaneConstants.UPPER_TRAILING_CORNER ) == null;
}
//---- class FlatTableCellHeaderRenderer ----------------------------------

View File

@@ -17,6 +17,7 @@
package com.formdev.flatlaf.ui;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
@@ -26,8 +27,10 @@ import java.awt.event.FocusListener;
import java.awt.geom.Rectangle2D;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JTable;
import javax.swing.JScrollPane;
import javax.swing.JViewport;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicTableUI;
@@ -215,16 +218,11 @@ public class FlatTableUI
boolean verticalLines = table.getShowVerticalLines();
if( horizontalLines || verticalLines ) {
// fix grid painting issues in BasicTableUI
// - do not paint last vertical grid line if auto-resize mode is not off
// - in right-to-left component orientation, do not paint last vertical grid line
// in any auto-resize mode; can not paint on left side of table because
// cells are painted over left line
// - do not paint last vertical grid line if line is on right edge of scroll pane
// - fix unstable grid line thickness when scaled at 125%, 150%, 175%, 225%, ...
// which paints either 1px or 2px lines depending on location
boolean hideLastVerticalLine =
table.getAutoResizeMode() != JTable.AUTO_RESIZE_OFF ||
!table.getComponentOrientation().isLeftToRight();
boolean hideLastVerticalLine = hideLastVerticalLine();
int tableWidth = table.getWidth();
double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g );
@@ -281,4 +279,26 @@ public class FlatTableUI
super.paint( g, c );
}
protected boolean hideLastVerticalLine() {
Container viewport = SwingUtilities.getUnwrappedParent( table );
Container viewportParent = (viewport != null) ? viewport.getParent() : null;
if( !(viewportParent instanceof JScrollPane) )
return false;
// do not hide last vertical line if table is smaller than viewport
if( table.getX() + table.getWidth() < viewport.getWidth() )
return false;
// in left-to-right:
// - do not hide last vertical line if table used as row header in scroll pane
// in right-to-left:
// - hide last vertical line if table used as row header in scroll pane
// - do not hide last vertical line if table is in center and scroll pane has row header
JScrollPane scrollPane = (JScrollPane) viewportParent;
JViewport rowHeader = scrollPane.getRowHeader();
return scrollPane.getComponentOrientation().isLeftToRight()
? (viewport != rowHeader)
: (viewport == rowHeader || rowHeader == null);
}
}

View File

@@ -17,6 +17,8 @@
package com.formdev.flatlaf.testing;
import java.awt.Color;
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.datatransfer.DataFlavor;
@@ -29,10 +31,13 @@ import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.swing.*;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import com.formdev.flatlaf.icons.FlatMenuArrowIcon;
import com.formdev.flatlaf.util.UIScale;
import net.miginfocom.swing.*;
import org.jdesktop.swingx.JXTable;
import org.jdesktop.swingx.JXTreeTable;
@@ -53,6 +58,7 @@ public class FlatComponents2Test
public static void main( String[] args ) {
SwingUtilities.invokeLater( () -> {
FlatTestFrame frame = FlatTestFrame.create( args, "FlatComponents2Test" );
frame.useApplyComponentOrientation = true;
frame.showFrame( FlatComponents2Test::new );
} );
}
@@ -60,7 +66,9 @@ public class FlatComponents2Test
private final TestListModel listModel;
private final TestTreeModel treeModel;
private final TestTableModel tableModel;
private final JTable[] allTables;
private final List<JTable> allTables = new ArrayList<>();
private final List<JTable> allTablesInclRowHeader = new ArrayList<>();
private JTable rowHeaderTable1;
FlatComponents2Test() {
initComponents();
@@ -96,7 +104,10 @@ public class FlatComponents2Test
xTreeTable1.setTreeTableModel( new FileSystemModel( new File( "." ) ) );
xTreeTable1.setHighlighters( simpleStriping, magenta, rollover, shading );
allTables = new JTable[] { table1, xTable1, xTreeTable1 };
allTables.add( table1 );
allTables.add( xTable1 );
allTables.add( xTreeTable1 );
allTablesInclRowHeader.addAll( allTables );
expandTree( tree1 );
expandTree( tree2 );
@@ -219,6 +230,7 @@ public class FlatComponents2Test
JButton button = null;
if( show ) {
button = new JButton( new FlatMenuArrowIcon() );
button.applyComponentOrientation( getComponentOrientation() );
button.addActionListener( e -> {
JOptionPane.showMessageDialog( this, "hello" );
} );
@@ -237,25 +249,72 @@ public class FlatComponents2Test
}
private void showHorizontalLinesChanged() {
for( JTable table : allTables )
for( JTable table : allTablesInclRowHeader )
table.setShowHorizontalLines( showHorizontalLinesCheckBox.isSelected() );
}
private void showVerticalLinesChanged() {
for( JTable table : allTables )
for( JTable table : allTablesInclRowHeader )
table.setShowVerticalLines( showVerticalLinesCheckBox.isSelected() );
}
private void intercellSpacingChanged() {
for( JTable table : allTables )
for( JTable table : allTablesInclRowHeader )
table.setIntercellSpacing( intercellSpacingCheckBox.isSelected() ? new Dimension( 1, 1 ) : new Dimension() );
}
private void redGridColorChanged() {
for( JTable table : allTables )
for( JTable table : allTablesInclRowHeader )
table.setGridColor( redGridColorCheckBox.isSelected() ? Color.red : UIManager.getColor( "Table.gridColor" ) );
}
private void rowHeaderChanged() {
if( rowHeaderCheckBox.isSelected() ) {
TestTableRowHeaderModel rowHeaderModel = new TestTableRowHeaderModel( tableModel );
rowHeaderTable1 = new JTable( rowHeaderModel );
rowHeaderTable1.setPreferredScrollableViewportSize( UIScale.scale( new Dimension( 50, 50 ) ) );
rowHeaderTable1.setSelectionModel( table1.getSelectionModel() );
DefaultTableCellRenderer rowHeaderRenderer = new DefaultTableCellRenderer();
rowHeaderRenderer.setHorizontalAlignment( JLabel.CENTER );
rowHeaderTable1.setDefaultRenderer( Object.class, rowHeaderRenderer );
table1ScrollPane.setRowHeaderView( rowHeaderTable1 );
JViewport headerViewport = new JViewport();
headerViewport.setView( rowHeaderTable1.getTableHeader() );
table1ScrollPane.setCorner( ScrollPaneConstants.UPPER_LEADING_CORNER, headerViewport );
table1ScrollPane.applyComponentOrientation( getComponentOrientation() );
allTablesInclRowHeader.add( rowHeaderTable1 );
showHorizontalLinesChanged();
showVerticalLinesChanged();
intercellSpacingChanged();
redGridColorChanged();
} else {
table1ScrollPane.setRowHeader( null );
table1ScrollPane.setCorner( ScrollPaneConstants.UPPER_LEADING_CORNER, null );
allTablesInclRowHeader.remove( rowHeaderTable1 );
((TestTableRowHeaderModel)rowHeaderTable1.getModel()).dispose();
rowHeaderTable1 = null;
}
}
@Override
public void applyComponentOrientation( ComponentOrientation o ) {
super.applyComponentOrientation( o );
// swap upper right and left corners (other corners are not used in this app)
Component leftCorner = table1ScrollPane.getCorner( ScrollPaneConstants.UPPER_LEFT_CORNER );
Component rightCorner = table1ScrollPane.getCorner( ScrollPaneConstants.UPPER_RIGHT_CORNER );
table1ScrollPane.setCorner( ScrollPaneConstants.UPPER_LEFT_CORNER, null );
table1ScrollPane.setCorner( ScrollPaneConstants.UPPER_RIGHT_CORNER, null );
table1ScrollPane.setCorner( ScrollPaneConstants.UPPER_LEFT_CORNER, rightCorner );
table1ScrollPane.setCorner( ScrollPaneConstants.UPPER_RIGHT_CORNER, leftCorner );
}
@Override
public void updateUI() {
super.updateUI();
@@ -300,6 +359,7 @@ public class FlatComponents2Test
JLabel autoResizeModeLabel = new JLabel();
autoResizeModeField = new JComboBox<>();
showHorizontalLinesCheckBox = new JCheckBox();
rowHeaderCheckBox = new JCheckBox();
showVerticalLinesCheckBox = new JCheckBox();
intercellSpacingCheckBox = new JCheckBox();
redGridColorCheckBox = new JCheckBox();
@@ -461,7 +521,7 @@ public class FlatComponents2Test
panel3.add(tableRowCountLabel, "cell 0 2");
//---- tableRowCountSpinner ----
tableRowCountSpinner.setModel(new SpinnerNumberModel(20, 0, null, 10));
tableRowCountSpinner.setModel(new SpinnerNumberModel(20, 0, null, 5));
tableRowCountSpinner.addChangeListener(e -> tableRowCountChanged());
panel3.add(tableRowCountSpinner, "cell 0 3");
}
@@ -515,6 +575,11 @@ public class FlatComponents2Test
showHorizontalLinesCheckBox.addActionListener(e -> showHorizontalLinesChanged());
tableOptionsPanel.add(showHorizontalLinesCheckBox, "cell 0 1");
//---- rowHeaderCheckBox ----
rowHeaderCheckBox.setText("row header");
rowHeaderCheckBox.addActionListener(e -> rowHeaderChanged());
tableOptionsPanel.add(rowHeaderCheckBox, "cell 1 1");
//---- showVerticalLinesCheckBox ----
showVerticalLinesCheckBox.setText("show vertical lines");
showVerticalLinesCheckBox.addActionListener(e -> showVerticalLinesChanged());
@@ -588,6 +653,7 @@ public class FlatComponents2Test
private JTable table1;
private JComboBox<String> autoResizeModeField;
private JCheckBox showHorizontalLinesCheckBox;
private JCheckBox rowHeaderCheckBox;
private JCheckBox showVerticalLinesCheckBox;
private JCheckBox intercellSpacingCheckBox;
private JCheckBox redGridColorCheckBox;
@@ -885,4 +951,56 @@ public class FlatComponents2Test
fireTableCellUpdated( rowIndex, columnIndex );
}
}
//---- TestTableRowHeaderModel --------------------------------------------
private class TestTableRowHeaderModel
extends AbstractTableModel
implements TableModelListener
{
private final TableModel model;
TestTableRowHeaderModel( TableModel model ) {
this.model = model;
model.addTableModelListener( this );
}
void dispose() {
model.removeTableModelListener( this );
}
@Override
public int getRowCount() {
return model.getRowCount();
}
@Override
public int getColumnCount() {
return 1;
}
@Override
public String getColumnName( int columnIndex ) {
return "Row #";
}
@Override
public Object getValueAt( int rowIndex, int columnIndex ) {
return rowIndex + 1;
}
@Override
public void tableChanged( TableModelEvent e ) {
switch( e.getType() ) {
case TableModelEvent.INSERT:
fireTableRowsInserted( e.getFirstRow(), e.getLastRow() );
break;
case TableModelEvent.DELETE:
fireTableRowsDeleted( e.getFirstRow(), e.getLastRow() );
break;
}
}
}
}

View File

@@ -64,7 +64,7 @@ new FormModel {
} )
add( new FormComponent( "javax.swing.JSpinner" ) {
name: "listRowCountSpinner"
"model": &SpinnerNumberModel0 new javax.swing.SpinnerNumberModel {
"model": new javax.swing.SpinnerNumberModel {
minimum: 0
stepSize: 10
value: 20
@@ -184,7 +184,11 @@ new FormModel {
} )
add( new FormComponent( "javax.swing.JSpinner" ) {
name: "tableRowCountSpinner"
"model": #SpinnerNumberModel0
"model": new javax.swing.SpinnerNumberModel {
minimum: 0
stepSize: 5
value: 20
}
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
@@ -251,6 +255,16 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 1"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "rowHeaderCheckBox"
"text": "row header"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "rowHeaderChanged", false ) )
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 1"
} )
add( new FormComponent( "javax.swing.JCheckBox" ) {
name: "showVerticalLinesCheckBox"
"text": "show vertical lines"