diff --git a/CHANGELOG.md b/CHANGELOG.md index b98d7a13..37331464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ FlatLaf Change Log ================== +## Unreleased + +- CheckBox: Support painting a third state (set client property + "JButton.selectedState" to "indeterminate"). +- `TriStateCheckBox` component added (see [FlatLaf Extras](flatlaf-extras)). + + ## 0.16 - Made some fixes for right-to-left support in ComboBox, Slider and ToolTip. 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 c59e5bd1..6f9f63b2 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -27,6 +27,9 @@ public interface FlatClientProperties String BUTTON_TYPE = "JButton.buttonType"; String BUTTON_TYPE_HELP = "help"; + String SELECTED_STATE = "JButton.selectedState"; + String SELECTED_STATE_INDETERMINATE = "indeterminate"; + /** * Checks whether a client property of a component has the given value. */ diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxIcon.java index 2b169e21..97509346 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxIcon.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatCheckBoxIcon.java @@ -16,12 +16,15 @@ package com.formdev.flatlaf.icons; +import static com.formdev.flatlaf.FlatClientProperties.*; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Component; import java.awt.Graphics2D; import java.awt.geom.Path2D; +import java.awt.geom.RoundRectangle2D; import javax.swing.AbstractButton; +import javax.swing.JComponent; import javax.swing.UIManager; import com.formdev.flatlaf.ui.FlatButtonUI; @@ -84,7 +87,9 @@ public class FlatCheckBoxIcon @Override protected void paintIcon( Component c, Graphics2D g2 ) { - boolean selected = (c instanceof AbstractButton) ? ((AbstractButton)c).isSelected() : false; + boolean indeterminate = c instanceof JComponent && clientPropertyEquals( (JComponent) c, SELECTED_STATE, SELECTED_STATE_INDETERMINATE ); + boolean selected = indeterminate || (c instanceof AbstractButton && ((AbstractButton)c).isSelected()); + System.out.println( "icon "+indeterminate+" "+selected ); // paint focused border if( c.hasFocus() && focusWidth > 0 ) { @@ -111,9 +116,12 @@ public class FlatCheckBoxIcon paintBackground( g2 ); // paint checkmark - if( selected ) { + if( selected || indeterminate ) { g2.setColor( c.isEnabled() ? checkmarkColor : disabledCheckmarkColor ); - paintCheckmark( g2 ); + if( indeterminate ) + paintIndeterminate( g2 ); + else + paintCheckmark( g2 ); } } @@ -140,4 +148,8 @@ public class FlatCheckBoxIcon g2.setStroke( new BasicStroke( 1.9f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND ) ); g2.draw( path ); } + + protected void paintIndeterminate( Graphics2D g2 ) { + g2.fill( new RoundRectangle2D.Float( 3.75f, 5.75f, 8.5f, 2.5f, 2f, 2f ) ); + } } diff --git a/flatlaf-extras/README.md b/flatlaf-extras/README.md new file mode 100644 index 00000000..230e50ad --- /dev/null +++ b/flatlaf-extras/README.md @@ -0,0 +1,13 @@ +FlatLaf Extras +============== + +This sub-project provides some additional components and classes: + +- [TriStateCheckBox](flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/TriStateCheckBox.java): + A tri-state check box. + + +Download +-------- + +Not yet available. diff --git a/flatlaf-extras/build.gradle.kts b/flatlaf-extras/build.gradle.kts new file mode 100644 index 00000000..00346352 --- /dev/null +++ b/flatlaf-extras/build.gradle.kts @@ -0,0 +1,30 @@ +/* + * Copyright 2019 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +version = rootProject.version + +plugins { + `java-library` +} + +dependencies { + implementation( project( ":flatlaf-core" ) ) +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/TriStateCheckBox.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/TriStateCheckBox.java new file mode 100644 index 00000000..4c13396b --- /dev/null +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/TriStateCheckBox.java @@ -0,0 +1,107 @@ +/* + * Copyright 2019 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.extras; + +import java.awt.event.ItemEvent; +import javax.swing.JCheckBox; + +/** + * A tri-state check box. + * + * To display the third state, this component requires an LaF that supports painting + * the indeterminate state if client property {@code "JButton.selectedState"} has the + * value {@code "indeterminate"}. + * + * FlatLaf and Mac Aqua LaF support the third state. + * + * @author Karl Tauber + */ +public class TriStateCheckBox + extends JCheckBox +{ + public enum State { INDETERMINATE, SELECTED, UNSELECTED } + + private State state; + private boolean thirdStateEnabled = true; + + public TriStateCheckBox() { + this( null ); + } + + public TriStateCheckBox( String text ) { + this( text, State.INDETERMINATE ); + } + + public TriStateCheckBox( String text, State initialState ) { + super( text ); + + setModel( new ToggleButtonModel() { + @Override + public boolean isSelected() { + return state != State.UNSELECTED; + } + + @Override + public void setSelected( boolean b ) { + switch( state ) { + case INDETERMINATE: setState( State.SELECTED ); break; + case SELECTED: setState( State.UNSELECTED ); break; + case UNSELECTED: setState( thirdStateEnabled ? State.INDETERMINATE : State.SELECTED ); break; + } + + fireStateChanged(); + fireItemStateChanged( new ItemEvent( this, ItemEvent.ITEM_STATE_CHANGED, this, + isSelected() ? ItemEvent.SELECTED : ItemEvent.DESELECTED ) ); + } + } ); + + setState( initialState ); + } + + public State getState() { + return state; + } + + public void setState( State state ) { + if( this.state == state ) + return; + + State oldState = this.state; + this.state = state; + + putClientProperty( "JButton.selectedState", state == State.INDETERMINATE ? "indeterminate" : null ); + + firePropertyChange( "state", oldState, state ); + repaint(); + } + + public boolean isThirdStateEnabled() { + return thirdStateEnabled; + } + + public void setThirdStateEnabled( boolean thirdStateEnabled ) { + this.thirdStateEnabled = thirdStateEnabled; + + if( state == State.INDETERMINATE ) + setState( State.UNSELECTED ); + } + + @Override + public void setSelected( boolean b ) { + setState( b ? State.SELECTED : State.UNSELECTED ); + } +} diff --git a/flatlaf-testing/build.gradle.kts b/flatlaf-testing/build.gradle.kts index 7fb22032..0ad46588 100644 --- a/flatlaf-testing/build.gradle.kts +++ b/flatlaf-testing/build.gradle.kts @@ -22,6 +22,7 @@ plugins { dependencies { implementation( project( ":flatlaf-core" ) ) + implementation( project( ":flatlaf-extras" ) ) implementation( project( ":flatlaf-swingx" ) ) implementation( "com.miglayout:miglayout-swing:5.2" ) diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/extras/FlatExtrasTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/extras/FlatExtrasTest.java new file mode 100644 index 00000000..3361cdbc --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/extras/FlatExtrasTest.java @@ -0,0 +1,103 @@ +/* + * Copyright 2019 FormDev Software GmbH + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.formdev.flatlaf.testing.extras; + +import javax.swing.*; +import com.formdev.flatlaf.extras.*; +import com.formdev.flatlaf.testing.*; +import net.miginfocom.swing.*; + +/** + * @author Karl Tauber + */ +public class FlatExtrasTest + extends FlatTestPanel +{ + public static void main( String[] args ) { + SwingUtilities.invokeLater( () -> { + FlatTestFrame frame = FlatTestFrame.create( args, "FlatExtrasTest" ); + frame.showFrame( new FlatExtrasTest() ); + } ); + } + + public FlatExtrasTest() { + initComponents(); + + triStateLabel1.setText( triStateCheckBox1.getState().toString() ); + triStateLabel2.setText( triStateCheckBox2.getState().toString() ); + } + + private void triStateCheckBox1Changed() { + triStateLabel1.setText( triStateCheckBox1.getState().toString() ); + } + + private void triStateCheckBox2Changed() { + triStateLabel2.setText( triStateCheckBox2.getState().toString() ); + } + + private void initComponents() { + // JFormDesigner - Component initialization - DO NOT MODIFY //GEN-BEGIN:initComponents + label1 = new JLabel(); + triStateCheckBox1 = new TriStateCheckBox(); + triStateLabel1 = new JLabel(); + triStateCheckBox2 = new TriStateCheckBox(); + triStateLabel2 = new JLabel(); + + //======== this ======== + setLayout(new MigLayout( + "hidemode 3,ltr", + // columns + "[]" + + "[]" + + "[left]", + // rows + "[]" + + "[]")); + + //---- label1 ---- + label1.setText("TriStateCheckBox:"); + add(label1, "cell 0 0"); + + //---- triStateCheckBox1 ---- + triStateCheckBox1.setText("three states"); + triStateCheckBox1.addActionListener(e -> triStateCheckBox1Changed()); + add(triStateCheckBox1, "cell 1 0"); + + //---- triStateLabel1 ---- + triStateLabel1.setText("text"); + add(triStateLabel1, "cell 2 0"); + + //---- triStateCheckBox2 ---- + triStateCheckBox2.setText("third state disabled"); + triStateCheckBox2.setThirdStateEnabled(false); + triStateCheckBox2.addActionListener(e -> triStateCheckBox2Changed()); + add(triStateCheckBox2, "cell 1 1"); + + //---- triStateLabel2 ---- + triStateLabel2.setText("text"); + add(triStateLabel2, "cell 2 1"); + // JFormDesigner - End of component initialization //GEN-END:initComponents + } + + // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JLabel label1; + private TriStateCheckBox triStateCheckBox1; + private JLabel triStateLabel1; + private TriStateCheckBox triStateCheckBox2; + private JLabel triStateLabel2; + // JFormDesigner - End of variables declaration //GEN-END:variables +} diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/extras/FlatExtrasTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/extras/FlatExtrasTest.jfd new file mode 100644 index 00000000..8e3591a2 --- /dev/null +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/extras/FlatExtrasTest.jfd @@ -0,0 +1,50 @@ +JFDML JFormDesigner: "7.0.0.0.194" Java: "11.0.2" encoding: "UTF-8" + +new FormModel { + contentType: "form/swing" + root: new FormRoot { + add( new FormContainer( "com.formdev.flatlaf.testing.FlatTestPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { + "$layoutConstraints": "hidemode 3,ltr" + "$columnConstraints": "[][][left]" + "$rowConstraints": "[][]" + } ) { + name: "this" + add( new FormComponent( "javax.swing.JLabel" ) { + name: "label1" + "text": "TriStateCheckBox:" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 0" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.TriStateCheckBox" ) { + name: "triStateCheckBox1" + "text": "three states" + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "triStateCheckBox1Changed", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 0" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "triStateLabel1" + "text": "text" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 0" + } ) + add( new FormComponent( "com.formdev.flatlaf.extras.TriStateCheckBox" ) { + name: "triStateCheckBox2" + "text": "third state disabled" + "thirdStateEnabled": false + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "triStateCheckBox2Changed", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 1 1" + } ) + add( new FormComponent( "javax.swing.JLabel" ) { + name: "triStateLabel2" + "text": "text" + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 2 1" + } ) + }, new FormLayoutConstraints( null ) { + "location": new java.awt.Point( 0, 0 ) + "size": new java.awt.Dimension( 400, 300 ) + } ) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index b5e616c4..26cdae3e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,6 +17,7 @@ rootProject.name = "FlatLaf" include( "flatlaf-core" ) +include( "flatlaf-extras" ) include( "flatlaf-swingx" ) include( "flatlaf-demo" ) include( "flatlaf-testing" )