diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b9e27450..ec43a59f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ FlatLaf Change Log #### New features and improvements +- Table: Support rounded selection. (PR #856) - Button and ToggleButton: Added border colors for pressed and selected states. (issue #848) - Label: Support painting background with rounded corners. (issue #842) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java index 57635398b..a55b91a63 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatListUI.java @@ -324,8 +324,7 @@ protected void paintCell( Graphics g, int row, Rectangle rowBounds, ListCellRend (rendererComponent instanceof DefaultListCellRenderer || rendererComponent instanceof BasicComboBoxRenderer) && (selectionArc > 0 || - (selectionInsets != null && - (selectionInsets.top != 0 || selectionInsets.left != 0 || selectionInsets.bottom != 0 || selectionInsets.right != 0))) ) + (selectionInsets != null && !FlatUIUtils.isInsetsEmpty( selectionInsets ))) ) { // Because selection painting is done in the cell renderer, it would be // necessary to require a FlatLaf specific renderer to implement rounded selection. @@ -374,7 +373,15 @@ public void fillRect( int x, int y, int width, int height ) { rendererPane.paintComponent( g, rendererComponent, list, cx, rowBounds.y, cw, rowBounds.height, true ); } - /** @since 3 */ + /** + * Paints (rounded) cell selection. + * Supports {@link #selectionArc} and {@link #selectionInsets}. + *

+ * Note: This method is only invoked if either selection arc + * is greater than zero or if selection insets are not empty. + * + * @since 3 + */ protected void paintCellSelection( Graphics g, int row, int x, int y, int width, int height ) { float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight; arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f ); @@ -440,7 +447,8 @@ private boolean useUnitedRoundedSelection( boolean vertical, boolean horizontal * Paints a cell selection at the given coordinates. * The selection color must be set on the graphics context. *

- * This method is intended for use in custom cell renderers. + * This method is intended for use in custom cell renderers + * to support {@link #selectionArc} and {@link #selectionInsets}. * * @since 3 */ diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableCellBorder.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableCellBorder.java index b22dc6fc5..32cd12138 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableCellBorder.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableCellBorder.java @@ -65,8 +65,28 @@ public Color getLineColor() { return super.getLineColor(); } + @Override + public int getArc() { + if( c != null ) { + Integer selectionArc = getStyleFromTableUI( c, ui -> ui.selectionArc ); + if( selectionArc != null ) + return selectionArc; + } + return super.getArc(); + } + @Override public void paintBorder( Component c, Graphics g, int x, int y, int width, int height ) { + if( c != null ) { + Insets selectionInsets = getStyleFromTableUI( c, ui -> ui.selectionInsets ); + if( selectionInsets != null ) { + x += selectionInsets.left; + y += selectionInsets.top; + width -= selectionInsets.left + selectionInsets.right; + height -= selectionInsets.top + selectionInsets.bottom; + } + } + this.c = c; super.paintBorder( c, g, x, y, width, height ); this.c = null; diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java index a7b032eb3..c30d90bee 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTableUI.java @@ -24,6 +24,9 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.Shape; import java.awt.event.ActionEvent; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; @@ -41,16 +44,23 @@ import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.JViewport; +import javax.swing.ListSelectionModel; import javax.swing.LookAndFeel; import javax.swing.SwingConstants; import javax.swing.SwingUtilities; import javax.swing.UIManager; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.event.TableColumnModelEvent; +import javax.swing.event.TableColumnModelListener; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTableUI; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.JTableHeader; import javax.swing.table.TableCellRenderer; +import javax.swing.table.TableColumnModel; import com.formdev.flatlaf.FlatClientProperties; import com.formdev.flatlaf.icons.FlatCheckBoxIcon; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; @@ -95,6 +105,8 @@ * @uiDefault Table.intercellSpacing Dimension * @uiDefault Table.selectionInactiveBackground Color * @uiDefault Table.selectionInactiveForeground Color + * @uiDefault Table.selectionInsets Insets + * @uiDefault Table.selectionArc int * @uiDefault Table.paintOutsideAlternateRows boolean * @uiDefault Table.editorSelectAllOnStartEditing boolean * @@ -123,6 +135,8 @@ public class FlatTableUI @Styleable protected Color selectionForeground; @Styleable protected Color selectionInactiveBackground; @Styleable protected Color selectionInactiveForeground; + /** @since 3.5 */ @Styleable protected Insets selectionInsets; + /** @since 3.5 */ @Styleable protected int selectionArc; // for FlatTableCellBorder /** @since 2 */ @Styleable protected Insets cellMargins; @@ -136,6 +150,8 @@ public class FlatTableUI private PropertyChangeListener propertyChangeListener; private ComponentListener outsideAlternateRowsListener; + private ListSelectionListener rowSelectionListener; + private TableColumnModelListener columnSelectionListener; private Map oldStyleValues; public static ComponentUI createUI( JComponent c ) { @@ -162,6 +178,8 @@ protected void installDefaults() { selectionForeground = UIManager.getColor( "Table.selectionForeground" ); selectionInactiveBackground = UIManager.getColor( "Table.selectionInactiveBackground" ); selectionInactiveForeground = UIManager.getColor( "Table.selectionInactiveForeground" ); + selectionInsets = UIManager.getInsets( "Table.selectionInsets" ); + selectionArc = UIManager.getInt( "Table.selectionArc" ); toggleSelectionColors(); @@ -249,6 +267,28 @@ protected void installListeners() { propertyChangeListener = e -> { switch( e.getPropertyName() ) { + case "selectionModel": + if( rowSelectionListener != null ) { + Object oldModel = e.getOldValue(); + Object newModel = e.getNewValue(); + if( oldModel != null ) + ((ListSelectionModel)oldModel).removeListSelectionListener( rowSelectionListener ); + if( newModel != null ) + ((ListSelectionModel)newModel).addListSelectionListener( rowSelectionListener ); + } + break; + + case "columnModel": + if( columnSelectionListener != null ) { + Object oldModel = e.getOldValue(); + Object newModel = e.getNewValue(); + if( oldModel != null ) + ((TableColumnModel)oldModel).removeColumnModelListener( columnSelectionListener ); + if( newModel != null ) + ((TableColumnModel)newModel).addColumnModelListener( columnSelectionListener ); + } + break; + case FlatClientProperties.COMPONENT_FOCUS_OWNER: toggleSelectionColors(); break; @@ -262,6 +302,9 @@ protected void installListeners() { } }; table.addPropertyChangeListener( propertyChangeListener ); + + if( selectionArc > 0 ) + installRepaintRoundedSelectionListeners(); } @Override @@ -275,6 +318,14 @@ protected void uninstallListeners() { table.removeComponentListener( outsideAlternateRowsListener ); outsideAlternateRowsListener = null; } + if( rowSelectionListener != null ) { + table.getSelectionModel().removeListSelectionListener( rowSelectionListener ); + rowSelectionListener = null; + } + if( columnSelectionListener != null ) { + table.getColumnModel().removeColumnModelListener( columnSelectionListener ); + columnSelectionListener = null; + } } @Override @@ -351,6 +402,8 @@ else if( selFg == oldSelectionInactiveForeground ) protected Object applyStyleProperty( String key, Object value ) { if( "rowHeight".equals( key ) && value instanceof Integer ) value = UIScale.scale( (Integer) value ); + else if( "selectionArc".equals( key ) && value instanceof Integer && (Integer) value > 0 ) + installRepaintRoundedSelectionListeners(); return FlatStylingSupport.applyToAnnotatedObjectOrComponent( this, table, key, value ); } @@ -413,6 +466,7 @@ public void paint( Graphics g, JComponent c ) { double systemScaleFactor = UIScale.getSystemScaleFactor( (Graphics2D) g ); double lineThickness = (1. / systemScaleFactor) * (int) systemScaleFactor; + double lineOffset = (1. - lineThickness) + 0.05; // adding 0.05 to fix line location in some cases // Java 8 uses drawLine() to paint grid lines // Java 9+ uses fillRect() to paint grid lines (except for dragged column) @@ -455,11 +509,11 @@ public void fillRect( int x, int y, int width, int height ) { // reduce line thickness to avoid unstable painted line thickness if( lineThickness != 1 ) { if( horizontalLines && height == 1 && wasInvokedFromPaintGrid() ) { - super.fill( new Rectangle2D.Double( x, y, width, lineThickness ) ); + super.fill( new Rectangle2D.Double( x, y + lineOffset, width, lineThickness ) ); return; } if( verticalLines && width == 1 && y == 0 && wasInvokedFromPaintGrid() ) { - super.fill( new Rectangle2D.Double( x, y, lineThickness, height ) ); + super.fill( new Rectangle2D.Double( x + lineOffset, y, lineThickness, height ) ); return; } } @@ -477,6 +531,10 @@ private boolean wasInvokedFromMethod( String methodName ) { }; } + // rounded selection or selection insets + if( selectionArc > 0 || (selectionInsets != null && !FlatUIUtils.isInsetsEmpty( selectionInsets )) ) + g = new RoundedSelectionGraphics( g, UIManager.getColor( "Table.alternateRowColor" ) ); + super.paint( g, c ); } @@ -535,7 +593,7 @@ public void paintViewport( Graphics g, JComponent c, JViewport viewport ) { int x = viewport.getComponentOrientation().isLeftToRight() ? 0 : viewportWidth - tableWidth; for( int y = tableHeight, row = rowCount; y < viewportHeight; y += rowHeight, row++ ) { if( row % 2 != 0 ) - g.fillRect( x, y, tableWidth, rowHeight ); + paintAlternateRowBackground( g, -1, -1, x, y, tableWidth, rowHeight ); } // add listener on demand @@ -547,6 +605,300 @@ public void paintViewport( Graphics g, JComponent c, JViewport viewport ) { } } + /** + * Paints (rounded) alternate row background. + * Supports {@link #selectionArc} and {@link #selectionInsets}. + *

+ * Note: This method is only invoked if either selection arc + * is greater than zero or if selection insets are not empty. + * + * @since 3.5 + */ + protected void paintAlternateRowBackground( Graphics g, int row, int column, int x, int y, int width, int height ) { + Insets insets = (selectionInsets != null) ? (Insets) selectionInsets.clone() : null; + float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight; + arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f ); + + if( column >= 0 ) { + // selection insets + + // selection arc + if( column > 0 ) { + if( insets != null ) + insets.left = 0; + + if( table.getComponentOrientation().isLeftToRight() ) + arcTopLeft = arcBottomLeft = 0; + else + arcTopRight = arcBottomRight = 0; + } + if( column < table.getColumnCount() - 1 ) { + if( insets != null ) + insets.right = 0; + + if( table.getComponentOrientation().isLeftToRight() ) + arcTopRight = arcBottomRight = 0; + else + arcTopLeft = arcBottomLeft = 0; + } + } + + FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height, + UIScale.scale( insets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 ); + } + + /** + * Paints (rounded) cell selection. + * Supports {@link #selectionArc} and {@link #selectionInsets}. + *

+ * Note: This method is only invoked if either selection arc + * is greater than zero or if selection insets are not empty. + * + * @since 3.5 + */ + protected void paintCellSelection( Graphics g, int row, int column, int x, int y, int width, int height ) { + boolean rowSelAllowed = table.getRowSelectionAllowed(); + boolean colSelAllowed = table.getColumnSelectionAllowed(); + boolean rowSelOnly = rowSelAllowed && !colSelAllowed; + boolean colSelOnly = colSelAllowed && !rowSelAllowed; + boolean cellOnlySel = rowSelAllowed && colSelAllowed; + + // get selection state of surrounding cells + boolean leftSelected = (column > 0 && (rowSelOnly || table.isCellSelected( row, column - 1 ))); + boolean topSelected = (row > 0 && (colSelOnly || table.isCellSelected( row - 1, column ))); + boolean rightSelected = (column < table.getColumnCount() - 1 && (rowSelOnly || table.isCellSelected( row, column + 1 ))); + boolean bottomSelected = (row < table.getRowCount() - 1 && (colSelOnly || table.isCellSelected( row + 1, column ))); + if( !table.getComponentOrientation().isLeftToRight() ) { + boolean temp = leftSelected; + leftSelected = rightSelected; + rightSelected = temp; + } + + // selection insets + // (insets are applied to whole row if row-only selection is used, + // or to whole column if column-only selection is used, + // or to cell if cell selection is used) + Insets insets = (selectionInsets != null) ? (Insets) selectionInsets.clone() : null; + if( insets != null ) { + if( rowSelOnly && leftSelected ) + insets.left = 0; + if( rowSelOnly && rightSelected ) + insets.right = 0; + if( colSelOnly && topSelected ) + insets.top = 0; + if( colSelOnly && bottomSelected ) + insets.bottom = 0; + } + + // selection arc + float arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight; + arcTopLeft = arcTopRight = arcBottomLeft = arcBottomRight = UIScale.scale( selectionArc / 2f ); + if( selectionArc > 0 ) { + // note that intercellSpacing is not considered as a gap because + // grid lines are usually painted to intercell space + boolean hasRowGap = (rowSelOnly || cellOnlySel) && insets != null && (insets.top != 0 || insets.bottom != 0); + boolean hasColGap = (colSelOnly || cellOnlySel) && insets != null && (insets.left != 0 || insets.right != 0); + + if( leftSelected && !hasColGap ) + arcTopLeft = arcBottomLeft = 0; + if( rightSelected && !hasColGap ) + arcTopRight = arcBottomRight = 0; + if( topSelected && !hasRowGap ) + arcTopLeft = arcTopRight = 0; + if( bottomSelected && !hasRowGap ) + arcBottomLeft = arcBottomRight = 0; + } + + FlatUIUtils.paintSelection( (Graphics2D) g, x, y, width, height, + UIScale.scale( insets ), arcTopLeft, arcTopRight, arcBottomLeft, arcBottomRight, 0 ); + } + + /** + * Paints a cell selection at the given coordinates. + * The selection color must be set on the graphics context. + *

+ * This method is intended for use in custom cell renderers to support + * {@link #selectionArc} and {@link #selectionInsets}. + * + * @since 3.5 + */ + public static void paintCellSelection( JTable table, Graphics g, int row, int column, int x, int y, int width, int height ) { + if( !(table.getUI() instanceof FlatTableUI) ) + return; + + FlatTableUI ui = (FlatTableUI) table.getUI(); + ui.paintCellSelection( g, row, column, x, y, width, height ); + } + + private void installRepaintRoundedSelectionListeners() { + if( rowSelectionListener == null ) { + rowSelectionListener = this::repaintRoundedRowSelection; + table.getSelectionModel().addListSelectionListener( rowSelectionListener ); + } + + if( columnSelectionListener == null ) { + columnSelectionListener = new TableColumnModelListener() { + @Override + public void columnSelectionChanged( ListSelectionEvent e ) { + repaintRoundedColumnSelection( e ); + } + @Override public void columnRemoved( TableColumnModelEvent e ) {} + @Override public void columnMoved( TableColumnModelEvent e ) {} + @Override public void columnMarginChanged( ChangeEvent e ) {} + @Override public void columnAdded( TableColumnModelEvent e ) {} + }; + table.getColumnModel().addColumnModelListener( columnSelectionListener ); + } + } + + private void repaintRoundedRowSelection( ListSelectionEvent e ) { + if( selectionArc <= 0 || !table.getRowSelectionAllowed() ) + return; + + int rowCount = table.getRowCount(); + int columnCount = table.getColumnCount(); + if( rowCount <= 0 || columnCount <= 0 ) + return; + + // repaint including rows before and after changed selection + int firstRow = Math.max( 0, Math.min( e.getFirstIndex() - 1, rowCount - 1 ) ); + int lastRow = Math.max( 0, Math.min( e.getLastIndex() + 1, rowCount - 1 ) ); + Rectangle firstRect = table.getCellRect( firstRow, 0, false ); + Rectangle lastRect = table.getCellRect( lastRow, columnCount - 1, false ); + table.repaint( firstRect.union( lastRect ) ); + } + + private void repaintRoundedColumnSelection( ListSelectionEvent e ) { + if( selectionArc <= 0 || !table.getColumnSelectionAllowed() ) + return; + + int rowCount = table.getRowCount(); + int columnCount = table.getColumnCount(); + if( rowCount <= 0 || columnCount <= 0 ) + return; + + // limit to selected rows for cell selection + int firstRow = 0; + int lastRow = rowCount - 1; + if( table.getRowSelectionAllowed() ) { + firstRow = table.getSelectionModel().getMinSelectionIndex(); + lastRow = table.getSelectionModel().getMaxSelectionIndex(); + } + + // repaint including columns before and after changed selection + int firstColumn = Math.max( 0, Math.min( e.getFirstIndex() - 1, columnCount - 1 ) ); + int lastColumn = Math.max( 0, Math.min( e.getLastIndex() + 1, columnCount - 1 ) ); + Rectangle firstRect = table.getCellRect( firstRow, firstColumn, false ); + Rectangle lastRect = table.getCellRect( lastRow, lastColumn, false ); + table.repaint( firstRect.union( lastRect ) ); + } + + //---- class RoundedSelectionGraphics ------------------------------------- + + /** + * Because selection painting is done in the cell renderer, it would be + * necessary to require a FlatLaf specific renderer to implement rounded selection. + * Using a LaF specific renderer was avoided because often a custom renderer is + * already used in applications. Then either the rounded selection is not used, + * or the application has to be changed to extend a FlatLaf renderer. + *

+ * To solve this, a graphics proxy is used that paints rounded selection + * if row/column/cell is selected and the renderer wants to fill the background. + */ + private class RoundedSelectionGraphics + extends Graphics2DProxy + { + private final Color alternateRowColor; + + // used to avoid endless loop in case that paintCellSelection() invokes + // g.fillRect() with full bounds (selectionInsets is 0,0,0,0) + private boolean inPaintSelection; + + RoundedSelectionGraphics( Graphics delegate, Color alternateRowColor ) { + super( (Graphics2D) delegate ); + this.alternateRowColor = alternateRowColor; + } + + @Override + public Graphics create() { + return new RoundedSelectionGraphics( super.create(), alternateRowColor ); + } + + @Override + public Graphics create( int x, int y, int width, int height ) { + return new RoundedSelectionGraphics( super.create( x, y, width, height ), alternateRowColor ); + } + + @Override + public void fillRect( int x, int y, int width, int height ) { + if( fillCellSelection( x, y, width, height ) ) + return; + + super.fillRect( x, y, width, height ); + } + + @Override + public void fill( Shape shape ) { + if( shape instanceof Rectangle2D ) { + Rectangle2D r = (Rectangle2D) shape; + double x = r.getX(); + double y = r.getY(); + double width = r.getWidth(); + double height = r.getHeight(); + if( x == (int) x && y == (int) y && width == (int) width && height == (int) height ) { + if( fillCellSelection( (int) x, (int) y, (int) width, (int) height ) ) + return; + } + } + + super.fill( shape ); + } + + private boolean fillCellSelection( int x, int y, int width, int height ) { + if( inPaintSelection ) + return false; + + Color color; + Component rendererComponent; + if( x == 0 && y == 0 && + ((color = getColor()) == table.getSelectionBackground() || + (alternateRowColor != null && color == alternateRowColor)) && + (rendererComponent = findActiveRendererComponent()) != null && + width == rendererComponent.getWidth() && + height == rendererComponent.getHeight() ) + { + Point location = rendererComponent.getLocation(); + int row = table.rowAtPoint( location ); + int column = table.columnAtPoint( location ); + if( row >= 0 && column >= 0 ) { + inPaintSelection = true; + if( color == table.getSelectionBackground() ) + paintCellSelection( this, row, column, x, y, width, height ); + else + paintAlternateRowBackground( this, row, column, x, y, width, height ); + inPaintSelection = false; + + return true; + } + } + return false; + } + + /** + * A CellRendererPane may contain multiple components, if multiple renderers + * are used. Inactive renderer components have size {@code 0x0}. + */ + private Component findActiveRendererComponent() { + int count = rendererPane.getComponentCount(); + for( int i = 0; i < count; i++ ) { + Component c = rendererPane.getComponent( i ); + if( c.getWidth() > 0 && c.getHeight() > 0 ) + return c; + } + return null; + } + } + //---- class OutsideAlternateRowsListener --------------------------------- /** diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java index 115185cae..011ac2e69 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatUIUtils.java @@ -124,6 +124,11 @@ public static void setInsets( Insets dest, Insets src ) { dest.right = src.right; } + /** @since 3.5 */ + public static boolean isInsetsEmpty( Insets insets ) { + return insets.top == 0 && insets.left == 0 && insets.bottom == 0 && insets.right == 0; + } + public static Color getUIColor( String key, int defaultColorRGB ) { Color color = UIManager.getColor( key ); return (color != null) ? color : new Color( defaultColorRGB ); diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java index ade216816..ea536a16c 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableInfo.java @@ -800,6 +800,8 @@ void table() { "selectionForeground", Color.class, "selectionInactiveBackground", Color.class, "selectionInactiveForeground", Color.class, + "selectionInsets", Insets.class, + "selectionArc", int.class, // FlatTableCellBorder "cellMargins", Insets.class, diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java index 2fd174aa5..ed4708fe4 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyleableValue.java @@ -305,6 +305,9 @@ void comboBox() { testColor( c, ui, "buttonPressedArrowColor", 0x123456 ); testColor( c, ui, "popupBackground", 0x123456 ); + testInsets( c, ui, "popupInsets", 1,2,3,4 ); + testInsets( c, ui, "selectionInsets", 1,2,3,4 ); + testInteger( c, ui, "selectionArc", 123 ); // border flatRoundBorder( c, ui ); @@ -367,6 +370,8 @@ void list() { testColor( c, ui, "selectionForeground", 0x123456 ); testColor( c, ui, "selectionInactiveBackground", 0x123456 ); testColor( c, ui, "selectionInactiveForeground", 0x123456 ); + testInsets( c, ui, "selectionInsets", 1,2,3,4 ); + testInteger( c, ui, "selectionArc", 123 ); // FlatListCellBorder testInsets( c, ui, "cellMargins", 1,2,3,4 ); @@ -802,6 +807,8 @@ void table() { testColor( c, ui, "selectionForeground", 0x123456 ); testColor( c, ui, "selectionInactiveBackground", 0x123456 ); testColor( c, ui, "selectionInactiveForeground", 0x901324 ); + testInsets( c, ui, "selectionInsets", 1,2,3,4 ); + testInteger( c, ui, "selectionArc", 123 ); // FlatTableCellBorder testInsets( c, ui, "cellMargins", 1,2,3,4 ); @@ -931,6 +938,8 @@ void tree() { testColor( c, ui, "selectionInactiveBackground", 0x123456 ); testColor( c, ui, "selectionInactiveForeground", 0x123456 ); testColor( c, ui, "selectionBorderColor", 0x123456 ); + testInsets( c, ui, "selectionInsets", 1,2,3,4 ); + testInteger( c, ui, "selectionArc", 123 ); testBoolean( c, ui, "wideSelection", true ); testBoolean( c, ui, "showCellFocusIndicator", true ); diff --git a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java index 6ac0c60fc..204cf8bee 100644 --- a/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java +++ b/flatlaf-core/src/test/java/com/formdev/flatlaf/ui/TestFlatStyling.java @@ -987,6 +987,8 @@ void table() { ui.applyStyle( "selectionForeground: #fff" ); ui.applyStyle( "selectionInactiveBackground: #fff" ); ui.applyStyle( "selectionInactiveForeground: #fff" ); + ui.applyStyle( "selectionInsets: 1,2,3,4" ); + ui.applyStyle( "selectionArc: 8" ); // FlatTableCellBorder ui.applyStyle( "cellMargins: 1,2,3,4" ); diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DataComponentsPanel.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DataComponentsPanel.java index 952f7e01a..e936658b1 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DataComponentsPanel.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DataComponentsPanel.java @@ -25,6 +25,8 @@ import javax.swing.table.*; import javax.swing.tree.*; import com.formdev.flatlaf.FlatClientProperties; +import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.util.ColorFunctions; import net.miginfocom.swing.*; /** @@ -93,10 +95,12 @@ private void dndChanged() { private void rowSelectionChanged() { table1.setRowSelectionAllowed( rowSelectionCheckBox.isSelected() ); + roundedSelectionChanged(); } private void columnSelectionChanged() { table1.setColumnSelectionAllowed( columnSelectionCheckBox.isSelected() ); + roundedSelectionChanged(); } private void showHorizontalLinesChanged() { @@ -127,6 +131,28 @@ private void intercellSpacingPropertyChange() { intercellSpacingCheckBox.setSelected( table1.getRowMargin() != 0 ); } + private void roundedSelectionChanged() { + String style = null; + if( roundedSelectionCheckBox.isSelected() ) { + style = rowSelectionCheckBox.isSelected() + ? "selectionArc: 6; selectionInsets: 0,1,0,1" + : "selectionArc: 6"; + } + table1.putClientProperty( FlatClientProperties.STYLE, style ); + } + + private void alternatingRowsChanged() { + Color alternateRowColor = null; + if( alternatingRowsCheckBox.isSelected() ) { + Color background = table1.getBackground(); + alternateRowColor = FlatLaf.isLafDark() + ? ColorFunctions.lighten( background, 0.05f ) + : ColorFunctions.darken( background, 0.05f ); + } + UIManager.put( "Table.alternateRowColor", alternateRowColor ); + table1.repaint(); + } + @SuppressWarnings( { "unchecked", "rawtypes" } ) private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents @@ -151,12 +177,14 @@ private void initComponents() { JScrollPane scrollPane5 = new JScrollPane(); table1 = new JTable(); JPanel tableOptionsPanel = new JPanel(); + roundedSelectionCheckBox = new JCheckBox(); showHorizontalLinesCheckBox = new JCheckBox(); showVerticalLinesCheckBox = new JCheckBox(); intercellSpacingCheckBox = new JCheckBox(); redGridColorCheckBox = new JCheckBox(); rowSelectionCheckBox = new JCheckBox(); columnSelectionCheckBox = new JCheckBox(); + alternatingRowsCheckBox = new JCheckBox(); dndCheckBox = new JCheckBox(); JPopupMenu popupMenu2 = new JPopupMenu(); JMenuItem menuItem3 = new JMenuItem(); @@ -403,44 +431,56 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { "[]0" + "[]0" + "[]0" + + "[]0" + + "[]0" + "[]0")); + //---- roundedSelectionCheckBox ---- + roundedSelectionCheckBox.setText("rounded selection"); + roundedSelectionCheckBox.addActionListener(e -> roundedSelectionChanged()); + tableOptionsPanel.add(roundedSelectionCheckBox, "cell 0 0"); + //---- showHorizontalLinesCheckBox ---- showHorizontalLinesCheckBox.setText("show horizontal lines"); showHorizontalLinesCheckBox.addActionListener(e -> showHorizontalLinesChanged()); - tableOptionsPanel.add(showHorizontalLinesCheckBox, "cell 0 0"); + tableOptionsPanel.add(showHorizontalLinesCheckBox, "cell 0 1"); //---- showVerticalLinesCheckBox ---- showVerticalLinesCheckBox.setText("show vertical lines"); showVerticalLinesCheckBox.addActionListener(e -> showVerticalLinesChanged()); - tableOptionsPanel.add(showVerticalLinesCheckBox, "cell 0 1"); + tableOptionsPanel.add(showVerticalLinesCheckBox, "cell 0 2"); //---- intercellSpacingCheckBox ---- intercellSpacingCheckBox.setText("intercell spacing"); intercellSpacingCheckBox.addActionListener(e -> intercellSpacingChanged()); - tableOptionsPanel.add(intercellSpacingCheckBox, "cell 0 2"); + tableOptionsPanel.add(intercellSpacingCheckBox, "cell 0 3"); //---- redGridColorCheckBox ---- redGridColorCheckBox.setText("red grid color"); redGridColorCheckBox.addActionListener(e -> redGridColorChanged()); - tableOptionsPanel.add(redGridColorCheckBox, "cell 0 3"); + tableOptionsPanel.add(redGridColorCheckBox, "cell 0 4"); //---- rowSelectionCheckBox ---- rowSelectionCheckBox.setText("row selection"); rowSelectionCheckBox.setSelected(true); rowSelectionCheckBox.addActionListener(e -> rowSelectionChanged()); - tableOptionsPanel.add(rowSelectionCheckBox, "cell 0 4"); + tableOptionsPanel.add(rowSelectionCheckBox, "cell 0 5"); //---- columnSelectionCheckBox ---- columnSelectionCheckBox.setText("column selection"); columnSelectionCheckBox.addActionListener(e -> columnSelectionChanged()); - tableOptionsPanel.add(columnSelectionCheckBox, "cell 0 5"); + tableOptionsPanel.add(columnSelectionCheckBox, "cell 0 6"); + + //---- alternatingRowsCheckBox ---- + alternatingRowsCheckBox.setText("alternating rows"); + alternatingRowsCheckBox.addActionListener(e -> alternatingRowsChanged()); + tableOptionsPanel.add(alternatingRowsCheckBox, "cell 0 7"); //---- dndCheckBox ---- dndCheckBox.setText("enable drag and drop"); dndCheckBox.setMnemonic('D'); dndCheckBox.addActionListener(e -> dndChanged()); - tableOptionsPanel.add(dndCheckBox, "cell 0 6"); + tableOptionsPanel.add(dndCheckBox, "cell 0 8"); } add(tableOptionsPanel, "cell 4 3"); @@ -477,12 +517,14 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { private JTree tree3; private JTree tree2; private JTable table1; + private JCheckBox roundedSelectionCheckBox; private JCheckBox showHorizontalLinesCheckBox; private JCheckBox showVerticalLinesCheckBox; private JCheckBox intercellSpacingCheckBox; private JCheckBox redGridColorCheckBox; private JCheckBox rowSelectionCheckBox; private JCheckBox columnSelectionCheckBox; + private JCheckBox alternatingRowsCheckBox; private JCheckBox dndCheckBox; // JFormDesigner - End of variables declaration //GEN-END:variables diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DataComponentsPanel.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DataComponentsPanel.jfd index bde3e024a..0f729bb18 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DataComponentsPanel.jfd +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/DataComponentsPanel.jfd @@ -343,9 +343,19 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets 0,hidemode 3" "$columnConstraints": "[]" - "$rowConstraints": "[]0[]0[]0[]0[]0[]0[]0" + "$rowConstraints": "[]0[]0[]0[]0[]0[]0[]0[]0[]0" } ) { name: "tableOptionsPanel" + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "roundedSelectionCheckBox" + "text": "rounded selection" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "roundedSelectionChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "showHorizontalLinesCheckBox" "text": "show horizontal lines" @@ -354,7 +364,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showHorizontalLinesChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 0" + "value": "cell 0 1" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "showVerticalLinesCheckBox" @@ -364,7 +374,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showVerticalLinesChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 1" + "value": "cell 0 2" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "intercellSpacingCheckBox" @@ -374,7 +384,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "intercellSpacingChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 2" + "value": "cell 0 3" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "redGridColorCheckBox" @@ -384,7 +394,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "redGridColorChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 3" + "value": "cell 0 4" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "rowSelectionCheckBox" @@ -395,7 +405,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "rowSelectionChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 4" + "value": "cell 0 5" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "columnSelectionCheckBox" @@ -405,7 +415,17 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "columnSelectionChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 5" + "value": "cell 0 6" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "alternatingRowsCheckBox" + "text": "alternating rows" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "alternatingRowsChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 7" } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "dndCheckBox" @@ -416,7 +436,7 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "dndChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 6" + "value": "cell 0 8" } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 4 3" diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.java index a21f27e4b..57cc183b3 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.java @@ -47,6 +47,7 @@ import com.formdev.flatlaf.icons.FlatMenuArrowIcon; import com.formdev.flatlaf.ui.FlatEmptyBorder; import com.formdev.flatlaf.ui.FlatListUI; +import com.formdev.flatlaf.ui.FlatTableUI; import com.formdev.flatlaf.util.UIScale; import com.jidesoft.swing.*; import com.jidesoft.swing.CheckBoxTreeCellRenderer; @@ -131,6 +132,14 @@ public static void main( String[] args ) { initTableEditors( table1 ); initTableEditors( xTable1 ); + // table selection listeners + table1.getSelectionModel().addListSelectionListener( e -> { + System.out.printf( "row sel %d-%d adj=%b\n", e.getFirstIndex(), e.getLastIndex(), e.getValueIsAdjusting() ); + } ); + table1.getColumnModel().getSelectionModel().addListSelectionListener( e -> { + System.out.printf( "column sel %d-%d adj=%b\n", e.getFirstIndex(), e.getLastIndex(), e.getValueIsAdjusting() ); + } ); + // JXTable Highlighter simpleStriping = HighlighterFactory.createSimpleStriping(); PatternPredicate patternPredicate = new PatternPredicate( "^J", 2 ); @@ -164,6 +173,8 @@ private void initTableEditors( JTable table ) { JComboBox editableComboBox = new JComboBox<>( months ); editableComboBox.setEditable( true ); cm.getColumn(3).setCellEditor( new DefaultCellEditor( editableComboBox ) ); + +// table.setDefaultRenderer( Object.class, new TestLabelRoundedTableCellRenderer() ); } private void expandTree( JTree tree ) { @@ -229,6 +240,65 @@ private void sortIconPositionChanged() { FlatLaf.updateUILater(); } + private void roundedSelectionChanged() { + String style = roundedSelectionCheckBox.isSelected() ? "selectionArc: 12; " : ""; + int left = leftSelectionInsetsCheckBox.isSelected() ? 2 : 0; + int right = rightSelectionInsetsCheckBox.isSelected() ? 2 : 0; + int top = topSelectionInsetsCheckBox.isSelected() ? 2 : 0; + int bottom = bottomSelectionInsetsCheckBox.isSelected() ? 2 : 0; + if( left > 0 || right > 0 || top > 0 || bottom > 0 ) + style += "selectionInsets: " + top + ',' + left + ',' + bottom + ',' + right; + if( style.isEmpty() ) + style = null; + + list1.putClientProperty( FlatClientProperties.STYLE, style ); + list2.putClientProperty( FlatClientProperties.STYLE, style ); + tree1.putClientProperty( FlatClientProperties.STYLE, style ); + tree2.putClientProperty( FlatClientProperties.STYLE, style ); + xTree1.putClientProperty( FlatClientProperties.STYLE, style ); + checkBoxTree1.putClientProperty( FlatClientProperties.STYLE, style ); + table1.putClientProperty( FlatClientProperties.STYLE, style ); + xTable1.putClientProperty( FlatClientProperties.STYLE, style ); + xTreeTable1.putClientProperty( FlatClientProperties.STYLE, style ); + + // initial selection + if( style != null ) { + initSelection( list1 ); + initSelection( list2 ); + initSelection( tree1 ); + initSelection( tree2 ); + initSelection( xTree1 ); + initSelection( checkBoxTree1 ); + initSelection( table1 ); + initSelection( xTable1 ); + initSelection( xTreeTable1 ); + } + + if( paintOutsideAlternateRowsCheckBox.isSelected() ) + table1ScrollPane.repaint(); + } + + private static void initSelection( JList list ) { + if( list.isSelectionEmpty() ) { + list.addSelectionInterval( 1, 2 ); + list.addSelectionInterval( 5, 5 ); + } + } + + private static void initSelection( JTree tree ) { + if( tree.isSelectionEmpty() ) { + tree.addSelectionInterval( 1, 2 ); + tree.addSelectionInterval( 5, 5 ); + } + } + + private static void initSelection( JTable table ) { + if( table.getSelectedRowCount() == 0 ) { + table.addRowSelectionInterval( 1, 2 ); + table.addRowSelectionInterval( 5, 5 ); + } + } + private void dndChanged() { boolean dnd = dndCheckBox.isSelected(); list1.setDragEnabled( dnd ); @@ -535,6 +605,12 @@ private void treeMouseClicked( MouseEvent e ) { public void applyComponentOrientation( ComponentOrientation o ) { super.applyComponentOrientation( o ); + // always use left-to-right for options panels + generalOptionsPanel.applyComponentOrientation( ComponentOrientation.LEFT_TO_RIGHT ); + listOptionsPanel.applyComponentOrientation( ComponentOrientation.LEFT_TO_RIGHT ); + treeOptionsPanel.applyComponentOrientation( ComponentOrientation.LEFT_TO_RIGHT ); + tableOptionsPanel.applyComponentOrientation( ComponentOrientation.LEFT_TO_RIGHT ); + // 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 ); @@ -596,16 +672,22 @@ private void initComponents() { JLabel label2 = new JLabel(); xTreeTable1ScrollPane = new JScrollPane(); xTreeTable1 = new JXTreeTable(); - JPanel panel5 = new JPanel(); + generalOptionsPanel = new JPanel(); + roundedSelectionCheckBox = new JCheckBox(); + JLabel label6 = new JLabel(); + topSelectionInsetsCheckBox = new JCheckBox(); + bottomSelectionInsetsCheckBox = new JCheckBox(); + leftSelectionInsetsCheckBox = new JCheckBox(); + rightSelectionInsetsCheckBox = new JCheckBox(); dndCheckBox = new JCheckBox(); - JPanel panel6 = new JPanel(); + listOptionsPanel = new JPanel(); JLabel listRendererLabel = new JLabel(); listRendererComboBox = new JComboBox<>(); JLabel listLayoutOrientationLabel = new JLabel(); listLayoutOrientationField = new JComboBox<>(); JLabel listVisibleRowCountLabel = new JLabel(); listVisibleRowCountSpinner = new JSpinner(); - JPanel treeOptionsPanel = new JPanel(); + treeOptionsPanel = new JPanel(); JLabel treeRendererLabel = new JLabel(); treeRendererComboBox = new JComboBox<>(); treeWideSelectionCheckBox = new JCheckBox(); @@ -614,7 +696,7 @@ private void initComponents() { treeRedLinesCheckBox = new JCheckBox(); treeEditableCheckBox = new JCheckBox(); treeShowDefaultIconsCheckBox = new JCheckBox(); - JPanel tableOptionsPanel = new JPanel(); + tableOptionsPanel = new JPanel(); JLabel autoResizeModeLabel = new JLabel(); autoResizeModeField = new JComboBox<>(); JLabel sortIconPositionLabel = new JLabel(); @@ -872,30 +954,68 @@ public void mouseClicked(MouseEvent e) { } add(xTreeTable1ScrollPane, "cell 4 3 2 1"); - //======== panel5 ======== + //======== generalOptionsPanel ======== { - panel5.setBorder(new TitledBorder("General Control")); - panel5.putClientProperty("FlatLaf.internal.testing.ignore", true); - panel5.setLayout(new MigLayout( - "hidemode 3", + generalOptionsPanel.setBorder(new TitledBorder("General Control")); + generalOptionsPanel.putClientProperty("FlatLaf.internal.testing.ignore", true); + generalOptionsPanel.setLayout(new MigLayout( + "insets 8,hidemode 3", // columns - "[fill]", + "[left]", // rows + "[]" + + "[]0" + + "[]0" + + "[]rel" + "[]")); + //---- roundedSelectionCheckBox ---- + roundedSelectionCheckBox.setText("rounded selection"); + roundedSelectionCheckBox.setMnemonic('D'); + roundedSelectionCheckBox.addActionListener(e -> roundedSelectionChanged()); + generalOptionsPanel.add(roundedSelectionCheckBox, "cell 0 0"); + + //---- label6 ---- + label6.setText("Selection insets:"); + generalOptionsPanel.add(label6, "cell 0 1"); + + //---- topSelectionInsetsCheckBox ---- + topSelectionInsetsCheckBox.setText("top"); + topSelectionInsetsCheckBox.setMnemonic('D'); + topSelectionInsetsCheckBox.addActionListener(e -> roundedSelectionChanged()); + generalOptionsPanel.add(topSelectionInsetsCheckBox, "cell 0 2,gapx ind"); + + //---- bottomSelectionInsetsCheckBox ---- + bottomSelectionInsetsCheckBox.setText("bottom"); + bottomSelectionInsetsCheckBox.setMnemonic('D'); + bottomSelectionInsetsCheckBox.addActionListener(e -> roundedSelectionChanged()); + generalOptionsPanel.add(bottomSelectionInsetsCheckBox, "cell 0 2"); + + //---- leftSelectionInsetsCheckBox ---- + leftSelectionInsetsCheckBox.setText("left"); + leftSelectionInsetsCheckBox.setMnemonic('D'); + leftSelectionInsetsCheckBox.addActionListener(e -> roundedSelectionChanged()); + generalOptionsPanel.add(leftSelectionInsetsCheckBox, "cell 0 3,gapx ind"); + + //---- rightSelectionInsetsCheckBox ---- + rightSelectionInsetsCheckBox.setText("right"); + rightSelectionInsetsCheckBox.setMnemonic('D'); + rightSelectionInsetsCheckBox.addActionListener(e -> roundedSelectionChanged()); + generalOptionsPanel.add(rightSelectionInsetsCheckBox, "cell 0 3"); + //---- dndCheckBox ---- dndCheckBox.setText("drag and drop"); dndCheckBox.setMnemonic('D'); dndCheckBox.addActionListener(e -> dndChanged()); - panel5.add(dndCheckBox, "cell 0 0"); + generalOptionsPanel.add(dndCheckBox, "cell 0 4"); } - add(panel5, "cell 0 4 4 1"); + add(generalOptionsPanel, "cell 0 4 4 1"); - //======== panel6 ======== + //======== listOptionsPanel ======== { - panel6.setBorder(new TitledBorder("JList Control")); - panel6.setLayout(new MigLayout( - "hidemode 3", + listOptionsPanel.setBorder(new TitledBorder("JList Control")); + listOptionsPanel.setLayout(new MigLayout( + "insets 8,hidemode 3", // columns "[fill]" + "[fill]", @@ -906,7 +1026,7 @@ public void mouseClicked(MouseEvent e) { //---- listRendererLabel ---- listRendererLabel.setText("Renderer:"); - panel6.add(listRendererLabel, "cell 0 0"); + listOptionsPanel.add(listRendererLabel, "cell 0 0"); //---- listRendererComboBox ---- listRendererComboBox.setModel(new DefaultComboBoxModel<>(new String[] { @@ -916,11 +1036,11 @@ public void mouseClicked(MouseEvent e) { "labelRounded" })); listRendererComboBox.addActionListener(e -> listRendererChanged()); - panel6.add(listRendererComboBox, "cell 1 0"); + listOptionsPanel.add(listRendererComboBox, "cell 1 0"); //---- listLayoutOrientationLabel ---- listLayoutOrientationLabel.setText("Orientation:"); - panel6.add(listLayoutOrientationLabel, "cell 0 1"); + listOptionsPanel.add(listLayoutOrientationLabel, "cell 0 1"); //---- listLayoutOrientationField ---- listLayoutOrientationField.setModel(new DefaultComboBoxModel<>(new String[] { @@ -929,25 +1049,25 @@ public void mouseClicked(MouseEvent e) { "horzontal wrap" })); listLayoutOrientationField.addActionListener(e -> listLayoutOrientationChanged()); - panel6.add(listLayoutOrientationField, "cell 1 1"); + listOptionsPanel.add(listLayoutOrientationField, "cell 1 1"); //---- listVisibleRowCountLabel ---- listVisibleRowCountLabel.setText("Visible row count:"); - panel6.add(listVisibleRowCountLabel, "cell 0 2"); + listOptionsPanel.add(listVisibleRowCountLabel, "cell 0 2"); //---- listVisibleRowCountSpinner ---- listVisibleRowCountSpinner.setModel(new SpinnerNumberModel(8, 0, null, 1)); listVisibleRowCountSpinner.addChangeListener(e -> listVisibleRowCountChanged()); - panel6.add(listVisibleRowCountSpinner, "cell 1 2"); + listOptionsPanel.add(listVisibleRowCountSpinner, "cell 1 2"); } - add(panel6, "cell 0 4 4 1"); + add(listOptionsPanel, "cell 0 4 4 1"); //======== treeOptionsPanel ======== { treeOptionsPanel.setBorder(new TitledBorder("JTree Control")); treeOptionsPanel.putClientProperty("FlatLaf.internal.testing.ignore", true); treeOptionsPanel.setLayout(new MigLayout( - "hidemode 3", + "insets 8,hidemode 3", // columns "[left]", // rows @@ -1014,7 +1134,7 @@ public void mouseClicked(MouseEvent e) { tableOptionsPanel.setBorder(new TitledBorder("JTable Control")); tableOptionsPanel.putClientProperty("FlatLaf.internal.testing.ignore", true); tableOptionsPanel.setLayout(new MigLayout( - "hidemode 3", + "insets 8,hidemode 3", // columns "[]" + "[fill]" + @@ -1132,10 +1252,18 @@ public void mouseClicked(MouseEvent e) { private CheckBoxTree checkBoxTree1; private JScrollPane xTreeTable1ScrollPane; private JXTreeTable xTreeTable1; + private JPanel generalOptionsPanel; + private JCheckBox roundedSelectionCheckBox; + private JCheckBox topSelectionInsetsCheckBox; + private JCheckBox bottomSelectionInsetsCheckBox; + private JCheckBox leftSelectionInsetsCheckBox; + private JCheckBox rightSelectionInsetsCheckBox; private JCheckBox dndCheckBox; + private JPanel listOptionsPanel; private JComboBox listRendererComboBox; private JComboBox listLayoutOrientationField; private JSpinner listVisibleRowCountSpinner; + private JPanel treeOptionsPanel; private JComboBox treeRendererComboBox; private JCheckBox treeWideSelectionCheckBox; private JCheckBox treePaintSelectionCheckBox; @@ -1143,6 +1271,7 @@ public void mouseClicked(MouseEvent e) { private JCheckBox treeRedLinesCheckBox; private JCheckBox treeEditableCheckBox; private JCheckBox treeShowDefaultIconsCheckBox; + private JPanel tableOptionsPanel; private JComboBox autoResizeModeField; private JComboBox sortIconPositionComboBox; private JCheckBox showHorizontalLinesCheckBox; @@ -1683,4 +1812,46 @@ public Component getTableCellRendererComponent( JTable table, Object value, bool return this; } } + + //---- class TestLabelRoundedTableCellRenderer ---------------------------- + + @SuppressWarnings( "unused" ) + private static class TestLabelRoundedTableCellRenderer + extends JLabel + implements TableCellRenderer + { + private JTable table; + private int row; + private int column; + private boolean isSelected; + + TestLabelRoundedTableCellRenderer() { + setBorder( new FlatEmptyBorder( 1, 6, 1, 6 ) ); + } + + @Override + public Component getTableCellRendererComponent( JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column ) + { + this.table = table; + this.row = row; + this.column = column; + this.isSelected = isSelected; + + setText( String.valueOf( value ) ); + setBackground( isSelected ? Color.green : table.getBackground() ); + setForeground( isSelected ? Color.blue : table.getForeground() ); + return this; + } + + @Override + protected void paintComponent( Graphics g ) { + if( isSelected ) { + g.setColor( getBackground() ); + FlatTableUI.paintCellSelection( table, g, row, column, 0, 0, getWidth(), getHeight() ); + } + + super.paintComponent( g ); + } + } } diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.jfd index 066ca1c9e..d792fee6c 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatComponents2Test.jfd @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "8.0.0.0.194" Java: "17.0.2" encoding: "UTF-8" +JFDML JFormDesigner: "8.2.2.0.9999" Java: "21.0.1" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -297,13 +297,77 @@ new FormModel { "value": "cell 4 3 2 1" } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { - "$layoutConstraints": "hidemode 3" - "$columnConstraints": "[fill]" - "$rowConstraints": "[]" + "$layoutConstraints": "insets 8,hidemode 3" + "$columnConstraints": "[left]" + "$rowConstraints": "[][]0[]0[]rel[]" } ) { - name: "panel5" + name: "generalOptionsPanel" "border": new javax.swing.border.TitledBorder( "General Control" ) "$client.FlatLaf.internal.testing.ignore": true + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "roundedSelectionCheckBox" + "text": "rounded selection" + "mnemonic": 68 + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "roundedSelectionChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label6" + "text": "Selection insets:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "topSelectionInsetsCheckBox" + "text": "top" + "mnemonic": 68 + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "roundedSelectionChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2,gapx ind" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "bottomSelectionInsetsCheckBox" + "text": "bottom" + "mnemonic": 68 + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "roundedSelectionChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "leftSelectionInsetsCheckBox" + "text": "left" + "mnemonic": 68 + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "roundedSelectionChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3,gapx ind" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "rightSelectionInsetsCheckBox" + "text": "right" + "mnemonic": 68 + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "roundedSelectionChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "dndCheckBox" "text": "drag and drop" @@ -313,18 +377,21 @@ new FormModel { } addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "dndChanged", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { - "value": "cell 0 0" + "value": "cell 0 4" } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 4 4 1" } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { - "$layoutConstraints": "hidemode 3" + "$layoutConstraints": "insets 8,hidemode 3" "$columnConstraints": "[fill][fill]" "$rowConstraints": "[][][]" } ) { - name: "panel6" + name: "listOptionsPanel" "border": new javax.swing.border.TitledBorder( "JList Control" ) + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } add( new FormComponent( "javax.swing.JLabel" ) { name: "listRendererLabel" "text": "Renderer:" @@ -392,13 +459,16 @@ new FormModel { "value": "cell 0 4 4 1" } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { - "$layoutConstraints": "hidemode 3" + "$layoutConstraints": "insets 8,hidemode 3" "$columnConstraints": "[left]" "$rowConstraints": "[][]0[]0[]0[]" } ) { name: "treeOptionsPanel" "border": new javax.swing.border.TitledBorder( "JTree Control" ) "$client.FlatLaf.internal.testing.ignore": true + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } add( new FormComponent( "javax.swing.JLabel" ) { name: "treeRendererLabel" "text": "Renderer:" @@ -491,13 +561,16 @@ new FormModel { "value": "cell 0 4 4 1" } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { - "$layoutConstraints": "hidemode 3" + "$layoutConstraints": "insets 8,hidemode 3" "$columnConstraints": "[][fill][fill]" "$rowConstraints": "[][]0[]0[]0[]0" } ) { name: "tableOptionsPanel" "border": new javax.swing.border.TitledBorder( "JTable Control" ) "$client.FlatLaf.internal.testing.ignore": true + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } add( new FormComponent( "javax.swing.JLabel" ) { name: "autoResizeModeLabel" "text": "Auto resize mode:"