From e14fe96ac626a9ccfd8e763d4f6c68458ccc10d4 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 9 Dec 2024 19:40:07 +0100 Subject: [PATCH] Tree: support wide cell renderer (issue #922) --- CHANGELOG.md | 1 + .../formdev/flatlaf/FlatClientProperties.java | 12 ++- .../com/formdev/flatlaf/ui/FlatTreeUI.java | 21 ++++ .../com/formdev/flatlaf/FlatLaf.properties | 1 + .../flatlaf/ui/TestFlatStyleableInfo.java | 1 + .../flatlaf/ui/TestFlatStyleableValue.java | 1 + .../formdev/flatlaf/ui/TestFlatStyling.java | 1 + .../flatlaf/extras/components/FlatTree.java | 22 ++++- .../flatlaf/testing/FlatComponents2Test.java | 97 +++++++++++-------- .../flatlaf/testing/FlatComponents2Test.jfd | 13 ++- 10 files changed, 126 insertions(+), 44 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3fa4c634..2f724e854 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ FlatLaf Change Log #### New features and improvements - 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 implement component state based color mappings. (issue #906) diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java index 75ffcd2dd..a1953cce1 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -1410,13 +1410,23 @@ public interface FlatClientProperties //---- JTree -------------------------------------------------------------- /** - * Override if a tree shows a wide selection. Default is {@code true}. + * Specifies whether tree shows a wide selection. Default is {@code true}. *

* Component {@link javax.swing.JTree}
* Value type {@link java.lang.Boolean} */ String TREE_WIDE_SELECTION = "JTree.wideSelection"; + /** + * Specifies whether tree uses a wide cell renderer. Default is {@code false}. + *

+ * Component {@link javax.swing.JTree}
+ * Value type {@link java.lang.Boolean} + * + * @since 3.6 + */ + String TREE_WIDE_CELL_RENDERER = "JTree.wideCellRenderer"; + /** * Specifies whether tree item selection is painted. Default is {@code true}. * If set to {@code false}, then the tree cell renderer is responsible for painting selection. diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java index ed3ecc905..2007bc22d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTreeUI.java @@ -106,6 +106,7 @@ * @uiDefault Tree.selectionInsets Insets * @uiDefault Tree.selectionArc int * @uiDefault Tree.wideSelection boolean + * @uiDefault Tree.wideCellRenderer boolean * @uiDefault Tree.showCellFocusIndicator boolean * @uiDefault Tree.showDefaultIcons boolean * @@ -146,6 +147,7 @@ public class FlatTreeUI /** @since 3 */ @Styleable protected Insets selectionInsets; /** @since 3 */ @Styleable protected int selectionArc; @Styleable protected boolean wideSelection; + /** @since 3.6 */ @Styleable protected boolean wideCellRenderer; @Styleable protected boolean showCellFocusIndicator; /** @since 3 */ protected boolean showDefaultIcons; @@ -198,6 +200,7 @@ protected void installDefaults() { selectionInsets = UIManager.getInsets( "Tree.selectionInsets" ); selectionArc = UIManager.getInt( "Tree.selectionArc" ); wideSelection = UIManager.getBoolean( "Tree.wideSelection" ); + wideCellRenderer = UIManager.getBoolean( "Tree.wideCellRenderer" ); showCellFocusIndicator = UIManager.getBoolean( "Tree.showCellFocusIndicator" ); showDefaultIcons = UIManager.getBoolean( "Tree.showDefaultIcons" ); @@ -314,6 +317,7 @@ protected PropertyChangeListener createPropertyChangeListener() { if( e.getSource() == tree ) { switch( e.getPropertyName() ) { case TREE_WIDE_SELECTION: + case TREE_WIDE_CELL_RENDERER: case TREE_PAINT_SELECTION: HiDPIUtils.repaint( tree ); break; @@ -584,6 +588,18 @@ protected void paintRow( Graphics g, Rectangle clipBounds, Insets insets, Rectan UIScale.scale( selectionInsets ), arc, arc, arc, arc, 0 ); } + // update bounds for wide cell renderer + if( isWideSelection() && isWideCellRenderer() ) { + Rectangle wideBounds = new Rectangle( bounds ); + if( tree.getComponentOrientation().isLeftToRight() ) + wideBounds.width = tree.getWidth() - bounds.x - insets.right; + else { + wideBounds.x = insets.left; + wideBounds.width = bounds.x + bounds.width - insets.left; + } + bounds = wideBounds; + } + // do not paint row if editing if( isEditing ) { // paint wide selection @@ -808,6 +824,11 @@ protected boolean isWideSelection() { return clientPropertyBoolean( tree, TREE_WIDE_SELECTION, wideSelection ); } + /** @since 3.6 */ + protected boolean isWideCellRenderer() { + return clientPropertyBoolean( tree, TREE_WIDE_CELL_RENDERER, wideCellRenderer ); + } + protected boolean isPaintSelection() { return clientPropertyBoolean( tree, TREE_PAINT_SELECTION, paintSelection ); } diff --git a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties index 1f23a13ce..c39995ffd 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -934,6 +934,7 @@ Tree.rendererMargins = 1,2,1,2 Tree.selectionInsets = 0,0,0,0 Tree.selectionArc = 0 Tree.wideSelection = true +Tree.wideCellRenderer = false Tree.repaintWholeRow = true Tree.paintLines = false Tree.showCellFocusIndicator = false 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 8058bed2a..596ac6c8e 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 @@ -969,6 +969,7 @@ void tree() { "selectionInsets", Insets.class, "selectionArc", int.class, "wideSelection", boolean.class, + "wideCellRenderer", boolean.class, "showCellFocusIndicator", boolean.class, "paintSelection", boolean.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 b5fab0a98..f0ae44841 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 @@ -942,6 +942,7 @@ void tree() { testInsets( c, ui, "selectionInsets", 1,2,3,4 ); testInteger( c, ui, "selectionArc", 123 ); testBoolean( c, ui, "wideSelection", true ); + testBoolean( c, ui, "wideCellRenderer", true ); testBoolean( c, ui, "showCellFocusIndicator", true ); testBoolean( c, ui, "paintSelection", false ); 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 621daa012..d20e49fb0 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 @@ -1191,6 +1191,7 @@ void tree() { ui.applyStyle( "selectionInsets: 1,2,3,4" ); ui.applyStyle( "selectionArc: 8" ); ui.applyStyle( "wideSelection: true" ); + ui.applyStyle( "wideCellRenderer: true" ); ui.applyStyle( "showCellFocusIndicator: true" ); ui.applyStyle( "paintSelection: false" ); diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTree.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTree.java index 83c0075c6..4c32d50a4 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTree.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTree.java @@ -29,19 +29,37 @@ public class FlatTree implements FlatComponentExtension, FlatStyleableComponent { /** - * Returns if the tree shows a wide selection + * Returns whether tree shows a wide selection */ public boolean isWideSelection() { return getClientPropertyBoolean( TREE_WIDE_SELECTION, "Tree.wideSelection" ); } /** - * Sets if the tree shows a wide selection + * Specifies whether tree shows a wide selection */ public void setWideSelection( boolean wideSelection ) { putClientProperty( TREE_WIDE_SELECTION, wideSelection ); } + /** + * Returns whether tree uses a wide cell renderer. + * + * @since 3.6 + */ + public boolean isWideCellRenderer() { + return getClientPropertyBoolean( TREE_WIDE_CELL_RENDERER, "Tree.wideCellRenderer" ); + } + + /** + * Specifies whether tree uses a wide cell renderer. + * + * @since 3.6 + */ + public void setWideCellRenderer( boolean wideCellRenderer ) { + putClientProperty( TREE_WIDE_CELL_RENDERER, wideCellRenderer ); + } + /** * Returns whether tree item selection is painted. Default is {@code true}. * If set to {@code false}, then the tree cell renderer is responsible for painting selection. 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 57cc183b3..f8fcade99 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 @@ -16,6 +16,7 @@ package com.formdev.flatlaf.testing; +import java.awt.BorderLayout; import java.awt.Color; import java.awt.Component; import java.awt.ComponentOrientation; @@ -32,6 +33,7 @@ import java.util.List; import java.util.Map; import java.util.Random; +import java.util.function.Supplier; import javax.swing.*; import javax.swing.border.*; import javax.swing.event.TableModelEvent; @@ -497,48 +499,23 @@ private void treeRendererChanged() { if( !(sel instanceof String) ) return; - JTree[] trees = { tree1, tree2, xTree1 }; + Supplier creator; switch( (String) sel ) { - case "default": - for( JTree tree : trees ) - tree.setCellRenderer( new DefaultTreeCellRenderer() ); - break; - - case "defaultSubclass": - for( JTree tree : trees ) - tree.setCellRenderer( new TestDefaultTreeCellRenderer() ); - break; - - case "defaultWithIcons": - for( JTree tree : trees ) - tree.setCellRenderer( new TestDefaultWithIconsTreeCellRenderer() ); - break; - - case "defaultWithIcon": - for( JTree tree : trees ) - tree.setCellRenderer( new TestDefaultWithIconTreeCellRenderer() ); - break; - - case "label": - for( JTree tree : trees ) - tree.setCellRenderer( new TestLabelTreeCellRenderer() ); - break; - - case "swingxDefault": - for( JTree tree : trees ) - tree.setCellRenderer( new DefaultTreeRenderer() ); - break; - - case "jideCheckBox": - for( JTree tree : trees ) - tree.setCellRenderer( new CheckBoxTreeCellRenderer( new DefaultTreeCellRenderer() ) ); - break; - - case "jideStyled": - for( JTree tree : trees ) - tree.setCellRenderer( new StyledTreeCellRenderer() ); - break; + default: + case "default": creator = DefaultTreeCellRenderer::new; break; + case "defaultSubclass": creator = TestDefaultTreeCellRenderer::new; break; + case "defaultWithIcons": creator = TestDefaultWithIconsTreeCellRenderer::new; break; + case "defaultWithIcon": creator = TestDefaultWithIconTreeCellRenderer::new; break; + case "label": creator = TestLabelTreeCellRenderer::new; break; + case "wide": creator = TestWideTreeCellRenderer::new; break; + case "swingxDefault": creator = DefaultTreeRenderer::new; break; + case "jideCheckBox": creator = () -> new CheckBoxTreeCellRenderer( new DefaultTreeCellRenderer() ); break; + case "jideStyled": creator = StyledTreeCellRenderer::new; break; } + + JTree[] trees = { tree1, tree2, xTree1 }; + for( JTree tree : trees ) + tree.setCellRenderer( creator.get() ); } private void treeWideSelectionChanged() { @@ -547,6 +524,12 @@ private void treeWideSelectionChanged() { tree.putClientProperty( FlatClientProperties.TREE_WIDE_SELECTION, wideSelection ); } + private void treeWideCellRendererChanged() { + boolean wideCellRenderer = treeWideCellRendererCheckBox.isSelected(); + for( JTree tree : allTrees ) + tree.putClientProperty( FlatClientProperties.TREE_WIDE_CELL_RENDERER, wideCellRenderer ); + } + private void treePaintSelectionChanged() { boolean paintSelection = treePaintSelectionCheckBox.isSelected(); for( JTree tree : allTrees ) @@ -691,6 +674,7 @@ private void initComponents() { JLabel treeRendererLabel = new JLabel(); treeRendererComboBox = new JComboBox<>(); treeWideSelectionCheckBox = new JCheckBox(); + treeWideCellRendererCheckBox = new JCheckBox(); treePaintSelectionCheckBox = new JCheckBox(); treePaintLinesCheckBox = new JCheckBox(); treeRedLinesCheckBox = new JCheckBox(); @@ -1088,6 +1072,7 @@ public void mouseClicked(MouseEvent e) { "defaultWithIcons", "defaultWithIcon", "label", + "wide", "swingxDefault", "jideCheckBox", "jideStyled" @@ -1100,6 +1085,11 @@ public void mouseClicked(MouseEvent e) { treeWideSelectionCheckBox.addActionListener(e -> treeWideSelectionChanged()); treeOptionsPanel.add(treeWideSelectionCheckBox, "cell 0 1"); + //---- treeWideCellRendererCheckBox ---- + treeWideCellRendererCheckBox.setText("wide cell renderer"); + treeWideCellRendererCheckBox.addActionListener(e -> treeWideCellRendererChanged()); + treeOptionsPanel.add(treeWideCellRendererCheckBox, "cell 0 1"); + //---- treePaintSelectionCheckBox ---- treePaintSelectionCheckBox.setText("paint selection"); treePaintSelectionCheckBox.setSelected(true); @@ -1266,6 +1256,7 @@ public void mouseClicked(MouseEvent e) { private JPanel treeOptionsPanel; private JComboBox treeRendererComboBox; private JCheckBox treeWideSelectionCheckBox; + private JCheckBox treeWideCellRendererCheckBox; private JCheckBox treePaintSelectionCheckBox; private JCheckBox treePaintLinesCheckBox; private JCheckBox treeRedLinesCheckBox; @@ -1794,6 +1785,32 @@ public Component getTreeCellRendererComponent( JTree tree, Object value, boolean } } + //---- class TestLabelTreeCellRenderer ------------------------------------ + + private static class TestWideTreeCellRenderer + extends JPanel + implements TreeCellRenderer + { + private final JLabel label = new JLabel(); + private final JLabel icon = new JLabel( UIManager.getIcon( "FileView.floppyDriveIcon" ) ); + + TestWideTreeCellRenderer() { + super( new BorderLayout() ); + setOpaque( false ); + add( label, BorderLayout.CENTER ); + add( icon, BorderLayout.LINE_END ); + setBorder( new LineBorder( Color.red ) ); + } + + @Override + public Component getTreeCellRendererComponent( JTree tree, Object value, boolean selected, boolean expanded, + boolean leaf, int row, boolean hasFocus ) + { + label.setText( String.valueOf( value ) ); + return this; + } + } + //---- class TestComboBoxTableCellRenderer -------------------------------- private static class TestComboBoxTableCellRenderer 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 d792fee6c..e1372a999 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.2.2.0.9999" Java: "21.0.1" encoding: "UTF-8" +JFDML JFormDesigner: "8.3" encoding: "UTF-8" new FormModel { contentType: "form/swing" @@ -484,6 +484,7 @@ new FormModel { addElement( "defaultWithIcons" ) addElement( "defaultWithIcon" ) addElement( "label" ) + addElement( "wide" ) addElement( "swingxDefault" ) addElement( "jideCheckBox" ) addElement( "jideStyled" ) @@ -505,6 +506,16 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 1" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "treeWideCellRendererCheckBox" + "text": "wide cell renderer" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "treeWideCellRendererChanged", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 1" + } ) add( new FormComponent( "javax.swing.JCheckBox" ) { name: "treePaintSelectionCheckBox" "text": "paint selection"