|
|
@@ -6,12 +6,13 @@ import java.util.function.BiFunction;
|
|
|
import java.util.function.Consumer;
|
|
|
import java.util.function.Function;
|
|
|
|
|
|
+import javax.swing.JFormattedTextField;
|
|
|
import javax.swing.text.JTextComponent;
|
|
|
|
|
|
import org.leumasjaffe.event.AnyActionDocumentListener;
|
|
|
|
|
|
-import lombok.experimental.FieldDefaults;
|
|
|
-import lombok.AccessLevel;
|
|
|
+import lombok.experimental.UtilityClass;
|
|
|
+import lombok.NonNull;
|
|
|
|
|
|
/**
|
|
|
* An ObservableListener object that also provides an update link in the other direction.
|
|
|
@@ -21,22 +22,103 @@ import lombok.AccessLevel;
|
|
|
* This allows you to have a text input field attached to your model (like with a JavaBean),
|
|
|
* that also fires off notifications to other interested listeners when you edit the text.
|
|
|
*/
|
|
|
-@FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
|
|
|
-public class ObservableController<S extends JTextComponent, T extends Observable> extends ObservableListener<S, T> {
|
|
|
- BiFunction<String, T, Boolean> func;
|
|
|
+@UtilityClass
|
|
|
+public class ObservableController {
|
|
|
+
|
|
|
+ private static class TextFieldController<S extends JTextComponent, T extends Observable> extends ObservableListener<S, T> {
|
|
|
+ BiFunction<String, T, Boolean> func;
|
|
|
+
|
|
|
+ public TextFieldController(@NonNull final S comp,
|
|
|
+ @NonNull final BiFunction<String, T, Boolean> func,
|
|
|
+ @NonNull final BiConsumer<? super S, ? super T> update,
|
|
|
+ final Consumer<T> onEmpty) {
|
|
|
+ super(comp, update);
|
|
|
+ this.func = func;
|
|
|
+ if (onEmpty == null) {
|
|
|
+ AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
|
|
|
+ } else {
|
|
|
+ AnyActionDocumentListener.emptyOrText( comp,
|
|
|
+ e -> onEmpty.accept( impl.getModel() ),
|
|
|
+ evt -> accept( ) );
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public TextFieldController(@NonNull final S comp,
|
|
|
+ @NonNull final Function<T, String> get,
|
|
|
+ @NonNull final BiConsumer<T, String> set,
|
|
|
+ final Consumer<T> onEmpty) {
|
|
|
+ this(comp, (t, u) -> {
|
|
|
+ if (t.equals(get.apply(u))) return false;
|
|
|
+ set.accept(u, t);
|
|
|
+ return true;
|
|
|
+ }, (c, u) -> {
|
|
|
+ if (c.getText().equals(get.apply(u))) return;
|
|
|
+ c.setText(get.apply(u).toString());
|
|
|
+ }, onEmpty);
|
|
|
+ }
|
|
|
+
|
|
|
+ private boolean update() {
|
|
|
+ return func.apply( impl.getComponent().getText( ), impl.getModel() );
|
|
|
+ }
|
|
|
+
|
|
|
+ private void accept() {
|
|
|
+ Objects.requireNonNull( impl.getModel() );
|
|
|
+ if ( update( ) ) {
|
|
|
+ impl.notifySubscribers(impl.getModel());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class FormattedTextFieldController<S extends JFormattedTextField, T extends Observable, V> extends ObservableListener<S, T> {
|
|
|
+ BiFunction<V, T, Boolean> func;
|
|
|
+
|
|
|
+ public FormattedTextFieldController(@NonNull final S comp,
|
|
|
+ @NonNull final Function<T, V> get,
|
|
|
+ @NonNull final BiConsumer<T, V> set,
|
|
|
+ V onEmpty) {
|
|
|
+ super(comp, (c, u) -> {
|
|
|
+ if (Objects.equals(c.getValue(), get.apply(u))) return;
|
|
|
+ c.setValue(get.apply(u));
|
|
|
+ });
|
|
|
+ this.func = (t, u) -> {
|
|
|
+ if (t.equals(get.apply(u))) return false;
|
|
|
+ set.accept(u, t);
|
|
|
+ return true;
|
|
|
+ };
|
|
|
+
|
|
|
+ if (onEmpty == null) {
|
|
|
+ AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
|
|
|
+ } else {
|
|
|
+ AnyActionDocumentListener.emptyOrText( comp,
|
|
|
+ e -> set.accept( impl.getModel(), onEmpty ),
|
|
|
+ evt -> accept( ) );
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private boolean update() {
|
|
|
+ return func.apply( (V) impl.getComponent().getValue( ), impl.getModel() );
|
|
|
+ }
|
|
|
+
|
|
|
+ private void accept() {
|
|
|
+ Objects.requireNonNull( impl.getModel() );
|
|
|
+ if ( update( ) ) {
|
|
|
+ impl.notifySubscribers(impl.getModel());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Construct an ObservableController that modifies its model whenever the linked
|
|
|
* JTextComponent is updated. Ignores changes when the text document is empty.
|
|
|
* @param comp A GUI object that is to be coupled with the model
|
|
|
- * @param func An update function to modify the model from text content
|
|
|
- * @param update The standard callback to set the component when the model changes elsewhere
|
|
|
+ * @param updateModel An update function to modify the model from text content
|
|
|
+ * @param updateComp The standard callback to set the component when the model changes elsewhere
|
|
|
*/
|
|
|
- public ObservableController(final S comp, final BiFunction<String, T, Boolean> func,
|
|
|
- final BiConsumer<? super S, ? super T> update) {
|
|
|
- super(comp, update);
|
|
|
- this.func = func;
|
|
|
- AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
|
|
|
+ public <S extends JTextComponent, T extends Observable> ObservableListener<S, T> from(
|
|
|
+ @NonNull final S comp, @NonNull final BiFunction<String, T, Boolean> updateModel,
|
|
|
+ @NonNull final BiConsumer<? super S, ? super T> updateComp) {
|
|
|
+ return new TextFieldController<>(comp, updateModel, updateComp, null);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -48,13 +130,11 @@ public class ObservableController<S extends JTextComponent, T extends Observable
|
|
|
* @param onEmpty A special handler to set model value when we delete the content of the
|
|
|
* component.
|
|
|
*/
|
|
|
- public ObservableController(final S comp, final BiFunction<String, T, Boolean> func,
|
|
|
- final BiConsumer<? super S, ? super T> update, final Consumer<T> onEmpty) {
|
|
|
- super(comp, update);
|
|
|
- this.func = func;
|
|
|
- AnyActionDocumentListener.emptyOrText( comp,
|
|
|
- e -> onEmpty.accept( impl.getModel() ),
|
|
|
- evt -> accept( ) );
|
|
|
+ public <S extends JTextComponent, T extends Observable> ObservableListener<S, T> from(
|
|
|
+ @NonNull final S comp, @NonNull final BiFunction<String, T, Boolean> func,
|
|
|
+ @NonNull final BiConsumer<? super S, ? super T> update,
|
|
|
+ Consumer<T> onEmpty) {
|
|
|
+ return new TextFieldController<>(comp, func, update, onEmpty);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -65,18 +145,10 @@ public class ObservableController<S extends JTextComponent, T extends Observable
|
|
|
* @param get A model getter function to be called on the observed object
|
|
|
* @param set A model setter function to be called on the observed object
|
|
|
*/
|
|
|
- public ObservableController(final S comp, final Function<T, String> get,
|
|
|
- final BiConsumer<T, String> set) {
|
|
|
- super(comp, (c, u) -> {
|
|
|
- if (c.getText().equals(get.apply(u))) return;
|
|
|
- c.setText(get.apply(u));
|
|
|
- });
|
|
|
- this.func = (t, u) -> {
|
|
|
- if (t.equals(get.apply(u))) return false;
|
|
|
- set.accept(u, t);
|
|
|
- return true;
|
|
|
- };
|
|
|
- AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
|
|
|
+ public <S extends JTextComponent, T extends Observable, V> ObservableListener<S, T> from(
|
|
|
+ @NonNull final S comp, @NonNull final Function<T, String> get,
|
|
|
+ @NonNull final BiConsumer<T, String> set) {
|
|
|
+ return from(comp, get, set, null);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
@@ -87,27 +159,22 @@ public class ObservableController<S extends JTextComponent, T extends Observable
|
|
|
* @param get A model getter function to be called on the observed object
|
|
|
* @param set A model setter function to be called on the observed object
|
|
|
*/
|
|
|
- public ObservableController(final S comp, final Function<T, String> get,
|
|
|
- final BiConsumer<T, String> set, final Consumer<T> onEmpty) {
|
|
|
- super(comp, (c, u) -> c.setText(get.apply(u)));
|
|
|
- this.func = (t, u) -> {
|
|
|
- if (t.equals(get.apply(u))) return false;
|
|
|
- set.accept(u, t);
|
|
|
- return true;
|
|
|
- };
|
|
|
- AnyActionDocumentListener.emptyOrText( comp,
|
|
|
- e -> onEmpty.accept( impl.getModel() ),
|
|
|
- evt -> accept( ) );
|
|
|
+ public <S extends JTextComponent, T extends Observable> ObservableListener<S, T> from(
|
|
|
+ @NonNull final S comp, @NonNull final Function<T, String> get,
|
|
|
+ @NonNull final BiConsumer<T, String> set, Consumer<T> onEmpty) {
|
|
|
+ return new TextFieldController<S, T>(comp, get, set, onEmpty);
|
|
|
}
|
|
|
-
|
|
|
- private boolean update() {
|
|
|
- return func.apply( impl.getComponent().getText( ), impl.getModel() );
|
|
|
- }
|
|
|
-
|
|
|
- private void accept() {
|
|
|
- Objects.requireNonNull( impl.getModel() );
|
|
|
- if ( update( ) ) {
|
|
|
- impl.notifySubscribers(impl.getModel());
|
|
|
- }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Construct an ObservableController that ties together a JFormattedTextField with
|
|
|
+ * the member of an observable object such that updates flow between the model
|
|
|
+ * and the JFormattedTextField. Ignores changes when the text document is empty.
|
|
|
+ * @param comp The component being watched
|
|
|
+ * @param get A model getter function to be called on the observed object
|
|
|
+ * @param set A model setter function to be called on the observed object
|
|
|
+ */
|
|
|
+ public <S extends JFormattedTextField, T extends Observable, V> ObservableListener<S, T> from(
|
|
|
+ @NonNull final S comp, @NonNull final Function<T, V> get, @NonNull final BiConsumer<T, V> set) {
|
|
|
+ return new FormattedTextFieldController<>(comp, get, set, null);
|
|
|
}
|
|
|
}
|