BetterAutoGrowPanel.java 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package org.leumasjaffe.recipe.view;
  2. import java.awt.Component;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. import java.util.function.Consumer;
  6. import java.util.function.Function;
  7. import java.util.function.Supplier;
  8. import javax.swing.JPanel;
  9. import javax.swing.event.DocumentEvent;
  10. import javax.swing.event.DocumentListener;
  11. import org.jdesktop.swingx.VerticalLayout;
  12. import lombok.AccessLevel;
  13. import lombok.AllArgsConstructor;
  14. import lombok.NonNull;
  15. import lombok.RequiredArgsConstructor;
  16. import lombok.experimental.Delegate;
  17. import lombok.experimental.FieldDefaults;
  18. import lombok.experimental.NonFinal;
  19. @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
  20. public class BetterAutoGrowPanel<C extends Component & BetterAutoGrowPanel.ChildComponent, T> extends JPanel {
  21. private static interface SetGap { void setGap(int gap); }
  22. public static interface ChildComponent {
  23. void addGrowShrinkListener(DocumentListener dl);
  24. void removeGrowShrinkListener(DocumentListener dl);
  25. default void setListPosition(int zeroIndex) {}
  26. }
  27. @RequiredArgsConstructor
  28. @FieldDefaults(level=AccessLevel.PRIVATE, makeFinal=true)
  29. private class GrowOnData implements DocumentListener {
  30. Supplier<T> makeEmptyModel;
  31. Function<T, C> makeComponent;
  32. @NonFinal T model = null;
  33. @Override public void changedUpdate(DocumentEvent e) {}
  34. @Override public void removeUpdate(DocumentEvent e) {}
  35. @Override
  36. public void insertUpdate(DocumentEvent e) {
  37. if (model != null) {
  38. models.add(model);
  39. last().removeGrowShrinkListener(this);
  40. last().addGrowShrinkListener(new ShrinkOnEmpty(last()));
  41. }
  42. model = makeEmptyModel.get();
  43. final C comp = makeComponent.apply(model);
  44. comp.addGrowShrinkListener(this);
  45. members.add(comp);
  46. add(comp);
  47. callback.accept(true);
  48. }
  49. }
  50. @AllArgsConstructor
  51. private class ShrinkOnEmpty implements DocumentListener {
  52. ChildComponent component;
  53. @Override public void insertUpdate(DocumentEvent e) {}
  54. @Override public void changedUpdate(DocumentEvent e) {}
  55. @Override
  56. public void removeUpdate(DocumentEvent e) {
  57. if (e.getDocument().getLength() > 0) {
  58. return;
  59. }
  60. component.removeGrowShrinkListener(this);
  61. remove(members.indexOf(component));
  62. callback.accept(false);
  63. }
  64. }
  65. @Delegate(types={SetGap.class})
  66. VerticalLayout layout = new VerticalLayout();
  67. List<ChildComponent> members = new ArrayList<>();
  68. GrowOnData grow;
  69. @NonFinal List<T> models = null;
  70. @NonFinal Consumer<Boolean> callback = (b) -> {};
  71. /**
  72. *
  73. * @param makeEmptyModel A function to produce a blank model object for display.
  74. * If the model is updated in such a way as to be non-empty, it will be inserted
  75. * into the list of models.
  76. * @param makeComponent A function to generate a UI object given a model. The
  77. * object must meet the ChildComponent interface to install the AutoGrowPanel's
  78. * growing/shrinking listener objects.
  79. */
  80. public BetterAutoGrowPanel(final @NonNull Supplier<T> makeEmptyModel,
  81. final @NonNull Function<T, C> makeComponent) {
  82. setLayout(layout);
  83. this.grow = new GrowOnData(makeEmptyModel, makeComponent);
  84. this.grow.insertUpdate(null);
  85. }
  86. /**
  87. * Activate this component against the target list of children
  88. * @param models A mutable list object containing the "child models" to be
  89. * rendered in this component
  90. */
  91. public void setModel(final @NonNull List<T> models) {
  92. setModel(models, (b) -> {});
  93. }
  94. /**
  95. *
  96. * @param models A mutable list object containing the "child models" to be
  97. * rendered in this component
  98. * @param callback A callback that will be invoked each time a child is
  99. * added or removed. This allows us to provide some custom interactions
  100. * with the parent's context in case other actions need to occur.
  101. */
  102. public void setModel(final @NonNull List<T> models,
  103. final Consumer<Boolean> callback) {
  104. this.models = models;
  105. this.callback = callback;
  106. this.members.subList(0, lastIndex()).clear();
  107. models.forEach(model -> {
  108. final C comp = this.grow.makeComponent.apply(model);
  109. comp.addGrowShrinkListener(new ShrinkOnEmpty(comp));
  110. add(comp, lastIndex());
  111. this.members.add(lastIndex(), comp);
  112. });
  113. }
  114. @Override
  115. public void remove(final int index) {
  116. super.remove(index);
  117. members.remove(index);
  118. for (int size = lastIndex(); size >= index; --size) {
  119. members.get(index).setListPosition(index);
  120. }
  121. models.remove(index);
  122. }
  123. private int lastIndex() { return members.size() - 1; }
  124. private ChildComponent last() {
  125. return members.get(lastIndex());
  126. }
  127. }