diff --git a/CHANGELOG.md b/CHANGELOG.md index 27a602baa..e24a63988 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,8 @@ FlatLaf Change Log - CheckBox: Support styling indeterminate state of [tri-state check boxes](https://www.javadoc.io/doc/com.formdev/flatlaf-extras/latest/com/formdev/flatlaf/extras/components/FlatTriStateCheckBox.html). - (issue #919) + (PR #936; issue #919) +- List: Support for alternate row highlighting. (PR #939) - Tree: Support for alternate row highlighting. (PR #903) - Tree: Support wide cell renderer. (issue #922) - Extras: `FlatSVGIcon` color filters now can access painting component to 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 13f3b9155..59e814e6c 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 @@ -303,13 +303,24 @@ protected void paintCell( Graphics g, int row, Rectangle rowBounds, ListCellRend { boolean isSelected = selModel.isSelectedIndex( row ); + // paint alternating rows + if( alternateRowColor != null && row % 2 != 0 && + !"ComboBox.list".equals( list.getName() ) ) // combobox does not support alternate row color + { + g.setColor( alternateRowColor ); + + float arc = UIScale.scale( selectionArc / 2f ); + FlatUIUtils.paintSelection( (Graphics2D) g, rowBounds.x, rowBounds.y, rowBounds.width, rowBounds.height, + UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 ); + } + // get renderer component @SuppressWarnings( "unchecked" ) Component rendererComponent = cellRenderer.getListCellRendererComponent( list, dataModel.getElementAt( row ), row, isSelected, FlatUIUtils.isPermanentFocusOwner( list ) && (row == leadIndex) ); - // + // use smaller cell width if list is used in JFileChooser boolean isFileList = Boolean.TRUE.equals( list.getClientProperty( "List.isFileList" ) ); int cx, cw; if( isFileList ) { @@ -323,18 +334,6 @@ protected void paintCell( Graphics g, int row, Rectangle rowBounds, ListCellRend cw = rowBounds.width; } - // combobox does not support alternate row color - if( !"ComboBox.list".equals( list.getName() ) ) { - if( alternateRowColor != null && row % 2 != 0 ) { - g.setColor( alternateRowColor ); - - // paint respecting selection arc - float arc = UIScale.scale( selectionArc / 2f ); - FlatUIUtils.paintSelection( (Graphics2D) g, 0, rowBounds.y, list.getWidth(), rowBounds.height, - UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 ); - } - } - // rounded selection or selection insets if( isSelected && !isFileList && // rounded selection is not supported for file list 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 e936658b1..25aeb28b8 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 @@ -22,6 +22,7 @@ import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import javax.swing.*; +import javax.swing.UIDefaults.ActiveValue; import javax.swing.table.*; import javax.swing.tree.*; import com.formdev.flatlaf.FlatClientProperties; @@ -65,6 +66,45 @@ class DataComponentsPanel tree3.putClientProperty( FlatClientProperties.STYLE, "selectionInsets: 0,1,0,1; selectionArc: 6" ); } + private void listAlternatingRowsChanged() { + ActiveValue alternateRowColor = null; + if( listAlternatingRowsCheckBox.isSelected() ) { + alternateRowColor = table -> { + Color background = list1.getBackground(); + return FlatLaf.isLafDark() + ? ColorFunctions.lighten( background, 0.05f ) + : ColorFunctions.darken( background, 0.05f ); + }; + } + UIManager.put( "List.alternateRowColor", alternateRowColor ); + list1.updateUI(); + list2.updateUI(); + list3.updateUI(); + } + + private void treeWideSelectionChanged() { + boolean wideSelection = treeWideSelectionCheckBox.isSelected(); + tree1.putClientProperty( FlatClientProperties.TREE_WIDE_SELECTION, wideSelection ); + tree2.putClientProperty( FlatClientProperties.TREE_WIDE_SELECTION, wideSelection ); + tree3.putClientProperty( FlatClientProperties.TREE_WIDE_SELECTION, wideSelection ); + } + + private void treeAlternatingRowsChanged() { + ActiveValue alternateRowColor = null; + if( treeAlternatingRowsCheckBox.isSelected() ) { + alternateRowColor = table -> { + Color background = tree1.getBackground(); + return FlatLaf.isLafDark() + ? ColorFunctions.lighten( background, 0.05f ) + : ColorFunctions.darken( background, 0.05f ); + }; + } + UIManager.put( "Tree.alternateRowColor", alternateRowColor ); + tree1.updateUI(); + tree2.updateUI(); + tree3.updateUI(); + } + private void dndChanged() { boolean dnd = dndCheckBox.isSelected(); DropMode dropMode = dnd ? DropMode.ON_OR_INSERT : DropMode.USE_SELECTION; @@ -142,18 +182,20 @@ private void roundedSelectionChanged() { } private void alternatingRowsChanged() { - Color alternateRowColor = null; + ActiveValue alternateRowColor = null; if( alternatingRowsCheckBox.isSelected() ) { - Color background = table1.getBackground(); - alternateRowColor = FlatLaf.isLafDark() - ? ColorFunctions.lighten( background, 0.05f ) - : ColorFunctions.darken( background, 0.05f ); + alternateRowColor = table -> { + Color background = table1.getBackground(); + return FlatLaf.isLafDark() + ? ColorFunctions.lighten( background, 0.05f ) + : ColorFunctions.darken( background, 0.05f ); + }; } UIManager.put( "Table.alternateRowColor", alternateRowColor ); table1.repaint(); } - @SuppressWarnings( { "unchecked", "rawtypes" } ) + @SuppressWarnings( { "rawtypes" } ) private void initComponents() { // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents JLabel label1 = new JLabel(); @@ -166,6 +208,8 @@ private void initComponents() { list3 = new JList<>(); JScrollPane scrollPane2 = new JScrollPane(); list2 = new JList<>(); + JPanel listOptionsPanel = new JPanel(); + listAlternatingRowsCheckBox = new JCheckBox(); JLabel treeLabel = new JLabel(); JScrollPane scrollPane3 = new JScrollPane(); tree1 = new JTree(); @@ -173,6 +217,9 @@ private void initComponents() { tree3 = new JTree(); JScrollPane scrollPane4 = new JScrollPane(); tree2 = new JTree(); + JPanel treeOptionsPanel = new JPanel(); + treeWideSelectionCheckBox = new JCheckBox(); + treeAlternatingRowsCheckBox = new JCheckBox(); JLabel tableLabel = new JLabel(); JScrollPane scrollPane5 = new JScrollPane(); table1 = new JTable(); @@ -273,6 +320,22 @@ private void initComponents() { } add(scrollPane2, "cell 3 1"); + //======== listOptionsPanel ======== + { + listOptionsPanel.setLayout(new MigLayout( + "insets 0,hidemode 3", + // columns + "[fill]", + // rows + "[]")); + + //---- listAlternatingRowsCheckBox ---- + listAlternatingRowsCheckBox.setText("alternating rows"); + listAlternatingRowsCheckBox.addActionListener(e -> listAlternatingRowsChanged()); + listOptionsPanel.add(listAlternatingRowsCheckBox, "cell 0 0"); + } + add(listOptionsPanel, "cell 4 1"); + //---- treeLabel ---- treeLabel.setText("JTree:"); add(treeLabel, "cell 0 2,aligny top,growy 0"); @@ -334,6 +397,29 @@ private void initComponents() { } add(scrollPane4, "cell 3 2"); + //======== treeOptionsPanel ======== + { + treeOptionsPanel.setLayout(new MigLayout( + "insets 0,hidemode 3", + // columns + "[fill]", + // rows + "[]0" + + "[]")); + + //---- treeWideSelectionCheckBox ---- + treeWideSelectionCheckBox.setText("wide selection"); + treeWideSelectionCheckBox.setSelected(true); + treeWideSelectionCheckBox.addActionListener(e -> treeWideSelectionChanged()); + treeOptionsPanel.add(treeWideSelectionCheckBox, "cell 0 0"); + + //---- treeAlternatingRowsCheckBox ---- + treeAlternatingRowsCheckBox.setText("alternating rows"); + treeAlternatingRowsCheckBox.addActionListener(e -> treeAlternatingRowsChanged()); + treeOptionsPanel.add(treeAlternatingRowsCheckBox, "cell 0 1"); + } + add(treeOptionsPanel, "cell 4 2"); + //---- tableLabel ---- tableLabel.setText("JTable:"); add(tableLabel, "cell 0 3,aligny top,growy 0"); @@ -379,7 +465,7 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { { TableColumnModel cm = table1.getColumnModel(); cm.getColumn(2).setCellEditor(new DefaultCellEditor( - new JComboBox(new DefaultComboBoxModel(new String[] { + new JComboBox<>(new DefaultComboBoxModel<>(new String[] { "January", "February", "March", @@ -394,7 +480,7 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { "December" })))); cm.getColumn(3).setCellEditor(new DefaultCellEditor( - new JComboBox(new DefaultComboBoxModel(new String[] { + new JComboBox<>(new DefaultComboBoxModel<>(new String[] { "January", "February", "March", @@ -513,9 +599,12 @@ public boolean isCellEditable(int rowIndex, int columnIndex) { private JList list1; private JList list3; private JList list2; + private JCheckBox listAlternatingRowsCheckBox; private JTree tree1; private JTree tree3; private JTree tree2; + private JCheckBox treeWideSelectionCheckBox; + private JCheckBox treeAlternatingRowsCheckBox; private JTable table1; private JCheckBox roundedSelectionCheckBox; private JCheckBox showHorizontalLinesCheckBox; 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 0f729bb18..c51f3cfbf 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 @@ -1,4 +1,4 @@ -JFDML JFormDesigner: "8.2.0.0.331" Java: "21" encoding: "UTF-8" +JFDML JFormDesigner: "8.3" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -92,6 +92,25 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 3 1" } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 0,hidemode 3" + "$columnConstraints": "[fill]" + "$rowConstraints": "[]" + } ) { + name: "listOptionsPanel" + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "listAlternatingRowsCheckBox" + "text": "alternating rows" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "listAlternatingRowsChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 4 1" + } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "treeLabel" "text": "JTree:" @@ -192,6 +211,36 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 3 2" } ) + add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "insets 0,hidemode 3" + "$columnConstraints": "[fill]" + "$rowConstraints": "[]0[]" + } ) { + name: "treeOptionsPanel" + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "treeWideSelectionCheckBox" + "text": "wide selection" + "selected": true + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "treeWideSelectionChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "treeAlternatingRowsCheckBox" + "text": "alternating rows" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "treeAlternatingRowsChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 4 2" + } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "tableLabel" "text": "JTable:" 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 f8fcade99..e5a2502f0 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 @@ -494,6 +494,11 @@ private void listVisibleRowCountChanged() { list.setVisibleRowCount( visibleRowCount ); } + private void listAlternatingRowsChanged() { + UIManager.put( "List.alternateRowColor", listAlternatingRowsCheckBox.isSelected() ? Color.YELLOW : null ); + FlatLaf.updateUILater(); + } + private void treeRendererChanged() { Object sel = treeRendererComboBox.getSelectedItem(); if( !(sel instanceof String) ) @@ -530,6 +535,11 @@ private void treeWideCellRendererChanged() { tree.putClientProperty( FlatClientProperties.TREE_WIDE_CELL_RENDERER, wideCellRenderer ); } + private void treeAlternatingRowsChanged() { + UIManager.put( "Tree.alternateRowColor", treeAlternatingRowsCheckBox.isSelected() ? Color.cyan : null ); + FlatLaf.updateUILater(); + } + private void treePaintSelectionChanged() { boolean paintSelection = treePaintSelectionCheckBox.isSelected(); for( JTree tree : allTrees ) @@ -670,12 +680,14 @@ private void initComponents() { listLayoutOrientationField = new JComboBox<>(); JLabel listVisibleRowCountLabel = new JLabel(); listVisibleRowCountSpinner = new JSpinner(); + listAlternatingRowsCheckBox = new JCheckBox(); treeOptionsPanel = new JPanel(); JLabel treeRendererLabel = new JLabel(); treeRendererComboBox = new JComboBox<>(); treeWideSelectionCheckBox = new JCheckBox(); treeWideCellRendererCheckBox = new JCheckBox(); treePaintSelectionCheckBox = new JCheckBox(); + treeAlternatingRowsCheckBox = new JCheckBox(); treePaintLinesCheckBox = new JCheckBox(); treeRedLinesCheckBox = new JCheckBox(); treeEditableCheckBox = new JCheckBox(); @@ -737,7 +749,7 @@ private void initComponents() { // rows "[]" + "[grow]" + - "[]" + + "[]0" + "[]")); //---- listLabel ---- @@ -780,7 +792,7 @@ private void initComponents() { // rows "[]" + "[grow]" + - "[]" + + "[]0" + "[]")); //---- tableLabel ---- @@ -817,7 +829,7 @@ private void initComponents() { // rows "[]" + "[grow]" + - "[]" + + "[]0" + "[]")); //---- treeLabel ---- @@ -1006,6 +1018,7 @@ public void mouseClicked(MouseEvent e) { // rows "[]" + "[]" + + "[]" + "[]")); //---- listRendererLabel ---- @@ -1043,6 +1056,11 @@ public void mouseClicked(MouseEvent e) { listVisibleRowCountSpinner.setModel(new SpinnerNumberModel(8, 0, null, 1)); listVisibleRowCountSpinner.addChangeListener(e -> listVisibleRowCountChanged()); listOptionsPanel.add(listVisibleRowCountSpinner, "cell 1 2"); + + //---- listAlternatingRowsCheckBox ---- + listAlternatingRowsCheckBox.setText("alternating rows"); + listAlternatingRowsCheckBox.addActionListener(e -> listAlternatingRowsChanged()); + listOptionsPanel.add(listAlternatingRowsCheckBox, "cell 0 3 2 1,alignx left,growx 0"); } add(listOptionsPanel, "cell 0 4 4 1"); @@ -1096,6 +1114,11 @@ public void mouseClicked(MouseEvent e) { treePaintSelectionCheckBox.addActionListener(e -> treePaintSelectionChanged()); treeOptionsPanel.add(treePaintSelectionCheckBox, "cell 0 2"); + //---- treeAlternatingRowsCheckBox ---- + treeAlternatingRowsCheckBox.setText("alternating rows"); + treeAlternatingRowsCheckBox.addActionListener(e -> treeAlternatingRowsChanged()); + treeOptionsPanel.add(treeAlternatingRowsCheckBox, "cell 0 2"); + //---- treePaintLinesCheckBox ---- treePaintLinesCheckBox.setText("paint lines"); treePaintLinesCheckBox.addActionListener(e -> treePaintLinesChanged()); @@ -1253,11 +1276,13 @@ public void mouseClicked(MouseEvent e) { private JComboBox listRendererComboBox; private JComboBox listLayoutOrientationField; private JSpinner listVisibleRowCountSpinner; + private JCheckBox listAlternatingRowsCheckBox; private JPanel treeOptionsPanel; private JComboBox treeRendererComboBox; private JCheckBox treeWideSelectionCheckBox; private JCheckBox treeWideCellRendererCheckBox; private JCheckBox treePaintSelectionCheckBox; + private JCheckBox treeAlternatingRowsCheckBox; private JCheckBox treePaintLinesCheckBox; private JCheckBox treeRedLinesCheckBox; private JCheckBox treeEditableCheckBox; @@ -1363,7 +1388,7 @@ public int getSize() { @Override public String getElementAt( int index ) { return (index < 20) - ? "item " + (index + 1) + ? "item " + (index + 1) + ((index + 1) % 5 == 0 ? " ####" : "") : "item " + (index + 1) + " " + randomRowString( index ); } } 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 e1372a999..d8fd15a5f 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 @@ -33,7 +33,7 @@ new FormModel { } ) add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$columnConstraints": "[fill]" - "$rowConstraints": "[][grow][][]" + "$rowConstraints": "[][grow][]0[]" "$layoutConstraints": "ltr,insets 0,hidemode 3" } ) { name: "panel1" @@ -95,7 +95,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets 0,hidemode 3" "$columnConstraints": "[fill]" - "$rowConstraints": "[][grow][][]" + "$rowConstraints": "[][grow][]0[]" } ) { name: "panel3" "$client.FlatLaf.internal.testing.ignore": true @@ -146,7 +146,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets 0,hidemode 3" "$columnConstraints": "[fill]" - "$rowConstraints": "[][grow][][]" + "$rowConstraints": "[][grow][]0[]" } ) { name: "panel2" "$client.FlatLaf.internal.testing.ignore": true @@ -385,7 +385,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "insets 8,hidemode 3" "$columnConstraints": "[fill][fill]" - "$rowConstraints": "[][][]" + "$rowConstraints": "[][][][]" } ) { name: "listOptionsPanel" "border": new javax.swing.border.TitledBorder( "JList Control" ) @@ -455,6 +455,16 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 2" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "listAlternatingRowsCheckBox" + "text": "alternating rows" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "listAlternatingRowsChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 3 2 1,alignx left,growx 0" + } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 4 4 1" } ) @@ -527,6 +537,16 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 2" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "treeAlternatingRowsCheckBox" + "text": "alternating rows" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "treeAlternatingRowsChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 2" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "treePaintLinesCheckBox" "text": "paint lines"