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)
- 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

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.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 };
}
}

View File

@@ -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 ----

View File

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

View File

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

View File

@@ -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

View File

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

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.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

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.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

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.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

View File

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

View File

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

View File

@@ -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