|
|
@@ -0,0 +1,114 @@
|
|
|
+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 org.leumasjaffe.event.AnyActionDocumentListener;
|
|
|
+
|
|
|
+import lombok.experimental.FieldDefaults;
|
|
|
+import lombok.AccessLevel;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 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 formatted 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 ObservableFmtController<S extends JFormattedTextField, T extends Observable, V> extends ObservableListener<S, T> {
|
|
|
+ BiFunction<V, T, Boolean> func;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Construct an ObservableController that modifies its model whenever the linked
|
|
|
+ * JFormattedTextField 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
|
|
|
+ */
|
|
|
+ public ObservableFmtController(final S comp, final BiFunction<V, T, Boolean> func,
|
|
|
+ final BiConsumer<? super S, ? super T> update) {
|
|
|
+ super(comp, update);
|
|
|
+ this.func = func;
|
|
|
+ AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Construct an ObservableController that modifies its model whenever the linked
|
|
|
+ * JFormattedTextField 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 ObservableFmtController(final S comp, final BiFunction<V, 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( ) );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Construct an ObservableController that ties together a JTextComponent 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 ObservableFmtController(final S comp, final Function<T, V> get,
|
|
|
+ final BiConsumer<T, V> set) {
|
|
|
+ super(comp, (c, u) -> {
|
|
|
+ if (c.getValue().equals(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;
|
|
|
+ };
|
|
|
+ AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Construct an ObservableController that ties together a JTextComponent with
|
|
|
+ * the member of an observable object such that updates flow between the model
|
|
|
+ * and the JFormattedTextField, 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 ObservableFmtController(final S comp, final Function<T, V> get,
|
|
|
+ final BiConsumer<T, V> set, final Consumer<T> onEmpty) {
|
|
|
+ super(comp, (c, u) -> c.setValue(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( ) );
|
|
|
+ }
|
|
|
+
|
|
|
+ @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());
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|