| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180 |
- package org.leumasjaffe.observer;
- import java.util.Objects;
- import java.util.function.BiConsumer;
- 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.UtilityClass;
- import lombok.NonNull;
- /**
- * An ObservableListener object that also provides an update link in the other direction.
- * The normal Listener updates some (UI) component when the model object updates. This
- * object adds the feature to update the model from an update to UI component.
- *
- * 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.
- */
- @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 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 <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);
- }
-
- /**
- * Construct an ObservableController that modifies its model whenever the linked
- * JTextComponent is updated, including deleting all of the text.
- * @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 onEmpty A special handler to set model value when we delete the content of the
- * component.
- */
- 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);
- }
-
- /**
- * Construct an ObservableController that ties together a JTextComponent with
- * the member of an observable object such that updates flow between the model
- * and the JTextComponent. 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 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);
- }
-
- /**
- * Construct an ObservableController that ties together a JTextComponent with
- * the member of an observable object such that updates flow between the model
- * and the JTextComponent, including deleting all of the text.
- * @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 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);
- }
-
- /**
- * 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);
- }
- }
|