ObservableController.java 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package org.leumasjaffe.observer;
  2. import java.util.Objects;
  3. import java.util.function.BiConsumer;
  4. import java.util.function.BiFunction;
  5. import java.util.function.Consumer;
  6. import java.util.function.Function;
  7. import javax.swing.JFormattedTextField;
  8. import javax.swing.text.JTextComponent;
  9. import org.leumasjaffe.event.AnyActionDocumentListener;
  10. import lombok.experimental.UtilityClass;
  11. import lombok.NonNull;
  12. /**
  13. * An ObservableListener object that also provides an update link in the other direction.
  14. * The normal Listener updates some (UI) component when the model object updates. This
  15. * object adds the feature to update the model from an update to UI component.
  16. *
  17. * This allows you to have a text input field attached to your model (like with a JavaBean),
  18. * that also fires off notifications to other interested listeners when you edit the text.
  19. */
  20. @UtilityClass
  21. public class ObservableController {
  22. private static class TextFieldController<S extends JTextComponent, T extends Observable> extends ObservableListener<S, T> {
  23. BiFunction<String, T, Boolean> func;
  24. public TextFieldController(@NonNull final S comp,
  25. @NonNull final BiFunction<String, T, Boolean> func,
  26. @NonNull final BiConsumer<? super S, ? super T> update,
  27. final Consumer<T> onEmpty) {
  28. super(comp, update);
  29. this.func = func;
  30. if (onEmpty == null) {
  31. AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
  32. } else {
  33. AnyActionDocumentListener.emptyOrText( comp,
  34. e -> onEmpty.accept( impl.getModel() ),
  35. evt -> accept( ) );
  36. }
  37. }
  38. public TextFieldController(@NonNull final S comp,
  39. @NonNull final Function<T, String> get,
  40. @NonNull final BiConsumer<T, String> set,
  41. final Consumer<T> onEmpty) {
  42. this(comp, (t, u) -> {
  43. if (t.equals(get.apply(u))) return false;
  44. set.accept(u, t);
  45. return true;
  46. }, (c, u) -> {
  47. if (c.getText().equals(get.apply(u))) return;
  48. c.setText(get.apply(u).toString());
  49. }, onEmpty);
  50. }
  51. private boolean update() {
  52. return func.apply( impl.getComponent().getText( ), impl.getModel() );
  53. }
  54. private void accept() {
  55. Objects.requireNonNull( impl.getModel() );
  56. if ( update( ) ) {
  57. impl.notifySubscribers(impl.getModel());
  58. }
  59. }
  60. }
  61. private static class FormattedTextFieldController<S extends JFormattedTextField, T extends Observable, V> extends ObservableListener<S, T> {
  62. BiFunction<V, T, Boolean> func;
  63. public FormattedTextFieldController(@NonNull final S comp,
  64. @NonNull final Function<T, V> get,
  65. @NonNull final BiConsumer<T, V> set,
  66. V onEmpty) {
  67. super(comp, (c, u) -> {
  68. if (Objects.equals(c.getValue(), get.apply(u))) return;
  69. c.setValue(get.apply(u));
  70. });
  71. this.func = (t, u) -> {
  72. if (t.equals(get.apply(u))) return false;
  73. set.accept(u, t);
  74. return true;
  75. };
  76. if (onEmpty == null) {
  77. AnyActionDocumentListener.skipEmpty(comp, evt -> accept( ) );
  78. } else {
  79. AnyActionDocumentListener.emptyOrText( comp,
  80. e -> set.accept( impl.getModel(), onEmpty ),
  81. evt -> accept( ) );
  82. }
  83. }
  84. @SuppressWarnings("unchecked")
  85. private boolean update() {
  86. return func.apply( (V) impl.getComponent().getValue( ), impl.getModel() );
  87. }
  88. private void accept() {
  89. Objects.requireNonNull( impl.getModel() );
  90. if ( update( ) ) {
  91. impl.notifySubscribers(impl.getModel());
  92. }
  93. }
  94. }
  95. /**
  96. * Construct an ObservableController that modifies its model whenever the linked
  97. * JTextComponent is updated. Ignores changes when the text document is empty.
  98. * @param comp A GUI object that is to be coupled with the model
  99. * @param updateModel An update function to modify the model from text content
  100. * @param updateComp The standard callback to set the component when the model changes elsewhere
  101. */
  102. public <S extends JTextComponent, T extends Observable> ObservableListener<S, T> from(
  103. @NonNull final S comp, @NonNull final BiFunction<String, T, Boolean> updateModel,
  104. @NonNull final BiConsumer<? super S, ? super T> updateComp) {
  105. return new TextFieldController<>(comp, updateModel, updateComp, null);
  106. }
  107. /**
  108. * Construct an ObservableController that modifies its model whenever the linked
  109. * JTextComponent is updated, including deleting all of the text.
  110. * @param comp A GUI object that is to be coupled with the model
  111. * @param func An update function to modify the model from text content
  112. * @param update The standard callback to set the component when the model changes elsewhere
  113. * @param onEmpty A special handler to set model value when we delete the content of the
  114. * component.
  115. */
  116. public <S extends JTextComponent, T extends Observable> ObservableListener<S, T> from(
  117. @NonNull final S comp, @NonNull final BiFunction<String, T, Boolean> func,
  118. @NonNull final BiConsumer<? super S, ? super T> update,
  119. Consumer<T> onEmpty) {
  120. return new TextFieldController<>(comp, func, update, onEmpty);
  121. }
  122. /**
  123. * Construct an ObservableController that ties together a JTextComponent with
  124. * the member of an observable object such that updates flow between the model
  125. * and the JTextComponent. Ignores changes when the text document is empty.
  126. * @param comp The component being watched
  127. * @param get A model getter function to be called on the observed object
  128. * @param set A model setter function to be called on the observed object
  129. */
  130. public <S extends JTextComponent, T extends Observable, V> ObservableListener<S, T> from(
  131. @NonNull final S comp, @NonNull final Function<T, String> get,
  132. @NonNull final BiConsumer<T, String> set) {
  133. return from(comp, get, set, null);
  134. }
  135. /**
  136. * Construct an ObservableController that ties together a JTextComponent with
  137. * the member of an observable object such that updates flow between the model
  138. * and the JTextComponent, including deleting all of the text.
  139. * @param comp The component being watched
  140. * @param get A model getter function to be called on the observed object
  141. * @param set A model setter function to be called on the observed object
  142. */
  143. public <S extends JTextComponent, T extends Observable> ObservableListener<S, T> from(
  144. @NonNull final S comp, @NonNull final Function<T, String> get,
  145. @NonNull final BiConsumer<T, String> set, Consumer<T> onEmpty) {
  146. return new TextFieldController<S, T>(comp, get, set, onEmpty);
  147. }
  148. /**
  149. * Construct an ObservableController that ties together a JFormattedTextField with
  150. * the member of an observable object such that updates flow between the model
  151. * and the JFormattedTextField. Ignores changes when the text document is empty.
  152. * @param comp The component being watched
  153. * @param get A model getter function to be called on the observed object
  154. * @param set A model setter function to be called on the observed object
  155. */
  156. public <S extends JFormattedTextField, T extends Observable, V> ObservableListener<S, T> from(
  157. @NonNull final S comp, @NonNull final Function<T, V> get, @NonNull final BiConsumer<T, V> set) {
  158. return new FormattedTextFieldController<>(comp, get, set, null);
  159. }
  160. }