PasswordField: support "reveal" button to show password (issue #173)

This commit is contained in:
Karl Tauber
2021-12-14 00:57:40 +01:00
parent a4377e81cb
commit 02a9d4e31d
14 changed files with 188 additions and 5 deletions

View File

@@ -37,7 +37,9 @@ FlatLaf Change Log
`java.awt.Component`). (PR #386) `java.awt.Component`). (PR #386)
- Support "clear" (or "cancel") button to empty text field. Only shown if text - Support "clear" (or "cancel") button to empty text field. Only shown if text
field is not empty, editable and enabled. (set client property 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 - TextComponents: Double/triple-click-and-drag now extends selection by whole
words/lines. words/lines.
- Theming improvements: Reworks core themes to make it easier to create new - Theming improvements: Reworks core themes to make it easier to create new

View File

@@ -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 );
}
}

View File

@@ -28,6 +28,7 @@ import javax.swing.Action;
import javax.swing.ActionMap; import javax.swing.ActionMap;
import javax.swing.Icon; import javax.swing.Icon;
import javax.swing.JComponent; import javax.swing.JComponent;
import javax.swing.JToggleButton;
import javax.swing.LookAndFeel; import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import javax.swing.UIManager; import javax.swing.UIManager;
@@ -73,18 +74,25 @@ import com.formdev.flatlaf.util.UIScale;
* *
* @uiDefault PasswordField.echoChar character * @uiDefault PasswordField.echoChar character
* @uiDefault PasswordField.showCapsLock boolean * @uiDefault PasswordField.showCapsLock boolean
* @uiDefault PasswordField.showRevealButton boolean
* @uiDefault PasswordField.capsLockIcon Icon * @uiDefault PasswordField.capsLockIcon Icon
* @uiDefault PasswordField.revealIcon Icon
* *
* @author Karl Tauber * @author Karl Tauber
*/ */
public class FlatPasswordFieldUI public class FlatPasswordFieldUI
extends FlatTextFieldUI extends FlatTextFieldUI
{ {
private Character echoChar;
@Styleable protected boolean showCapsLock; @Styleable protected boolean showCapsLock;
/** @since 2 */ @Styleable protected boolean showRevealButton;
protected Icon capsLockIcon; protected Icon capsLockIcon;
/** @since 2 */ protected Icon revealIcon;
private KeyListener capsLockListener; private KeyListener capsLockListener;
private boolean capsLockIconShared = true; private boolean capsLockIconShared = true;
private JToggleButton revealButton;
public static ComponentUI createUI( JComponent c ) { public static ComponentUI createUI( JComponent c ) {
return new FlatPasswordFieldUI(); return new FlatPasswordFieldUI();
@@ -95,17 +103,33 @@ public class FlatPasswordFieldUI
return "PasswordField"; return "PasswordField";
} }
@Override
public void installUI( JComponent c ) {
super.installUI( c );
installRevealButton();
}
@Override
public void uninstallUI( JComponent c ) {
uninstallRevealButton();
super.uninstallUI( c );
}
@Override @Override
protected void installDefaults() { protected void installDefaults() {
super.installDefaults(); super.installDefaults();
String prefix = getPropertyPrefix(); String prefix = getPropertyPrefix();
Character echoChar = (Character) UIManager.get( prefix + ".echoChar" ); echoChar = (Character) UIManager.get( prefix + ".echoChar" );
if( echoChar != null ) if( echoChar != null )
LookAndFeel.installProperty( getComponent(), "echoChar", echoChar ); LookAndFeel.installProperty( getComponent(), "echoChar", echoChar );
showCapsLock = UIManager.getBoolean( "PasswordField.showCapsLock" ); showCapsLock = UIManager.getBoolean( "PasswordField.showCapsLock" );
showRevealButton = UIManager.getBoolean( "PasswordField.showRevealButton" );
capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" ); capsLockIcon = UIManager.getIcon( "PasswordField.capsLockIcon" );
revealIcon = UIManager.getIcon( "PasswordField.revealIcon" );
capsLockIconShared = true; capsLockIconShared = true;
} }
@@ -114,6 +138,7 @@ public class FlatPasswordFieldUI
super.uninstallDefaults(); super.uninstallDefaults();
capsLockIcon = null; capsLockIcon = null;
revealIcon = null;
} }
@Override @Override
@@ -168,6 +193,18 @@ public class FlatPasswordFieldUI
return "PasswordField"; return "PasswordField";
} }
@Override
protected void applyStyle( Object style ) {
boolean oldShowRevealButton = showRevealButton;
super.applyStyle( style );
if( showRevealButton != oldShowRevealButton ) {
uninstallRevealButton();
installRevealButton();
}
}
/** @since 2 */ /** @since 2 */
@Override @Override
protected Object applyStyleProperty( String key, Object value ) { protected Object applyStyleProperty( String key, Object value ) {
@@ -236,4 +273,39 @@ public class FlatPasswordFieldUI
return FlatUIUtils.isPermanentFocusOwner( c ) && return FlatUIUtils.isPermanentFocusOwner( c ) &&
Toolkit.getDefaultToolkit().getLockingKeyState( KeyEvent.VK_CAPS_LOCK ); 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 };
}
} }

View File

@@ -484,7 +484,10 @@ PasswordField.placeholderForeground = @disabledForeground
PasswordField.iconTextGap = 4 PasswordField.iconTextGap = 4
PasswordField.echoChar = \u2022 PasswordField.echoChar = \u2022
PasswordField.showCapsLock = true PasswordField.showCapsLock = true
PasswordField.showRevealButton = false
PasswordField.capsLockIcon = com.formdev.flatlaf.icons.FlatCapsLockIcon PasswordField.capsLockIcon = com.formdev.flatlaf.icons.FlatCapsLockIcon
PasswordField.revealIcon = com.formdev.flatlaf.icons.FlatRevealIcon
PasswordField.revealIconColor = lazy(Actions.Grey)
#---- Popup ---- #---- Popup ----

View File

@@ -398,7 +398,8 @@ public class TestFlatStyleableInfo
Map<String, Class<?>> expected = new LinkedHashMap<>(); Map<String, Class<?>> expected = new LinkedHashMap<>();
expectedMap( expected, expectedMap( expected,
"showCapsLock", boolean.class "showCapsLock", boolean.class,
"showRevealButton", boolean.class
); );
// FlatPasswordFieldUI extends FlatTextFieldUI // FlatPasswordFieldUI extends FlatTextFieldUI

View File

@@ -551,6 +551,7 @@ public class TestFlatStyling
textField( ui ); textField( ui );
ui.applyStyle( "showCapsLock: true" ); ui.applyStyle( "showCapsLock: true" );
ui.applyStyle( "showRevealButton: true" );
// capsLockIcon // capsLockIcon
ui.applyStyle( "capsLockIconColor: #fff" ); ui.applyStyle( "capsLockIconColor: #fff" );

View File

@@ -38,6 +38,11 @@ class BasicComponentsPanel
BasicComponentsPanel() { BasicComponentsPanel() {
initComponents(); 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 // search history button
JButton searchHistoryButton = new JButton( new FlatSearchWithHistoryIcon( true ) ); JButton searchHistoryButton = new JButton( new FlatSearchWithHistoryIcon( true ) );
searchHistoryButton.setToolTipText( "Search History" ); searchHistoryButton.setToolTipText( "Search History" );
@@ -128,7 +133,7 @@ class BasicComponentsPanel
JFormattedTextField formattedTextField4 = new JFormattedTextField(); JFormattedTextField formattedTextField4 = new JFormattedTextField();
JFormattedTextField formattedTextField5 = new JFormattedTextField(); JFormattedTextField formattedTextField5 = new JFormattedTextField();
JLabel passwordFieldLabel = new JLabel(); JLabel passwordFieldLabel = new JLabel();
JPasswordField passwordField1 = new JPasswordField(); passwordField1 = new JPasswordField();
JPasswordField passwordField2 = new JPasswordField(); JPasswordField passwordField2 = new JPasswordField();
JPasswordField passwordField3 = new JPasswordField(); JPasswordField passwordField3 = new JPasswordField();
JPasswordField passwordField4 = new JPasswordField(); JPasswordField passwordField4 = new JPasswordField();
@@ -913,6 +918,7 @@ class BasicComponentsPanel
} }
// JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables // JFormDesigner - Variables declaration - DO NOT MODIFY //GEN-BEGIN:variables
private JPasswordField passwordField1;
private JTextField compsTextField; private JTextField compsTextField;
private JTextField clearTextField; private JTextField clearTextField;
// JFormDesigner - End of variables declaration //GEN-END:variables // JFormDesigner - End of variables declaration //GEN-END:variables

View File

@@ -375,6 +375,9 @@ new FormModel {
add( new FormComponent( "javax.swing.JPasswordField" ) { add( new FormComponent( "javax.swing.JPasswordField" ) {
name: "passwordField1" name: "passwordField1"
"text": "Editable" "text": "Editable"
auxiliary() {
"JavaCodeGenerator.variableLocal": false
}
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 1 8,growx" "value": "cell 1 8,growx"
} ) } )

View File

@@ -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.inactiveForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI]
PasswordField.margin 2,6,2,6 javax.swing.plaf.InsetsUIResource [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.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.selectionBackground #4b6eaf HSL 219 40 49 javax.swing.plaf.ColorUIResource [UI]
PasswordField.selectionForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionForeground #bbbbbb HSL 0 0 73 javax.swing.plaf.ColorUIResource [UI]
PasswordField.showCapsLock true PasswordField.showCapsLock true
PasswordField.showRevealButton false
PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI

View File

@@ -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.inactiveForeground #8c8c8c HSL 0 0 55 javax.swing.plaf.ColorUIResource [UI]
PasswordField.margin 2,6,2,6 javax.swing.plaf.InsetsUIResource [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.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.selectionBackground #2675bf HSL 209 67 45 javax.swing.plaf.ColorUIResource [UI]
PasswordField.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionForeground #ffffff HSL 0 0 100 javax.swing.plaf.ColorUIResource [UI]
PasswordField.showCapsLock true PasswordField.showCapsLock true
PasswordField.showRevealButton false
PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI

View File

@@ -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.inactiveForeground #000088 HSL 240 100 27 javax.swing.plaf.ColorUIResource [UI]
PasswordField.margin 2,6,2,6 javax.swing.plaf.InsetsUIResource [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.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.selectionBackground #00aa00 HSL 120 100 33 javax.swing.plaf.ColorUIResource [UI]
PasswordField.selectionForeground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI] PasswordField.selectionForeground #ffff00 HSL 60 100 50 javax.swing.plaf.ColorUIResource [UI]
PasswordField.showCapsLock true PasswordField.showCapsLock true
PasswordField.showRevealButton false
PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI PasswordFieldUI com.formdev.flatlaf.ui.FlatPasswordFieldUI

View File

@@ -126,6 +126,14 @@ public class FlatTextComponentsTest
showClearButtonCheckBox.isSelected() ); 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 ) { private void putTextFieldClientProperty( String key, Object value ) {
for( Component c : getComponents() ) { for( Component c : getComponents() ) {
if( c instanceof JTextField ) if( c instanceof JTextField )
@@ -174,6 +182,7 @@ public class FlatTextComponentsTest
leadingComponentVisibleCheckBox = new JCheckBox(); leadingComponentVisibleCheckBox = new JCheckBox();
trailingComponentVisibleCheckBox = new JCheckBox(); trailingComponentVisibleCheckBox = new JCheckBox();
showClearButtonCheckBox = new JCheckBox(); showClearButtonCheckBox = new JCheckBox();
showRevealButtonCheckBox = new JCheckBox();
JLabel passwordFieldLabel = new JLabel(); JLabel passwordFieldLabel = new JLabel();
JPasswordField passwordField1 = new JPasswordField(); JPasswordField passwordField1 = new JPasswordField();
JPasswordField passwordField3 = new JPasswordField(); JPasswordField passwordField3 = new JPasswordField();
@@ -326,6 +335,7 @@ public class FlatTextComponentsTest
"[]" + "[]" +
"[]0" + "[]0" +
"[]" + "[]" +
"[]" +
"[]")); "[]"));
//---- button1 ---- //---- button1 ----
@@ -417,6 +427,12 @@ public class FlatTextComponentsTest
showClearButtonCheckBox.setName("showClearButtonCheckBox"); showClearButtonCheckBox.setName("showClearButtonCheckBox");
showClearButtonCheckBox.addActionListener(e -> showClearButton()); showClearButtonCheckBox.addActionListener(e -> showClearButton());
panel1.add(showClearButtonCheckBox, "cell 0 11 2 1,alignx left,growx 0"); 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"); add(panel1, "cell 4 0 1 10,aligny top,growy 0");
@@ -733,6 +749,7 @@ public class FlatTextComponentsTest
private JCheckBox leadingComponentVisibleCheckBox; private JCheckBox leadingComponentVisibleCheckBox;
private JCheckBox trailingComponentVisibleCheckBox; private JCheckBox trailingComponentVisibleCheckBox;
private JCheckBox showClearButtonCheckBox; private JCheckBox showClearButtonCheckBox;
private JCheckBox showRevealButtonCheckBox;
private JTextField textField; private JTextField textField;
private JCheckBox dragEnabledCheckBox; private JCheckBox dragEnabledCheckBox;
private JTextArea textArea; private JTextArea textArea;

View File

@@ -77,7 +77,7 @@ new FormModel {
add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) { add( new FormContainer( "javax.swing.JPanel", new FormLayoutManager( class net.miginfocom.swing.MigLayout ) {
"$layoutConstraints": "hidemode 3" "$layoutConstraints": "hidemode 3"
"$columnConstraints": "[fill][fill]" "$columnConstraints": "[fill][fill]"
"$rowConstraints": "[][][][][][]0[][]0[][]0[][]" "$rowConstraints": "[][][][][][]0[][]0[][]0[][][]"
} ) { } ) {
name: "panel1" name: "panel1"
"border": new javax.swing.border.TitledBorder( "Control" ) "border": new javax.swing.border.TitledBorder( "Control" )
@@ -220,6 +220,16 @@ new FormModel {
}, new FormLayoutConstraints( class net.miginfocom.layout.CC ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 0 11 2 1,alignx left,growx 0" "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 ) { }, new FormLayoutConstraints( class net.miginfocom.layout.CC ) {
"value": "cell 4 0 1 10,aligny top,growy 0" "value": "cell 4 0 1 10,aligny top,growy 0"
} ) } )

View File

@@ -557,9 +557,12 @@ PasswordField.inactiveBackground
PasswordField.inactiveForeground PasswordField.inactiveForeground
PasswordField.margin PasswordField.margin
PasswordField.placeholderForeground PasswordField.placeholderForeground
PasswordField.revealIcon
PasswordField.revealIconColor
PasswordField.selectionBackground PasswordField.selectionBackground
PasswordField.selectionForeground PasswordField.selectionForeground
PasswordField.showCapsLock PasswordField.showCapsLock
PasswordField.showRevealButton
PasswordFieldUI PasswordFieldUI
Popup.dropShadowColor Popup.dropShadowColor
Popup.dropShadowInsets Popup.dropShadowInsets