diff --git a/CHANGELOG.md b/CHANGELOG.md index 210db8f..1e1cd12 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - (Base) New `DeckPane` component with swipe and slide transition support. - (Base) New `MaskTextField` (and `MaskTextFormatter`) component to support masked text input. +- (Base) New `PasswordTextField` component to simplify `PasswordTextFormatter` usage. - (CSS) 🚀 New MacOS-like Cupertino theme in light and dark variants. - (CSS) 🚀 New [Dracula](https://ui.draculatheme.com/) theme. - (CSS) New `TabPane` style. There are three styles supported: default, floating and classic (new one). diff --git a/base/src/main/java/atlantafx/base/controls/PasswordTextField.java b/base/src/main/java/atlantafx/base/controls/PasswordTextField.java new file mode 100644 index 0000000..3fc9b26 --- /dev/null +++ b/base/src/main/java/atlantafx/base/controls/PasswordTextField.java @@ -0,0 +1,87 @@ +/* + * SPDX-License-Identifier: MIT + * + * Copyright (c) 2013, 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package atlantafx.base.controls; + +import atlantafx.base.util.PasswordTextFormatter; +import javafx.beans.NamedArg; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.ReadOnlyStringProperty; + +/** + * This is a convenience wrapper for instantiating a {@link CustomTextField} + * with {@code PasswordTextFormatter}. For additional info refer to the {@link PasswordTextFormatter} + * docs. + */ +public class PasswordTextField extends CustomTextField { + + protected final PasswordTextFormatter formatter; + + public PasswordTextField(@NamedArg("text") String text) { + this(text, PasswordTextFormatter.BULLET); + } + + public PasswordTextField(@NamedArg("text") String text, @NamedArg("bullet") char bullet) { + super(text); + this.formatter = PasswordTextFormatter.create(this, bullet); + } + + /** + * See {@link PasswordTextFormatter#passwordProperty()}. + */ + public ReadOnlyStringProperty passwordProperty() { + return formatter.passwordProperty(); + } + + /** + * See {@link PasswordTextFormatter#getPassword()}. + */ + public String getPassword() { + return formatter.getPassword(); + } + + /** + * See {@link PasswordTextFormatter#revealPasswordProperty()}. + */ + public BooleanProperty revealPasswordProperty() { + return formatter.revealPasswordProperty(); + } + + /** + * See {@link PasswordTextFormatter#isRevealPassword()}. + */ + public boolean isRevealPassword() { + return formatter.isRevealPassword(); + } + + /** + * See {@link PasswordTextFormatter#setRevealPassword(boolean)}. + */ + public void setRevealPassword(boolean reveal) { + formatter.setRevealPassword(reveal); + } +} diff --git a/base/src/main/java/atlantafx/base/util/PasswordTextFormatter.java b/base/src/main/java/atlantafx/base/util/PasswordTextFormatter.java index b63f027..f2a99b5 100644 --- a/base/src/main/java/atlantafx/base/util/PasswordTextFormatter.java +++ b/base/src/main/java/atlantafx/base/util/PasswordTextFormatter.java @@ -22,7 +22,7 @@ public class PasswordTextFormatter extends TextFormatter { protected PasswordTextFormatter(StringConverter valueConverter, UnaryOperator filter, - TextField textField, + TextField field, char bullet) { super(valueConverter, null, filter); @@ -32,13 +32,13 @@ public class PasswordTextFormatter extends TextFormatter { if (filter == null) { throw new NullPointerException("UnaryOperator cannot be null!"); } - if (textField == null) { + if (field == null) { throw new NullPointerException("TextField cannot be null!"); } PasswordFilter passwordFilter = (PasswordFilter) getFilter(); passwordFilter.setBullet(bullet); - passwordFilter.setInitialText(textField.getText()); + passwordFilter.setInitialText(field.getText()); revealPasswordProperty().addListener((obs, old, val) -> { if (val == null) { @@ -48,41 +48,68 @@ public class PasswordTextFormatter extends TextFormatter { // Force text field update, because converter is only called on focus events by default. // Also, reset caret first, because otherwise its position won't be correct due to // #javafx-bug (https://bugs.openjdk.org/browse/JDK-8248914). - textField.positionCaret(0); - textField.commitValue(); + field.positionCaret(0); + field.commitValue(); }); // force text field update on scene show - Platform.runLater(textField::commitValue); + Platform.runLater(field::commitValue); } + /** + * Always returns the unmasked password text regardless of + * the {@link #revealPasswordProperty} state. + */ public ReadOnlyStringProperty passwordProperty() { return ((PasswordFilter) getFilter()).password.getReadOnlyProperty(); } + /** + * See {@link #passwordProperty()}. + */ public String getPassword() { return passwordProperty().get(); } + /** + * Specifies whether the unmasked password text is revealed or not. + */ public BooleanProperty revealPasswordProperty() { return ((PasswordFilter) getFilter()).revealPassword; } + /** + * See {@link #revealPasswordProperty}. + */ public boolean isRevealPassword() { return revealPasswordProperty().get(); } + /** + * See {@link #revealPasswordProperty}. + */ public void setRevealPassword(boolean reveal) { revealPasswordProperty().set(reveal); } - // Life would be easier if TextFormatter had the default constructor. - public static PasswordTextFormatter create(TextField textField, char bullet) { + /** + * Creates a new password text formatter with the provided mask character and + * applies itself to the specified text field. + */ + public static PasswordTextFormatter create(TextField field, char bullet) { var filter = new PasswordFilter(); var converter = new PasswordStringConverter(filter); - return new PasswordTextFormatter(converter, filter, textField, bullet); + + var formatter = new PasswordTextFormatter(converter, filter, field, bullet); + field.setTextFormatter(formatter); + + return formatter; } + /** + * Creates a new password text formatter with the default mask character and + * applies itself to the specified text field. + */ public static PasswordTextFormatter create(TextField textField) { return create(textField, BULLET); } diff --git a/base/src/test/java/atlantafx/base/util/PasswordTextFormatterTest.java b/base/src/test/java/atlantafx/base/util/PasswordTextFormatterTest.java index 9aeff8d..9876e29 100644 --- a/base/src/test/java/atlantafx/base/util/PasswordTextFormatterTest.java +++ b/base/src/test/java/atlantafx/base/util/PasswordTextFormatterTest.java @@ -15,7 +15,6 @@ public class PasswordTextFormatterTest { public void testTextIsMaskedByDefault() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); assertEquals("+".repeat(3), field.getText()); @@ -26,7 +25,6 @@ public class PasswordTextFormatterTest { public void testTextCanBeRevealed() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); fmt.setRevealPassword(true); @@ -38,7 +36,6 @@ public class PasswordTextFormatterTest { public void testPrependText() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); field.insertText(0, "456"); @@ -50,7 +47,6 @@ public class PasswordTextFormatterTest { public void testAppendText() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); field.appendText("456"); @@ -62,7 +58,6 @@ public class PasswordTextFormatterTest { public void testInsertText() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); field.insertText(2, "456"); @@ -74,7 +69,6 @@ public class PasswordTextFormatterTest { public void testNoInitialText() { var field = new TextField(null); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.appendText("456"); assertEquals("+".repeat(3), field.getText()); @@ -85,7 +79,6 @@ public class PasswordTextFormatterTest { public void testDeleteSomeText() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); field.deleteText(0, 2); @@ -97,7 +90,6 @@ public class PasswordTextFormatterTest { public void testDeleteAllText() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); field.deleteText(0, field.getText().length()); @@ -109,7 +101,6 @@ public class PasswordTextFormatterTest { public void testSetTextToNull() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); field.setText(null); @@ -121,7 +112,6 @@ public class PasswordTextFormatterTest { public void testReplaceSelection() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); field.selectRange(1, field.getText().length()); @@ -134,7 +124,6 @@ public class PasswordTextFormatterTest { public void testReplaceAll() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123"); field.selectRange(0, field.getText().length()); @@ -147,7 +136,6 @@ public class PasswordTextFormatterTest { public void testCanContainBullets() { var field = new TextField(); var fmt = PasswordTextFormatter.create(field, '+'); - field.setTextFormatter(fmt); field.setText("123++"); assertEquals("+".repeat(5), field.getText()); diff --git a/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java b/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java index 1d33086..44a5376 100644 --- a/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java +++ b/sampler/src/main/java/atlantafx/sampler/layout/Sidebar.java @@ -236,7 +236,7 @@ class Sidebar extends StackPane { navLink(CheckBoxPage.NAME, CheckBoxPage.class), navLink(ColorPickerPage.NAME, ColorPickerPage.class), navLink(ComboBoxPage.NAME, ComboBoxPage.class, "ChoiceBox"), - navLink(CustomTextFieldPage.NAME, CustomTextFieldPage.class, "MaskTextField"), + navLink(CustomTextFieldPage.NAME, CustomTextFieldPage.class, "MaskTextField", "PasswordTextField"), navLink(DatePickerPage.NAME, DatePickerPage.class), navLink(DialogPage.NAME, DialogPage.class), navLink(HtmlEditorPage.NAME, HtmlEditorPage.class), diff --git a/sampler/src/main/java/atlantafx/sampler/page/components/CustomTextFieldPage.java b/sampler/src/main/java/atlantafx/sampler/page/components/CustomTextFieldPage.java index fef3a6e..9252bf8 100644 --- a/sampler/src/main/java/atlantafx/sampler/page/components/CustomTextFieldPage.java +++ b/sampler/src/main/java/atlantafx/sampler/page/components/CustomTextFieldPage.java @@ -8,7 +8,7 @@ import static atlantafx.sampler.page.SampleBlock.BLOCK_HGAP; import atlantafx.base.controls.CustomTextField; import atlantafx.base.controls.MaskTextField; -import atlantafx.base.util.PasswordTextFormatter; +import atlantafx.base.controls.PasswordTextField; import atlantafx.sampler.page.AbstractPage; import atlantafx.sampler.page.SampleBlock; import java.time.LocalTime; @@ -89,20 +89,17 @@ public class CustomTextFieldPage extends AbstractPage { } private SampleBlock passwordSample() { - var tf = new CustomTextField("qwerty"); + var tf = new PasswordTextField("qwerty"); tf.setPrefWidth(PREF_WIDTH); - var passwordFormatter = PasswordTextFormatter.create(tf); - tf.setTextFormatter(passwordFormatter); - var icon = new FontIcon(Feather.EYE_OFF); icon.setCursor(Cursor.HAND); icon.setOnMouseClicked(e -> { - if (passwordFormatter.revealPasswordProperty().get()) { - passwordFormatter.revealPasswordProperty().set(false); + if (tf.revealPasswordProperty().get()) { + tf.revealPasswordProperty().set(false); icon.setIconCode(Feather.EYE_OFF); } else { - passwordFormatter.revealPasswordProperty().set(true); + tf.revealPasswordProperty().set(true); icon.setIconCode(Feather.EYE); } });