• category: swing folder: SpinnerNumberModelGroup title: JSpinnerの数値の合計がグループ内で一定になるよう設定する tags: [JSpinner, SpinnerNumberModel, ChangeListener] author: aterai pubdate: 2023-09-04T08:13:40+09:00 description: 複数のSpinnerNumberModelをセットしたグループを作成し、グループ内でその数値の合計が一定になるよう設定します。 image: https://drive.google.com/uc?id=1zvtUOV-9xdUWI6Ybl6ZCMS52aBabjAjV

概要

複数のSpinnerNumberModelをセットしたグループを作成し、グループ内でその数値の合計が一定になるよう設定します。

サンプルコード

class SpinnerNumberModelGroup {
  private final int expectedSum;
  private final Deque<SpinnerNumberModel> candidates = new ArrayDeque<>();
  private final ChangeListener changeListener = e -> {
    SpinnerNumberModel source = (SpinnerNumberModel) e.getSource();
    update(source);
  };
  private boolean updating;

  protected SpinnerNumberModelGroup(int expectedSum) {
    this.expectedSum = expectedSum;
  }

  @SuppressWarnings("PMD.UnusedAssignment")
  private void update(SpinnerNumberModel source) {
    if (updating) {
      return;
    }
    updating = true;
    if (candidates.size() - 1 > 0) {
      int delta = computeSum() - expectedSum;
      if (delta > 0) {
        distributeRemove(delta, source);
      } else {
        distributeAdd(delta, source);
      }
    }
    updating = false;
  }

  private void distributeRemove(int delta, SpinnerNumberModel source) {
    int counter = 0;
    int remaining = delta;
    while (remaining > 0) {
      SpinnerNumberModel model = candidates.removeFirst();
      counter++;
      if (Objects.equals(model, source)) {
        candidates.addLast(model);
      } else {
        Object prev = model.getPreviousValue();
        if (prev instanceof Integer) {
          model.setValue(prev);
          remaining--;
          counter = 0;
        }
        candidates.addLast(model);
        if (remaining == 0) {
          break;
        }
      }
      if (counter > candidates.size()) {
        String msg = "Can not distribute " + delta + " among " + candidates;
        throw new IllegalArgumentException(msg);
      }
    }
  }

  private void distributeAdd(int delta, SpinnerNumberModel source) {
    int counter = 0;
    int remaining = -delta;
    while (remaining > 0) {
      SpinnerNumberModel model = candidates.removeLast();
      counter++;
      if (Objects.equals(model, source)) {
        candidates.addFirst(model);
      } else {
        // if (model.getNumber().intValue() < (int) model.getMaximum()) {
        Object next = model.getNextValue();
        if (next instanceof Integer) {
          model.setValue(next);
          remaining--;
          counter = 0;
        }
        candidates.addFirst(model);
        if (remaining == 0) {
          break;
        }
      }
      if (counter > candidates.size()) {
        String msg = "Can not distribute " + delta + " among " + candidates;
        throw new IllegalArgumentException(msg);
      }
    }
  }

  private int computeSum() {
    return candidates.stream()
        .map(SpinnerNumberModel::getNumber)
        .mapToInt(Number::intValue)
        .sum();
  }

  public void add(SpinnerNumberModel spinner) {
    candidates.add(spinner);
    spinner.addChangeListener(changeListener);
  }
}
View in GitHub: Java, Kotlin

解説

  • Deque<SpinnerNumberModel>SpinnerNumberModelのグループを作成
  • グループ内のSpinnerNumberModelChangeListenerを追加し、値の更新イベントを取得
  • ひとつのSpinnerNumberModelが更新されたらその差分を計算
  • Deque<SpinnerNumberModel>の末尾からSpinnerNumberModelを取り出して差分を配分し、Deque<SpinnerNumberModel>の先頭に戻す
    • 取り出したSpinnerNumberModelが更新イベント元のSpinnerNumberModelの場合はなにもせずに先頭に戻す
    • 差分が0になるまでこの処理を繰り返す

参考リンク

コメント