From a4377e81cb4b559127e55fbcd05ecdb4a8c27e35 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Mon, 13 Dec 2021 19:16:23 +0100 Subject: [PATCH 1/4] TextField: support "clear" (or "cancel") button to empty text field --- CHANGELOG.md | 16 +- .../formdev/flatlaf/FlatClientProperties.java | 11 + .../formdev/flatlaf/ui/FlatTextFieldUI.java | 221 +++++++++++++++--- .../com/formdev/flatlaf/FlatLaf.properties | 13 ++ .../flatlaf/ui/TestFlatStyleableInfo.java | 3 +- .../formdev/flatlaf/ui/TestFlatStyling.java | 2 + .../flatlaf/demo/BasicComponentsPanel.java | 10 + .../flatlaf/demo/BasicComponentsPanel.jfd | 9 + .../components/FlatFormattedTextField.java | 20 ++ .../extras/components/FlatPasswordField.java | 20 ++ .../extras/components/FlatTextField.java | 20 ++ .../dumps/uidefaults/FlatDarkLaf_1.8.0.txt | 1 + .../dumps/uidefaults/FlatLightLaf_1.8.0.txt | 1 + .../dumps/uidefaults/FlatTestLaf_1.8.0.txt | 1 + .../testing/FlatTextComponentsTest.java | 14 ++ .../testing/FlatTextComponentsTest.jfd | 12 +- .../flatlaf/themeeditor/FlatLafUIKeys.txt | 1 + 17 files changed, 339 insertions(+), 36 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 435808d2..b7545f86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,12 +28,16 @@ FlatLaf Change Log setting UI default `OptionPane.showIcon` to `true`. (issue #416) - No longer show the Java "duke/cup" icon if no window icon image is set. (issue #416) -- TextField, FormattedTextField and PasswordField: Support leading and trailing - icons (set client property `JTextField.leadingIcon` or - `JTextField.trailingIcon` to a `javax.swing.Icon`). (PR #378; issue #368) -- TextField, FormattedTextField and PasswordField: Support leading and trailing - components (set client property `JTextField.leadingComponent` or - `JTextField.trailingComponent` to a `java.awt.Component`). (PR #386) +- TextField, FormattedTextField and PasswordField: + - Support leading and trailing icons (set client property + `JTextField.leadingIcon` or `JTextField.trailingIcon` to a + `javax.swing.Icon`). (PR #378; issue #368) + - Support leading and trailing components (set client property + `JTextField.leadingComponent` or `JTextField.trailingComponent` to a + `java.awt.Component`). (PR #386) + - Support "clear" (or "cancel") button to empty text field. Only shown if text + field is not empty, editable and enabled. (set client property + `JTextField.showClearButton` to `true`). (PR #TODO) - TextComponents: Double/triple-click-and-drag now extends selection by whole words/lines. - Theming improvements: Reworks core themes to make it easier to create new 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 dd5e1242..ce55df19 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -903,6 +903,17 @@ public interface FlatClientProperties */ String TEXT_FIELD_TRAILING_COMPONENT = "JTextField.trailingComponent"; + /** + * Specifies whether a "clear" (or "cancel") button is shown on the trailing side + * if the text field is not empty, editable and enabled. Default is {@code false}. + *

+ * Component {@link javax.swing.JTextField} (and subclasses)
+ * Value type {@link java.lang.Boolean} + * + * @since 2 + */ + String TEXT_FIELD_SHOW_CLEAR_BUTTON = "JTextField.showClearButton"; + //---- JToggleButton ------------------------------------------------------ /** diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java index b6946080..62f9496e 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java @@ -45,10 +45,13 @@ import javax.swing.JToggleButton; import javax.swing.JToolBar; import javax.swing.LookAndFeel; import javax.swing.UIManager; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; import javax.swing.plaf.ComponentUI; import javax.swing.plaf.UIResource; import javax.swing.plaf.basic.BasicTextFieldUI; import javax.swing.text.Caret; +import javax.swing.text.Document; import javax.swing.text.JTextComponent; import com.formdev.flatlaf.ui.FlatStylingSupport.Styleable; import com.formdev.flatlaf.ui.FlatStylingSupport.StyleableUI; @@ -103,6 +106,10 @@ public class FlatTextFieldUI /** @since 2 */ @Styleable protected Icon trailingIcon; /** @since 2 */ protected JComponent leadingComponent; /** @since 2 */ protected JComponent trailingComponent; + /** @since 2 */ protected JComponent clearButton; + + // only used via styling (not in UI defaults, but has likewise client properties) + /** @since 2 */ @Styleable protected boolean showClearButton; private Color oldDisabledBackground; private Color oldInactiveBackground; @@ -110,6 +117,7 @@ public class FlatTextFieldUI private Insets defaultMargin; private FocusListener focusListener; + private DocumentListener documentListener; private Map oldStyleValues; private AtomicBoolean borderShared; @@ -126,6 +134,7 @@ public class FlatTextFieldUI installLeadingComponent(); installTrailingComponent(); + installClearButton(); installStyle(); } @@ -134,6 +143,7 @@ public class FlatTextFieldUI public void uninstallUI( JComponent c ) { uninstallLeadingComponent(); uninstallTrailingComponent(); + uninstallClearButton(); super.uninstallUI( c ); @@ -196,6 +206,11 @@ public class FlatTextFieldUI getComponent().removeFocusListener( focusListener ); focusListener = null; + + if( documentListener != null ) { + getComponent().getDocument().removeDocumentListener( documentListener ); + documentListener = null; + } } @Override @@ -254,9 +269,47 @@ public class FlatTextFieldUI c.revalidate(); c.repaint(); break; + + case TEXT_FIELD_SHOW_CLEAR_BUTTON: + uninstallClearButton(); + installClearButton(); + c.revalidate(); + c.repaint(); + break; + + case "enabled": + case "editable": + updateClearButton(); + break; + + case "document": + if( documentListener != null ) { + if( e.getOldValue() instanceof Document ) + ((Document)e.getOldValue()).removeDocumentListener( documentListener ); + if( e.getNewValue() instanceof Document ) + ((Document)e.getNewValue()).addDocumentListener( documentListener ); + + updateClearButton(); + } + break; } } + /** @since 2 */ + protected void installDocumentListener() { + if( documentListener != null ) + return; + + documentListener = new FlatDocumentListener(); + getComponent().getDocument().addDocumentListener( documentListener ); + } + + /** @since 2 */ + protected void documentChanged( DocumentEvent e ) { + if( clearButton != null ) + updateClearButton(); + } + /** @since 2 */ protected void installStyle() { try { @@ -275,10 +328,15 @@ public class FlatTextFieldUI protected void applyStyle( Object style ) { oldDisabledBackground = disabledBackground; oldInactiveBackground = inactiveBackground; + boolean oldShowClearButton = showClearButton; oldStyleValues = FlatStylingSupport.parseAndApply( oldStyleValues, style, this::applyStyleProperty ); updateBackground(); + if( showClearButton != oldShowClearButton ) { + uninstallClearButton(); + installClearButton(); + } } /** @since 2 */ @@ -474,10 +532,14 @@ debug*/ size.width += getLeadingIconWidth() + getTrailingIconWidth(); // add width of leading and trailing components - if( leadingComponent != null && leadingComponent.isVisible() ) - size.width += leadingComponent.getPreferredSize().width; - if( trailingComponent != null && trailingComponent.isVisible() ) - size.width += trailingComponent.getPreferredSize().width; + for( JComponent comp : getLeadingComponents() ) { + if( comp != null && comp.isVisible() ) + size.width += comp.getPreferredSize().width; + } + for( JComponent comp : getTrailingComponents() ) { + if( comp != null && comp.isVisible() ) + size.width += comp.getPreferredSize().width; + } return size; } @@ -558,17 +620,24 @@ debug*/ boolean ltr = isLeftToRight(); // remove width of leading/trailing components - JComponent leftComponent = ltr ? leadingComponent : trailingComponent; - JComponent rightComponent = ltr ? trailingComponent : leadingComponent; - boolean leftVisible = leftComponent != null && leftComponent.isVisible(); - boolean rightVisible = rightComponent != null && rightComponent.isVisible(); - if( leftVisible ) { - int w = leftComponent.getPreferredSize().width; - r.x += w; - r.width -= w; + JComponent[] leftComponents = ltr ? getLeadingComponents() : getTrailingComponents(); + JComponent[] rightComponents = ltr ? getTrailingComponents() : getLeadingComponents(); + boolean leftVisible = false; + boolean rightVisible = false; + for( JComponent leftComponent : leftComponents ) { + if( leftComponent != null && leftComponent.isVisible() ) { + int w = leftComponent.getPreferredSize().width; + r.x += w; + r.width -= w; + leftVisible = true; + } + } + for( JComponent rightComponent : rightComponents ) { + if( rightComponent != null && rightComponent.isVisible() ) { + r.width -= rightComponent.getPreferredSize().width; + rightVisible = true; + } } - if( rightVisible ) - r.width -= rightComponent.getPreferredSize().width; // if a leading/trailing icons (or components) are shown, then the left/right margins are reduced // to the top margin, which places the icon nicely centered on left/right side @@ -671,6 +740,76 @@ debug*/ } } + /** @since 2 */ + protected void installClearButton() { + JTextComponent c = getComponent(); + if( clientPropertyBoolean( c, TEXT_FIELD_SHOW_CLEAR_BUTTON, showClearButton ) ) { + clearButton = createClearButton(); + updateClearButton(); + installDocumentListener(); + installLayout(); + c.add( clearButton ); + } + } + + /** @since 2 */ + protected JComponent createClearButton() { + JButton button = new JButton(); + button.putClientProperty( STYLE_CLASS, "clearButton" ); + button.putClientProperty( BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON ); + button.setCursor( Cursor.getDefaultCursor() ); + button.addActionListener( e -> { + getComponent().setText( "" ); + } ); + return button; + } + + /** @since 2 */ + protected void uninstallClearButton() { + if( clearButton != null ) { + getComponent().remove( clearButton ); + clearButton = null; + } + } + + /** @since 2 */ + protected void updateClearButton() { + if( clearButton == null ) + return; + + JTextComponent c = getComponent(); + boolean visible = c.isEnabled() && c.isEditable() && c.getDocument().getLength() > 0; + if( visible != clearButton.isVisible() ) { + clearButton.setVisible( visible ); + c.revalidate(); + c.repaint(); + } + } + + /** + * Returns components placed at the leading side of the text field. + * The returned array may contain {@code null}. + * The default implementation returns {@link #leadingComponent}. + * + * @since 2 + */ + protected JComponent[] getLeadingComponents() { + return new JComponent[] { leadingComponent }; + } + + /** + * Returns components placed at the trailing side of the text field. + * The returned array may contain {@code null}. + * The default implementation returns {@link #trailingComponent} and {@link #clearButton}. + *

+ * Note: The components in the array must be in reverse (visual) order. + * + * @since 2 + */ + protected JComponent[] getTrailingComponents() { + return new JComponent[] { trailingComponent, clearButton }; + } + /** @since 2 */ protected void prepareLeadingOrTrailingComponent( JComponent c ) { c.putClientProperty( STYLE_CLASS, "inTextField" ); @@ -686,7 +825,8 @@ debug*/ } } - private void installLayout() { + /** @since 2 */ + protected void installLayout() { JTextComponent c = getComponent(); LayoutManager oldLayout = c.getLayout(); if( !(oldLayout instanceof FlatTextFieldLayout) ) @@ -731,25 +871,30 @@ debug*/ if( delegate != null ) delegate.layoutContainer( parent ); - if( leadingComponent == null && trailingComponent == null ) - return; - int ow = FlatUIUtils.getBorderFocusAndLineWidth( getComponent() ); int h = parent.getHeight() - ow - ow; boolean ltr = isLeftToRight(); - JComponent leftComponent = ltr ? leadingComponent : trailingComponent; - JComponent rightComponent = ltr ? trailingComponent : leadingComponent; + JComponent[] leftComponents = ltr ? getLeadingComponents() : getTrailingComponents(); + JComponent[] rightComponents = ltr ? getTrailingComponents() : getLeadingComponents(); - // layout left component - if( leftComponent != null && leftComponent.isVisible() ) { - int w = leftComponent.getPreferredSize().width; - leftComponent.setBounds( ow, ow, w, h ); + // layout left components + int x = ow; + for( JComponent leftComponent : leftComponents ) { + if( leftComponent != null && leftComponent.isVisible() ) { + int cw = leftComponent.getPreferredSize().width; + leftComponent.setBounds( x, ow, cw, h ); + x += cw; + } } - // layout right component - if( rightComponent != null && rightComponent.isVisible() ) { - int w = rightComponent.getPreferredSize().width; - rightComponent.setBounds( parent.getWidth() - ow - w, ow, w, h ); + // layout right components + x = parent.getWidth() - ow; + for( JComponent rightComponent : rightComponents ) { + if( rightComponent != null && rightComponent.isVisible() ) { + int cw = rightComponent.getPreferredSize().width; + x -= cw; + rightComponent.setBounds( x, ow, cw, h ); + } } } @@ -780,4 +925,24 @@ debug*/ ((LayoutManager2)delegate).invalidateLayout( target ); } } + //---- class FlatDocumentListener ----------------------------------------- + + private class FlatDocumentListener + implements DocumentListener + { + @Override + public void insertUpdate( DocumentEvent e ) { + documentChanged( e ); + } + + @Override + public void removeUpdate( DocumentEvent e ) { + documentChanged( e ); + } + + @Override + public void changedUpdate( DocumentEvent e ) { + documentChanged( e ); + } + } } 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 53879b71..f8ba150b 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -907,3 +907,16 @@ Tree.icon.openColor = @icon [style]ToolBarSeparator.inTextField = \ separatorWidth: 3 + + +#---- clearButton ---- +# for clear/cancel button in text fields + +[style]Button.clearButton = \ + icon: com.formdev.flatlaf.icons.FlatClearIcon; \ + focusable: false; \ + toolbar.margin: 1,1,1,1; \ + toolbar.spacingInsets: 1,1,1,1; \ + background: $TextField.background; \ + toolbar.hoverBackground: $TextField.background; \ + toolbar.pressedBackground: $TextField.background 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 e663fba6..7842df5d 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 @@ -823,7 +823,8 @@ public class TestFlatStyleableInfo "focusedBackground", Color.class, "iconTextGap", int.class, "leadingIcon", Icon.class, - "trailingIcon", Icon.class + "trailingIcon", Icon.class, + "showClearButton", boolean.class ); // border 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 b63a234d..700ced80 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 @@ -1011,6 +1011,8 @@ public class TestFlatStyling ui.applyStyle( "leadingIcon: com.formdev.flatlaf.icons.FlatSearchIcon" ); ui.applyStyle( "trailingIcon: com.formdev.flatlaf.icons.FlatClearIcon" ); + ui.applyStyle( "showClearButton: true" ); + // border flatTextBorder( style -> ui.applyStyle( style ) ); diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java index dc9e05e3..a43d40a5 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java @@ -73,6 +73,10 @@ class BasicComponentsPanel searchToolbar.addSeparator(); searchToolbar.add( regexButton ); compsTextField.putClientProperty( FlatClientProperties.TEXT_FIELD_TRAILING_COMPONENT, searchToolbar ); + + // show clear button (if text field is not empty) + compsTextField.putClientProperty( FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true ); + clearTextField.putClientProperty( FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, true ); } private void initComponents() { @@ -173,6 +177,7 @@ class BasicComponentsPanel JTextField iconsTextField = new JTextField(); JLabel compsLabel = new JLabel(); compsTextField = new JTextField(); + clearTextField = new JTextField(); JLabel fontsLabel = new JLabel(); JLabel h00Label = new JLabel(); JLabel h0Label = new JLabel(); @@ -734,6 +739,10 @@ class BasicComponentsPanel add(compsLabel, "cell 0 15"); add(compsTextField, "cell 1 15 2 1,growx"); + //---- clearTextField ---- + clearTextField.setText("clear me"); + add(clearTextField, "cell 3 15,growx"); + //---- fontsLabel ---- fontsLabel.setText("Typography / Fonts:"); add(fontsLabel, "cell 0 16"); @@ -905,5 +914,6 @@ class BasicComponentsPanel // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables private JTextField compsTextField; + private JTextField clearTextField; // JFormDesigner - End of variables declaration //GEN-END:variables } diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd index c5eb1554..b6d9a4b9 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd @@ -685,6 +685,15 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 15 2 1,growx" } ) + add( new FormComponent( "javax.swing.JTextField" ) { + name: "clearTextField" + "text": "clear me" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 3 15,growx" + } ) add( new FormComponent( "javax.swing.JLabel" ) { name: "fontsLabel" "text": "Typography / Fonts:" diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java index 326930f7..c3aa8826 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatFormattedTextField.java @@ -144,6 +144,26 @@ public class FlatFormattedTextField } + /** + * Returns whether a "clear" (or "cancel") button is shown. + * + * @since 2 + */ + public boolean isShowClearButton() { + return getClientPropertyBoolean( TEXT_FIELD_SHOW_CLEAR_BUTTON, false ); + } + + /** + * Specifies whether a "clear" (or "cancel") button is shown on the trailing side + * if the text field is not empty, editable and enabled. + * + * @since 2 + */ + public void setShowClearButton( boolean showClearButton ) { + putClientPropertyBoolean( TEXT_FIELD_SHOW_CLEAR_BUTTON, showClearButton, false ); + } + + /** * Returns whether all text is selected when the text component gains focus. */ diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java index cd7f2374..7c36be76 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatPasswordField.java @@ -144,6 +144,26 @@ public class FlatPasswordField } + /** + * Returns whether a "clear" (or "cancel") button is shown. + * + * @since 2 + */ + public boolean isShowClearButton() { + return getClientPropertyBoolean( TEXT_FIELD_SHOW_CLEAR_BUTTON, false ); + } + + /** + * Specifies whether a "clear" (or "cancel") button is shown on the trailing side + * if the text field is not empty, editable and enabled. + * + * @since 2 + */ + public void setShowClearButton( boolean showClearButton ) { + putClientPropertyBoolean( TEXT_FIELD_SHOW_CLEAR_BUTTON, showClearButton, false ); + } + + /** * Returns whether all text is selected when the text component gains focus. */ diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java index c7747bf5..c2d14a07 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/components/FlatTextField.java @@ -143,6 +143,26 @@ public class FlatTextField } + /** + * Returns whether a "clear" (or "cancel") button is shown. + * + * @since 2 + */ + public boolean isShowClearButton() { + return getClientPropertyBoolean( TEXT_FIELD_SHOW_CLEAR_BUTTON, false ); + } + + /** + * Specifies whether a "clear" (or "cancel") button is shown on the trailing side + * if the text field is not empty, editable and enabled. + * + * @since 2 + */ + public void setShowClearButton( boolean showClearButton ) { + putClientPropertyBoolean( TEXT_FIELD_SHOW_CLEAR_BUTTON, showClearButton, false ); + } + + // NOTE: enum names must be equal to allowed strings public enum SelectAllOnFocusPolicy { never, once, always }; diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt index 7cf0ed6a..c1741a44 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt @@ -1421,6 +1421,7 @@ ViewportUI com.formdev.flatlaf.ui.FlatViewportUI #---- [style]Button ---- +[style]Button.clearButton icon: com.formdev.flatlaf.icons.FlatClearIcon; focusable: false; toolbar.margin: 1,1,1,1; toolbar.spacingInsets: 1,1,1,1; background: $TextField.background; toolbar.hoverBackground: $TextField.background; toolbar.pressedBackground: $TextField.background [style]Button.inTextField focusable: false; toolbar.margin: 1,1,1,1; toolbar.spacingInsets: 1,1,1,1; background: $TextField.background; toolbar.hoverBackground: lighten($TextField.background,4%,derived); toolbar.pressedBackground: lighten($TextField.background,6%,derived); toolbar.selectedBackground: lighten($TextField.background,12%,derived) diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt index c0ae8ece..00d5267e 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt @@ -1426,6 +1426,7 @@ ViewportUI com.formdev.flatlaf.ui.FlatViewportUI #---- [style]Button ---- +[style]Button.clearButton icon: com.formdev.flatlaf.icons.FlatClearIcon; focusable: false; toolbar.margin: 1,1,1,1; toolbar.spacingInsets: 1,1,1,1; background: $TextField.background; toolbar.hoverBackground: $TextField.background; toolbar.pressedBackground: $TextField.background [style]Button.inTextField focusable: false; toolbar.margin: 1,1,1,1; toolbar.spacingInsets: 1,1,1,1; background: $TextField.background; toolbar.hoverBackground: darken($TextField.background,4%,derived); toolbar.pressedBackground: darken($TextField.background,8%,derived); toolbar.selectedBackground: darken($TextField.background,12%,derived) diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt index be298499..204b1cd8 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt @@ -1439,6 +1439,7 @@ ViewportUI com.formdev.flatlaf.ui.FlatViewportUI #---- [style]Button ---- +[style]Button.clearButton icon: com.formdev.flatlaf.icons.FlatClearIcon; focusable: false; toolbar.margin: 1,1,1,1; toolbar.spacingInsets: 1,1,1,1; background: $TextField.background; toolbar.hoverBackground: $TextField.background; toolbar.pressedBackground: $TextField.background [style]Button.inTextField focusable: false; toolbar.margin: 1,1,1,1; toolbar.spacingInsets: 1,1,1,1 diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java index a0c66148..e3bfa84b 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java @@ -121,6 +121,11 @@ public class FlatTextComponentsTest } } + private void showClearButton() { + putTextFieldClientProperty( FlatClientProperties.TEXT_FIELD_SHOW_CLEAR_BUTTON, + showClearButtonCheckBox.isSelected() ); + } + private void putTextFieldClientProperty( String key, Object value ) { for( Component c : getComponents() ) { if( c instanceof JTextField ) @@ -168,6 +173,7 @@ public class FlatTextComponentsTest trailingComponentCheckBox = new JCheckBox(); leadingComponentVisibleCheckBox = new JCheckBox(); trailingComponentVisibleCheckBox = new JCheckBox(); + showClearButtonCheckBox = new JCheckBox(); JLabel passwordFieldLabel = new JLabel(); JPasswordField passwordField1 = new JPasswordField(); JPasswordField passwordField3 = new JPasswordField(); @@ -319,6 +325,7 @@ public class FlatTextComponentsTest "[]0" + "[]" + "[]0" + + "[]" + "[]")); //---- button1 ---- @@ -404,6 +411,12 @@ public class FlatTextComponentsTest trailingComponentVisibleCheckBox.setName("trailingComponentVisibleCheckBox"); trailingComponentVisibleCheckBox.addActionListener(e -> trailingComponentVisible()); panel1.add(trailingComponentVisibleCheckBox, "cell 0 10 2 1,alignx left,growx 0"); + + //---- showClearButtonCheckBox ---- + showClearButtonCheckBox.setText("clear button"); + showClearButtonCheckBox.setName("showClearButtonCheckBox"); + showClearButtonCheckBox.addActionListener(e -> showClearButton()); + panel1.add(showClearButtonCheckBox, "cell 0 11 2 1,alignx left,growx 0"); } add(panel1, "cell 4 0 1 10,aligny top,growy 0"); @@ -719,6 +732,7 @@ public class FlatTextComponentsTest private JCheckBox trailingComponentCheckBox; private JCheckBox leadingComponentVisibleCheckBox; private JCheckBox trailingComponentVisibleCheckBox; + private JCheckBox showClearButtonCheckBox; private JTextField textField; private JCheckBox dragEnabledCheckBox; private JTextArea textArea; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd index 2dfcfb7b..06681160 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd @@ -77,7 +77,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" "$columnConstraints": "[fill][fill]" - "$rowConstraints": "[][][][][][]0[][]0[][]0[]" + "$rowConstraints": "[][][][][][]0[][]0[][]0[][]" } ) { name: "panel1" "border": new javax.swing.border.TitledBorder( "Control" ) @@ -210,6 +210,16 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 10 2 1,alignx left,growx 0" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "showClearButtonCheckBox" + "text": "clear button" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showClearButton", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 11 2 1,alignx left,growx 0" + } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 4 0 1 10,aligny top,growy 0" } ) diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt index 3257247b..b0340b10 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt @@ -1115,6 +1115,7 @@ ViewportUI [style].monospaced [style].semibold [style].small +[style]Button.clearButton [style]Button.inTextField [style]ToggleButton.inTextField [style]ToolBar.inTextField From 02a9d4e31d56d3d609673c5a88d2923ce6723fd0 Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 14 Dec 2021 00:57:40 +0100 Subject: [PATCH 2/4] PasswordField: support "reveal" button to show password (issue #173) --- CHANGELOG.md | 4 +- .../formdev/flatlaf/icons/FlatRevealIcon.java | 56 ++++++++++++++ .../flatlaf/ui/FlatPasswordFieldUI.java | 74 ++++++++++++++++++- .../com/formdev/flatlaf/FlatLaf.properties | 3 + .../flatlaf/ui/TestFlatStyleableInfo.java | 3 +- .../formdev/flatlaf/ui/TestFlatStyling.java | 1 + .../flatlaf/demo/BasicComponentsPanel.java | 8 +- .../flatlaf/demo/BasicComponentsPanel.jfd | 3 + .../dumps/uidefaults/FlatDarkLaf_1.8.0.txt | 3 + .../dumps/uidefaults/FlatLightLaf_1.8.0.txt | 3 + .../dumps/uidefaults/FlatTestLaf_1.8.0.txt | 3 + .../testing/FlatTextComponentsTest.java | 17 +++++ .../testing/FlatTextComponentsTest.jfd | 12 ++- .../flatlaf/themeeditor/FlatLafUIKeys.txt | 3 + 14 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatRevealIcon.java diff --git a/CHANGELOG.md b/CHANGELOG.md index b7545f86..1b91df39 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,9 @@ FlatLaf Change Log `java.awt.Component`). (PR #386) - Support "clear" (or "cancel") button to empty text field. Only shown if text field is not empty, editable and enabled. (set client property - `JTextField.showClearButton` to `true`). (PR #TODO) + `JTextField.showClearButton` to `true`). (PR #442) +- PasswordField: Support reveal (or "eye") button to show password. (see UI + value `PasswordField.showRevealButton`) (PR #442; issue #173) - TextComponents: Double/triple-click-and-drag now extends selection by whole words/lines. - Theming improvements: Reworks core themes to make it easier to create new diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatRevealIcon.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatRevealIcon.java new file mode 100644 index 00000000..8956e129 --- /dev/null +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/icons/FlatRevealIcon.java @@ -0,0 +1,56 @@ +/* + * Copyright 2021 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 + * + * https://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.icons; + +import java.awt.Component; +import java.awt.Graphics2D; +import java.awt.geom.Area; +import java.awt.geom.Ellipse2D; +import java.awt.geom.Path2D; +import java.awt.geom.Rectangle2D; +import javax.swing.UIManager; + +/** + * "eye" icon for {@link javax.swing.JPasswordField}. + * + * @uiDefault PasswordField.revealIconColor Color + * + * @author Karl Tauber + * @since 2 + */ +public class FlatRevealIcon + extends FlatAbstractIcon +{ + public FlatRevealIcon() { + super( 16, 16, UIManager.getColor( "PasswordField.revealIconColor" ) ); + } + + @Override + protected void paintIcon( Component c, Graphics2D g ) { + Path2D path = new Path2D.Float( Path2D.WIND_EVEN_ODD ); + path.append( new Ellipse2D.Float( 5.15f, 6.15f, 5.7f, 5.7f ), false ); + path.append( new Ellipse2D.Float( 6, 7, 4, 4 ), false ); + g.fill( path ); + + Path2D path2 = new Path2D.Float( Path2D.WIND_EVEN_ODD ); + path2.append( new Ellipse2D.Float( 2.15f, 4.15f, 11.7f, 11.7f ), false ); + path2.append( new Ellipse2D.Float( 3, 5, 10, 10 ), false ); + Area area = new Area( path2 ); + area.subtract( new Area( new Rectangle2D.Float( 0, 9.5f, 16, 16 ) ) ); + g.fill( area ); + } +} diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java index 69c93889..5d5fc82d 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatPasswordFieldUI.java @@ -28,6 +28,7 @@ import javax.swing.Action; import javax.swing.ActionMap; import javax.swing.Icon; import javax.swing.JComponent; +import javax.swing.JToggleButton; import javax.swing.LookAndFeel; import javax.swing.SwingUtilities; import javax.swing.UIManager; @@ -73,18 +74,25 @@ import com.formdev.flatlaf.util.UIScale; * * @uiDefault PasswordField.echoChar character * @uiDefault PasswordField.showCapsLock boolean + * @uiDefault PasswordField.showRevealButton boolean * @uiDefault PasswordField.capsLockIcon Icon + * @uiDefault PasswordField.revealIcon Icon * * @author Karl Tauber */ public class FlatPasswordFieldUI extends FlatTextFieldUI { + private Character echoChar; + @Styleable protected boolean showCapsLock; + /** @since 2 */ @Styleable protected boolean showRevealButton; protected Icon capsLockIcon; + /** @since 2 */ protected Icon revealIcon; private KeyListener capsLockListener; private boolean capsLockIconShared = true; + private JToggleButton revealButton; public static ComponentUI createUI( JComponent c ) { return new FlatPasswordFieldUI(); @@ -95,17 +103,33 @@ public class FlatPasswordFieldUI return "PasswordField"; } + @Override + public void installUI( JComponent c ) { + super.installUI( c ); + + installRevealButton(); + } + + @Override + public void uninstallUI( JComponent c ) { + uninstallRevealButton(); + + super.uninstallUI( c ); + } + @Override protected void installDefaults() { super.installDefaults(); String prefix = getPropertyPrefix(); - Character echoChar = (Character) UIManager.get( prefix + ".echoChar" ); + echoChar = (Character) UIManager.get( prefix + ".echoChar" ); if( echoChar != null ) LookAndFeel.installProperty( getComponent(), "echoChar", echoChar ); showCapsLock = UIManager.getBoolean( "PasswordField.showCapsLock" ); + showRevealButton = UIManager.getBoolean( "PasswordField.showRevealButton" ); capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" ); + revealIcon = UIManager.getIcon( "PasswordField.revealIcon" ); capsLockIconShared = true; } @@ -114,6 +138,7 @@ public class FlatPasswordFieldUI super.uninstallDefaults(); capsLockIcon = null; + revealIcon = null; } @Override @@ -168,6 +193,18 @@ public class FlatPasswordFieldUI return "PasswordField"; } + @Override + protected void applyStyle( Object style ) { + boolean oldShowRevealButton = showRevealButton; + + super.applyStyle( style ); + + if( showRevealButton != oldShowRevealButton ) { + uninstallRevealButton(); + installRevealButton(); + } + } + /** @since 2 */ @Override protected Object applyStyleProperty( String key, Object value ) { @@ -236,4 +273,39 @@ public class FlatPasswordFieldUI return FlatUIUtils.isPermanentFocusOwner( c ) && Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK ); } + + /** @since 2 */ + protected void installRevealButton() { + JTextComponent c = getComponent(); + if( showRevealButton ) { + revealButton = createRevealButton(); + installLayout(); + c.add( revealButton ); + } + } + + /** @since 2 */ + protected JToggleButton createRevealButton() { + JToggleButton button = new JToggleButton( revealIcon ); + prepareLeadingOrTrailingComponent( button ); + button.addActionListener( e -> { + LookAndFeel.installProperty( getComponent(), "echoChar", button.isSelected() + ? '\0' + : (echoChar != null ? echoChar : '*')); + } ); + return button; + } + + /** @since 2 */ + protected void uninstallRevealButton() { + if( revealButton != null ) { + getComponent().remove( revealButton ); + revealButton = null; + } + } + + @Override + protected JComponent[] getTrailingComponents() { + return new JComponent[] { trailingComponent, revealButton, clearButton }; + } } 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 f8ba150b..e4d4dbfc 100644 --- a/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties +++ b/flatlaf-core/src/main/resources/com/formdev/flatlaf/FlatLaf.properties @@ -484,7 +484,10 @@ PasswordField.placeholderForeground = @disabledForeground PasswordField.iconTextGap = 4 PasswordField.echoChar = \u2022 PasswordField.showCapsLock = true +PasswordField.showRevealButton = false PasswordField.capsLockIcon = com.formdev.flatlaf.icons.FlatCapsLockIcon +PasswordField.revealIcon = com.formdev.flatlaf.icons.FlatRevealIcon +PasswordField.revealIconColor = lazy(Actions.Grey) #---- Popup ---- 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 7842df5d..80900989 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 @@ -398,7 +398,8 @@ public class TestFlatStyleableInfo Map> expected = new LinkedHashMap<>(); expectedMap( expected, - "showCapsLock", boolean.class + "showCapsLock", boolean.class, + "showRevealButton", boolean.class ); // FlatPasswordFieldUI extends FlatTextFieldUI 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 700ced80..77654ca9 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 @@ -551,6 +551,7 @@ public class TestFlatStyling textField( ui ); ui.applyStyle( "showCapsLock: true" ); + ui.applyStyle( "showRevealButton: true" ); // capsLockIcon ui.applyStyle( "capsLockIconColor: #fff" ); diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java index a43d40a5..6c2e9f23 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.java @@ -38,6 +38,11 @@ class BasicComponentsPanel BasicComponentsPanel() { initComponents(); + // show reveal button for password field + // to enable this for all password fields use: + // UIManager.put( "PasswordField.showRevealButton", true ); + passwordField1.putClientProperty( FlatClientProperties.STYLE, "showRevealButton: true" ); + // search history button JButton searchHistoryButton = new JButton( new FlatSearchWithHistoryIcon( true ) ); searchHistoryButton.setToolTipText( "Search History" ); @@ -128,7 +133,7 @@ class BasicComponentsPanel JFormattedTextField formattedTextField4 = new JFormattedTextField(); JFormattedTextField formattedTextField5 = new JFormattedTextField(); JLabel passwordFieldLabel = new JLabel(); - JPasswordField passwordField1 = new JPasswordField(); + passwordField1 = new JPasswordField(); JPasswordField passwordField2 = new JPasswordField(); JPasswordField passwordField3 = new JPasswordField(); JPasswordField passwordField4 = new JPasswordField(); @@ -913,6 +918,7 @@ class BasicComponentsPanel } // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables + private JPasswordField passwordField1; private JTextField compsTextField; private JTextField clearTextField; // JFormDesigner - End of variables declaration //GEN-END:variables diff --git a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd index b6d9a4b9..b2735da8 100644 --- a/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd +++ b/flatlaf-demo/src/main/java/com/formdev/flatlaf/demo/BasicComponentsPanel.jfd @@ -375,6 +375,9 @@ new FormModel { add( new FormComponent( "javax.swing.JPasswordField" ) { name: "passwordField1" "text": "Editable" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 8,growx" } ) diff --git a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt index c1741a44..a3040e7a 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatDarkLaf_1.8.0.txt @@ -711,9 +711,12 @@ PasswordField.inactiveBackground #3c3f41 HSL 204 4 25 javax.swing.plaf.Co PasswordField.inactiveForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI] PasswordField.margin 2,6,2,6 javax.swing.plaf.InsetsUIResource [UI] PasswordField.placeholderForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI] +PasswordField.revealIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatRevealIcon [UI] +PasswordField.revealIconColor [lazy] #afb1b3 HSL 210 3 69 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionBackground #4b6eaf HSL 219 40 49 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI] PasswordField.showCapsLock true +PasswordField.showRevealButton false PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI diff --git a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt index 00d5267e..fc4ffa67 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatLightLaf_1.8.0.txt @@ -716,9 +716,12 @@ PasswordField.inactiveBackground #f2f2f2 HSL 0 0 95 javax.swing.plaf.Co PasswordField.inactiveForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI] PasswordField.margin 2,6,2,6 javax.swing.plaf.InsetsUIResource [UI] PasswordField.placeholderForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI] +PasswordField.revealIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatRevealIcon [UI] +PasswordField.revealIconColor [lazy] #6e6e6e HSL 0 0 43 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionBackground #2675bf HSL 209 67 45 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI] PasswordField.showCapsLock true +PasswordField.showRevealButton false PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI diff --git a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt index 204b1cd8..7dcba7ad 100644 --- a/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt +++ b/flatlaf-testing/dumps/uidefaults/FlatTestLaf_1.8.0.txt @@ -726,9 +726,12 @@ PasswordField.inactiveBackground #f0f0f0 HSL 0 0 94 javax.swing.plaf.Co PasswordField.inactiveForeground #000088 HSL 240 100 27 javax.swing.plaf.ColorUIResource [UI] PasswordField.margin 2,6,2,6 javax.swing.plaf.InsetsUIResource [UI] PasswordField.placeholderForeground #000088 HSL 240 100 27 javax.swing.plaf.ColorUIResource [UI] +PasswordField.revealIcon [lazy] 16,16 com.formdev.flatlaf.icons.FlatRevealIcon [UI] +PasswordField.revealIconColor [lazy] #6e6e6e HSL 0 0 43 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionBackground #00aa00 HSL 120 100 33 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionForeground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI] PasswordField.showCapsLock true +PasswordField.showRevealButton false PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java index e3bfa84b..0a3a2ad2 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.java @@ -126,6 +126,14 @@ public class FlatTextComponentsTest showClearButtonCheckBox.isSelected() ); } + private void showRevealButton() { + for( Component c : getComponents() ) { + if( c instanceof JPasswordField ) + ((JPasswordField)c).putClientProperty(FlatClientProperties.STYLE, + showRevealButtonCheckBox.isSelected() ? "showRevealButton: true" : null ); + } + } + private void putTextFieldClientProperty( String key, Object value ) { for( Component c : getComponents() ) { if( c instanceof JTextField ) @@ -174,6 +182,7 @@ public class FlatTextComponentsTest leadingComponentVisibleCheckBox = new JCheckBox(); trailingComponentVisibleCheckBox = new JCheckBox(); showClearButtonCheckBox = new JCheckBox(); + showRevealButtonCheckBox = new JCheckBox(); JLabel passwordFieldLabel = new JLabel(); JPasswordField passwordField1 = new JPasswordField(); JPasswordField passwordField3 = new JPasswordField(); @@ -326,6 +335,7 @@ public class FlatTextComponentsTest "[]" + "[]0" + "[]" + + "[]" + "[]")); //---- button1 ---- @@ -417,6 +427,12 @@ public class FlatTextComponentsTest showClearButtonCheckBox.setName("showClearButtonCheckBox"); showClearButtonCheckBox.addActionListener(e -> showClearButton()); panel1.add(showClearButtonCheckBox, "cell 0 11 2 1,alignx left,growx 0"); + + //---- showRevealButtonCheckBox ---- + showRevealButtonCheckBox.setText("password reveal button"); + showRevealButtonCheckBox.setName("showRevealButtonCheckBox"); + showRevealButtonCheckBox.addActionListener(e -> showRevealButton()); + panel1.add(showRevealButtonCheckBox, "cell 0 12 2 1,alignx left,growx 0"); } add(panel1, "cell 4 0 1 10,aligny top,growy 0"); @@ -733,6 +749,7 @@ public class FlatTextComponentsTest private JCheckBox leadingComponentVisibleCheckBox; private JCheckBox trailingComponentVisibleCheckBox; private JCheckBox showClearButtonCheckBox; + private JCheckBox showRevealButtonCheckBox; private JTextField textField; private JCheckBox dragEnabledCheckBox; private JTextArea textArea; diff --git a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd index 06681160..0d8d6b31 100644 --- a/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd +++ b/flatlaf-testing/src/main/java/com/formdev/flatlaf/testing/FlatTextComponentsTest.jfd @@ -77,7 +77,7 @@ new FormModel { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { "$layoutConstraints": "hidemode 3" "$columnConstraints": "[fill][fill]" - "$rowConstraints": "[][][][][][]0[][]0[][]0[][]" + "$rowConstraints": "[][][][][][]0[][]0[][]0[][][]" } ) { name: "panel1" "border": new javax.swing.border.TitledBorder( "Control" ) @@ -220,6 +220,16 @@ new FormModel { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 0 11 2 1,alignx left,growx 0" } ) + add( new FormComponent( "javax.swing.JCheckBox" ) { + name: "showRevealButtonCheckBox" + "text": "password reveal button" + auxiliary() { + "JavaCodeGenerator.variableLocal": false + } + addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "showRevealButton", false ) ) + }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { + "value": "cell 0 12 2 1,alignx left,growx 0" + } ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 4 0 1 10,aligny top,growy 0" } ) diff --git a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt index b0340b10..e719971f 100644 --- a/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt +++ b/flatlaf-theme-editor/src/main/resources/com/formdev/flatlaf/themeeditor/FlatLafUIKeys.txt @@ -557,9 +557,12 @@ PasswordField.inactiveBackground PasswordField.inactiveForeground PasswordField.margin PasswordField.placeholderForeground +PasswordField.revealIcon +PasswordField.revealIconColor PasswordField.selectionBackground PasswordField.selectionForeground PasswordField.showCapsLock +PasswordField.showRevealButton PasswordFieldUI Popup.dropShadowColor Popup.dropShadowInsets From 45332c8126437d541e6a0f5008c0dd871d2f18ba Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 14 Dec 2021 10:42:07 +0100 Subject: [PATCH 3/4] TextField: added "clear" button to theme editor find/replace and to UI defaults inspector --- .../formdev/flatlaf/extras/FlatUIDefaultsInspector.java | 8 +++++--- .../formdev/flatlaf/extras/FlatUIDefaultsInspector.jfd | 5 +++-- .../formdev/flatlaf/themeeditor/FlatFindReplaceBar.java | 2 ++ .../formdev/flatlaf/themeeditor/FlatFindReplaceBar.jfd | 2 ++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatUIDefaultsInspector.java b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatUIDefaultsInspector.java index 9c79f129..d925d36d 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatUIDefaultsInspector.java +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatUIDefaultsInspector.java @@ -43,6 +43,7 @@ import javax.swing.table.AbstractTableModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.TableColumnModel; import com.formdev.flatlaf.FlatLaf; +import com.formdev.flatlaf.extras.components.FlatTextField; import com.formdev.flatlaf.icons.FlatAbstractIcon; import com.formdev.flatlaf.ui.FlatBorder; import com.formdev.flatlaf.ui.FlatEmptyBorder; @@ -557,7 +558,7 @@ public class FlatUIDefaultsInspector panel = new JPanel(); filterPanel = new JPanel(); flterLabel = new JLabel(); - filterField = new JTextField(); + filterField = new FlatTextField(); valueTypeLabel = new JLabel(); valueTypeField = new JComboBox<>(); scrollPane = new JScrollPane(); @@ -588,7 +589,8 @@ public class FlatUIDefaultsInspector new Insets(0, 0, 0, 10), 0, 0)); //---- filterField ---- - filterField.putClientProperty("JTextField.placeholderText", "enter one or more filter strings, separated by space characters"); + filterField.setPlaceholderText("enter one or more filter strings, separated by space characters"); + filterField.setShowClearButton(true); filterPanel.add(filterField, new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 10), 0, 0)); @@ -667,7 +669,7 @@ public class FlatUIDefaultsInspector private JPanel panel; private JPanel filterPanel; private JLabel flterLabel; - private JTextField filterField; + private FlatTextField filterField; private JLabel valueTypeLabel; private JComboBox valueTypeField; private JScrollPane scrollPane; diff --git a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatUIDefaultsInspector.jfd b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatUIDefaultsInspector.jfd index f7baa2f1..d81ca863 100644 --- a/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatUIDefaultsInspector.jfd +++ b/flatlaf-extras/src/main/java/com/formdev/flatlaf/extras/FlatUIDefaultsInspector.jfd @@ -20,9 +20,10 @@ new FormModel { "labelFor": new FormReference( "filterField" ) "displayedMnemonic": 70 }, new FormLayoutConstraints( class com.jformdesigner.runtime.GridBagConstraintsEx ) ) - add( new FormComponent( "javax.swing.JTextField" ) { + add( new FormComponent( "com.formdev.flatlaf.extras.components.FlatTextField" ) { name: "filterField" - "$client.JTextField.placeholderText": "enter one or more filter strings, separated by space characters" + "placeholderText": "enter one or more filter strings, separated by space characters" + "showClearButton": true }, new FormLayoutConstraints( class com.jformdesigner.runtime.GridBagConstraintsEx ) { "gridx": 1 } ) diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatFindReplaceBar.java b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatFindReplaceBar.java index 3c907a2d..820be31e 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatFindReplaceBar.java +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatFindReplaceBar.java @@ -304,6 +304,7 @@ class FlatFindReplaceBar //---- findField ---- findField.setColumns(16); findField.setSelectAllOnFocusPolicy(FlatTextField.SelectAllOnFocusPolicy.always); + findField.setShowClearButton(true); findField.addActionListener(e -> find()); add(findField, "cell 1 0"); @@ -365,6 +366,7 @@ class FlatFindReplaceBar //---- replaceField ---- replaceField.setColumns(16); replaceField.setSelectAllOnFocusPolicy(FlatTextField.SelectAllOnFocusPolicy.always); + replaceField.setShowClearButton(true); add(replaceField, "cell 1 1"); //======== toolBar1 ======== diff --git a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatFindReplaceBar.jfd b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatFindReplaceBar.jfd index e852b805..4f1c399b 100644 --- a/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatFindReplaceBar.jfd +++ b/flatlaf-theme-editor/src/main/java/com/formdev/flatlaf/themeeditor/FlatFindReplaceBar.jfd @@ -22,6 +22,7 @@ new FormModel { name: "findField" "columns": 16 "selectAllOnFocusPolicy": enum com.formdev.flatlaf.extras.components.FlatTextField$SelectAllOnFocusPolicy always + "showClearButton": true addEvent( new FormEvent( "java.awt.event.ActionListener", "actionPerformed", "find", false ) ) }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 0" @@ -91,6 +92,7 @@ new FormModel { name: "replaceField" "columns": 16 "selectAllOnFocusPolicy": enum com.formdev.flatlaf.extras.components.FlatTextField$SelectAllOnFocusPolicy always + "showClearButton": true }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { "value": "cell 1 1" } ) From 12b7389376118611e12bb6a093aeb2aca403145c Mon Sep 17 00:00:00 2001 From: Karl Tauber Date: Tue, 14 Dec 2021 11:28:03 +0100 Subject: [PATCH 4/4] TextField: added "clear" callback --- .../formdev/flatlaf/FlatClientProperties.java | 30 ++++++++++++++++ .../formdev/flatlaf/ui/FlatTextFieldUI.java | 36 ++++++++++++------- 2 files changed, 54 insertions(+), 12 deletions(-) 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 ce55df19..4f398439 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/FlatClientProperties.java @@ -914,6 +914,36 @@ public interface FlatClientProperties */ String TEXT_FIELD_SHOW_CLEAR_BUTTON = "JTextField.showClearButton"; + /** + * Specifies the callback that is invoked when a "clear" (or "cancel") button is clicked. + * If a callback is specified than it is responsible for clearing the text field. + * Without callback, the text field clears itself. + *

+ * Either use a {@link java.lang.Runnable}: + *

{@code
+	 * myTextField.putClientProperty( "JTextField.clearCallback",
+	 *     (Runnable) () -> {
+	 *         // clear field here or cancel search
+	 *     } );
+	 * }
+ * Or use a {@link java.util.function.Consumer}<javax.swing.text.JTextComponent> + * that receives the text field as parameter: + *
{@code
+	 * myTextField.putClientProperty( "JTextField.clearCallback",
+	 *     (Consumer) textField -> {
+	 *         // clear field here or cancel search
+	 *     } );
+	 * }
+ *

+ * Component {@link javax.swing.JTextField} (and subclasses)
+ * Value type {@link java.lang.Runnable} + * or {@link java.util.function.Consumer}<javax.swing.text.JTextComponent> + * + * @see #TEXT_FIELD_SHOW_CLEAR_BUTTON + * @since 2 + */ + String TEXT_FIELD_CLEAR_CALLBACK = "JTextField.clearCallback"; + //---- JToggleButton ------------------------------------------------------ /** diff --git a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java index 62f9496e..0dca6fc8 100644 --- a/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java +++ b/flatlaf-core/src/main/java/com/formdev/flatlaf/ui/FlatTextFieldUI.java @@ -35,6 +35,7 @@ import java.beans.PropertyChangeEvent; import java.util.Map; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; import javax.swing.Icon; import javax.swing.JButton; import javax.swing.JComboBox; @@ -752,18 +753,6 @@ debug*/ } } - /** @since 2 */ - protected JComponent createClearButton() { - JButton button = new JButton(); - button.putClientProperty( STYLE_CLASS, "clearButton" ); - button.putClientProperty( BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON ); - button.setCursor( Cursor.getDefaultCursor() ); - button.addActionListener( e -> { - getComponent().setText( "" ); - } ); - return button; - } - /** @since 2 */ protected void uninstallClearButton() { if( clearButton != null ) { @@ -772,6 +761,29 @@ debug*/ } } + /** @since 2 */ + protected JComponent createClearButton() { + JButton button = new JButton(); + button.putClientProperty( STYLE_CLASS, "clearButton" ); + button.putClientProperty( BUTTON_TYPE, BUTTON_TYPE_TOOLBAR_BUTTON ); + button.setCursor( Cursor.getDefaultCursor() ); + button.addActionListener( e -> clearButtonClicked() ); + return button; + } + + /** @since 2 */ + @SuppressWarnings( "unchecked" ) + protected void clearButtonClicked() { + JTextComponent c = getComponent(); + Object callback = c.getClientProperty( TEXT_FIELD_CLEAR_CALLBACK ); + if( callback instanceof Runnable ) + ((Runnable)callback).run(); + else if( callback instanceof Consumer ) + ((Consumer)callback).accept( c ); + else + c.setText( "" ); + } + /** @since 2 */ protected void updateClearButton() { if( clearButton == null )