---
category: swing
folder: DotMatrixLedDigitalClock
title: JListでドットマトリクスLEDデジタル時計を作成する
tags: [JList, Icon, Timer, Clock]
author: aterai
pubdate: 2023-03-13T04:54:56+09:00
description: JListを垂直方向ニュースペーパー・スタイルレイアウトに設定して各セルにLED風Iconを配置し、ドットマトリクスLEDデジタル時計を作成します。
image: https://drive.google.com/uc?id=1Ml6DCmp11wZYl1r7nBmznwupB4J1MdIC
hreflang:
    https://java-swing-tips.blogspot.com/2023/03/create-dot-matrix-led-digital-clock.html
    href: https://java-swing-tips.blogspot.com/2023/03/create-dot-matrix-led-digital-clock.html
    lang: en
---
* 概要 [#summary]
`JList`を垂直方向ニュースペーパー・スタイルレイアウトに設定して各セルに`LED`風`Icon`を配置し、ドットマトリクス`LED`デジタル時計を作成します。

#download(https://drive.google.com/uc?id=1Ml6DCmp11wZYl1r7nBmznwupB4J1MdIC)

* サンプルコード [#sourcecode]
#code(link){{
private static JList<Boolean> makeLedDotMatrixList(
    ListModel<Boolean> model, Dimension dim) {
  return new JList<Boolean>(model) {
    @Override public void updateUI() {
      setFixedCellWidth(dim.width);
      setFixedCellHeight(dim.height);
      setVisibleRowCount(ROW);
      setCellRenderer(null);
      super.updateUI();
      setLayoutOrientation(JList.VERTICAL_WRAP);
      setFocusable(false);
      ListCellRenderer<? super Boolean> renderer = getCellRenderer();
      Icon on = new LedDotIcon(true, dim);
      Icon off = new LedDotIcon(false, dim);
      setCellRenderer((list, value, index, isSelected, cellHasFocus) -> {
        Component c = renderer.getListCellRendererComponent(
            list, null, index, false, false);
        if (c instanceof JLabel) {
          ((JLabel) c).setIcon(Objects.equals(Boolean.TRUE, value) ? on : off);
        }
        return c;
      });
      setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
      setBackground(Color.BLACK);
    }
  };
}
}}

* 解説 [#explanation]
- `JList<Boolean>`を作成し垂直方向ニュースペーパー・スタイルレイアウトに設定
-- セル選択などは不可に設定
-- セルの値が`true`の場合は`LED`風ドット`Icon`を表示する`ListCellRenderer`を設定
--- `SynthLookAndFeel`系(`Nimbus`, `GTK`)の`LookAndFeel`を適用した垂直方向ニュースペーパー・スタイル`JList`の場合、リストセルレンダラーに描画するアイコンの`x`座標がおかしくなる?

#code{{
class LedDotIcon implements Icon {
  private final Color on = new Color(0x32_FF_AA);
  private final boolean led;
  private final Dimension dim;

  protected LedDotIcon(boolean led, Dimension dim) {
    this.led = led;
    this.dim = dim;
  }

  @Override public void paintIcon(Component c, Graphics g, int x, int y) {
    Graphics2D g2 = (Graphics2D) g.create();
    g2.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    // JList#setLayoutOrientation(VERTICAL_WRAP) + Synth, Nimbus, GTK bug???
    // g2.translate(x, y);
    g2.setPaint(led ? on : c.getBackground());
    g2.fillOval(0, 0, getIconWidth() - 1, getIconHeight() - 1);
    g2.dispose();
  }

  @Override public int getIconWidth() {
    return dim.width;
  }

  @Override public int getIconHeight() {
    return dim.height;
  }
}
}}

- リストモデルは`DefaultListModel<Boolean>`を使用
-- ひとつの数字を表示するため列`4`、行`7`の`28`セルを使用するので時`2`、分`2`の`4`文字と区切りと余白の`5`列分の総セル数を`DefaultListModel#setSize((COLUMN * 4 + 5) * ROW)`で設定
-- 秒表示用のモデルは`DefaultListModel#setSize((COLUMN * 2 + 1) * ROW)`を設定
-- どちらも`DefaultListModel#getElementAt(int index)`をオーバーライドしてセルが点灯しているか判断して`Boolean`を返す

#code{{
DefaultListModel<Boolean> model1 = new DefaultListModel<Boolean>() {
  @Override public Boolean getElementAt(int index) {
    return getHoursMinutesDotMatrix(time, index);
  }
};
model1.setSize((COLUMN * 4 + 5) * ROW);

private static boolean getHoursMinutesDotMatrix(LocalTime time, int index) {
  int ten = 10;
  int hours = time.getHour();
  int h1 = hours / ten;
  int start = 0;
  int end = start + COLUMN;
  if (contains(index, start, end, h1)) {
    return hours >= ten;
  }
  int gap = 1;
  int h2 = hours - h1 * ten;
  start = end + gap;
  end = start + COLUMN;
  if (contains(index, start, end, h2)) {
    return true;
  }
  int seconds = time.getSecond();
  int s1 = seconds / ten;
  int s2 = seconds - s1 * ten;
  start = end + gap;
  end = start + gap;
  if (index < end * ROW && s2 % 2 == 0 && DOT.contains(index - start * ROW)) {
    return true;
  }
  int minutes = time.getMinute();
  int m1 = minutes / ten;
  start = end + gap;
  end = start + COLUMN;
  if (contains(index, start, end, m1)) {
    return true;
  }
  int m2 = minutes - m1 * ten;
  start = end + gap;
  end = start + COLUMN;
  return contains(index, start, end, m2);
}
}}

- `Set<Integer>`を使用して数字に対してどのセルが点灯しているかを定義
-- 垂直方向ニュースペーパー・スタイルレイアウトなので左上から順にセル番号`0`、右下のセル番号`27`までを使用して以下のように数字を定義する
-- 例えば数字`2`は`Set.of(0, 3, 4, 5, 6, 7, 10, 13, 14, 17, 20, 21, 22, 23, 24, 27)`でどのセルを点灯するかを表現する
-- `Java 8`では`Set.of(...)`が使用できないので以下のような`immutableSetOf()`メソッドを作成して代用している
--- `Collections.unmodifiableSet(new HashSet<>(Arrays.asList(...)))`

#code{{
private static final int COLUMN = 4;
private static final int ROW = 7;
private static final List<Set<Integer>> NUMBERS = Arrays.asList(
  Set.of(0, 1, 2, 3, 4, 5, 6, 7, 13, 14, 20, 21, 22, 23, 24, 25, 26, 27), // 0
  Set.of(21, 22, 23, 24, 25, 26, 27), // 1
  Set.of(0, 3, 4, 5, 6, 7, 10, 13, 14, 17, 20, 21, 22, 23, 24, 27), // 2
  Set.of(0, 3, 6, 7, 10, 13, 14, 17, 20, 21, 22, 23, 24, 25, 26, 27), // 3
  Set.of(0, 1, 2, 3, 10, 17, 21, 22, 23, 24, 25, 26, 27), // 4
  Set.of(0, 1, 2, 3, 6, 7, 10, 13, 14, 17, 20, 21, 24, 25, 26, 27), // 5
  Set.of(0, 1, 2, 3, 4, 5, 6, 7, 10, 13, 14, 17, 20, 21, 24, 25, 26, 27), // 6
  Set.of(0, 1, 2, 3, 7, 14, 21, 22, 23, 24, 25, 26, 27), // 7
  Set.of(0, 1, 2, 3, 4, 5, 6, 7, 10, 13, 14, 17, 20, 21, 22, 23, 24, 25, 26, 27), // 8
  Set.of(0, 1, 2, 3, 6, 7, 10, 13, 14, 17, 20, 21, 22, 23, 24, 25, 26, 27)); // 9
private static final List<Integer> DOT = Arrays.asList(2, 4);
}}

- 数字の列数やドットパターンを変更

#img2(https://ateraimemo.com/swing/dotmatrixleddigitalclock/screenshot1.png)

#code{{
private static final int COLUMN = 5;
private static final int ROW = 7;
private static final List<Set<Integer>> NUMBERS = Arrays.asList(
    Set.of(1, 2, 3, 4, 5, 7, 9, 13, 14, 17, 20, 21, 25, 27, 29, 30, 31, 32, 33), // 0
    Set.of(8, 13, 14, 15, 16, 17, 18, 19, 20, 27), // 1
    Set.of(1, 6, 7, 12, 13, 14, 18, 20, 21, 24, 27, 29, 30, 34), // 2
    Set.of(0, 5, 7, 13, 14, 17, 20, 21, 23, 24, 27, 28, 29, 32, 33), // 3
    Set.of(3, 4, 9, 11, 15, 18, 21, 22, 23, 24, 25, 26, 27, 32), // 4
    Set.of(0, 1, 2, 5, 7, 9, 13, 14, 16, 20, 21, 23, 27, 28, 31, 32, 33), // 5
    Set.of(1, 2, 3, 4, 5, 7, 10, 13, 14, 17, 20, 21, 24, 27, 29, 32, 33), // 6
    Set.of(0, 7, 11, 12, 13, 14, 17, 21, 23, 28, 29), // 7
    Set.of(1, 2, 4, 5, 7, 10, 13, 14, 17, 20, 21, 24, 27, 29, 30, 32, 33), // 8
    Set.of(1, 2, 5, 7, 10, 13, 14, 17, 20, 21, 24, 27, 29, 30, 31, 32, 33)); // 9
}}

* 参考リンク [#reference]
- [[AffineTransformを使用してPath2Dを変換し、7セグメントデジタル時計の数字を作成する>Swing/SevenSegmentDigitalClock]]
- [[JListでウィークカレンダーを作成してヒートマップを表示する>Swing/CalendarHeatmapList]]

* コメント [#comment]
#comment
#comment