JavaFX でカップラーメンタイマーを作る

# 概要
以前投稿した「[JavaFX でストップウォッチを作る](http://qiita.com/y_q1m/items/4fa24957be4c75d8098d)」のコメントで @shiracamus さんからカップラーメンタイマーのリクエストを受けましたので、作ってみました。

# 必要な機能
1. 3分、4分、5分のボタン
2. 押すとカウントダウンが始まる
3. 0でチャルメラ
4. 音が鳴らない人のためにポップアップウィンドウ表示
5. 停止ボタン

# スクリーンショット
<img width="450" alt="スクリーンショット (23).png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/092d7ac2-79bb-9dff-d63f-8a302308ed30.png">

起動直後です。クローズボタンだけ赤くしました。

<img width="453" alt="スクリーンショット (24).png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/30ca709e-189f-6f77-329b-73e8598b9a4d.png">

大きな数字のボタンを押すとタイマーにセットされます。いずれも分単位です。

<img width="449" alt="スクリーンショット (26).png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/117a1a85-7db6-1c09-1f42-82157c97fc0d.png">

3-5分しかできないのはカップラーメンの多様性にそぐわないので、好きな時間を分単位で入力できるTextFieldを用意してあります。[10分どん兵衛](http://matome.naver.jp/odai/2144842822866118401) (リンク先は NAVERまとめ の記事です) を試したい方もこれで安心ですね。

<img width="458" alt="スクリーンショット (27).png" src="https://qiita-image-store.s3.amazonaws.com/0/102004/0af2d430-d661-25bf-7590-316a07feaf36.png">

▶のボタンを押すとカウントダウンが始まります。カウントダウン中は■ボタンに変化し、このボタンを押せば一時中断が可能です。

![スクリーンショット (21).png](https://qiita-image-store.s3.amazonaws.com/0/102004/804888e0-9183-57c0-66a1-a5c4f36e9d71.png)
0になると音声とダイアログで通知します。なお、音声ファイルはWindowsのデフォルトを使用しています。音声ファイルが存在しない場合はダイアログだけ表示されます。

# 注意
Dialog を使用しているため、Java SE 8u40以前の環境では動作しません。

# 実装
jp.toastkid.gui.jfx.noodle_timer パッケージにすべてのクラスを置いてあります。各クラスのポイントを説明します。

## [Main.java](https://github.com/toastkidjp/noodle_timer/blob/08dfb92372bf63f97bb97959f0605e313b017c53/src/main/java/jp/toastkid/gui/jfx/noodle_timer/Main.java)
Application をスタートさせるクラスです。

Windows の場合、 JavaFX のタスクアイコンは下記のやり方で設定可能です。

```java:タスクアイコン設定
final Image image = new Image(getClass().getClassLoader().getResourceAsStream(PATH_TO_ICON));
stage.getIcons().add(image);
```

## [Controller.java](https://github.com/toastkidjp/noodle_timer/blob/08dfb92372bf63f97bb97959f0605e313b017c53/src/main/java/jp/toastkid/gui/jfx/noodle_timer/Controller.java)
UIからの入力に対し操作を実行するクラスです。

## [Notifier.java](https://github.com/toastkidjp/noodle_timer/blob/08dfb92372bf63f97bb97959f0605e313b017c53/src/main/java/jp/toastkid/gui/jfx/noodle_timer/Notifier.java)
通知を実行するクラスです。具体的にはダイアログの表示と音声ファイルの再生を実施します。下記の通り、 Builder を使って初期化します。

```java:Notifierの初期化
notifier = new Notifier.Builder().setPathToWav(Resource.PATH_TO_WAV)
                .setTitle("時間です！")
                .setMessage("ラーメンができました！！！！！")
                .build();
```

### アイコン差し替え
ダイアログはアイコンを差し替えてみました。

```java:画像の用意
final Image image = new Image(getClass().getClassLoader().getResourceAsStream("ramen.png"));
```

```java:ウィンドウアイコンの差し替え
final Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
stage.getIcons().add(image);
```

```java:Alertのアイコン差し替え
alert.setGraphic(new ImageView(image));
```

## [Stopwatch.java](https://github.com/toastkidjp/noodle_timer/blob/08dfb92372bf63f97bb97959f0605e313b017c53/src/main/java/jp/toastkid/gui/jfx/noodle_timer/Stopwatch.java)
カウントダウンタイマーを拡張で実装しました。

### コールバック設定機能
Start/ Stop 時にボタンの表示変更をしたかったので、コールバックを設定できるようにしてあります。

Controllerクラスから下記の通り設定してあります。

```java
timer.setOnStart( ()  -> {ctrl.setText("■");});
timer.setOnStop(  ()  -> {ctrl.setText("▶");});
```

### 自作の Control に独自の property を追加
コンストラクタの引数に NamedArg アノテーションを追加します。
この場合、 FXML で additional という属性名を指定してやることで値を設定できます。

```java:コンストラクタに追加
public Stopwatch(@NamedArg("isCountDown") final boolean isCountDown) {
```

```xml:FXML
<Stopwatch fx:id="timer" prefHeight="400.0" prefWidth="525.0" isCountDown="true" />
```


## [NumberTextField.java](https://github.com/toastkidjp/noodle_timer/blob/08dfb92372bf63f97bb97959f0605e313b017c53/src/main/java/jp/toastkid/gui/jfx/noodle_timer/NumberTextField.java)
数値だけを受け付ける機能は TextField を拡張して用意しました。 replaceText と replaceSelection メソッドを Override し、validate というバリデーションメソッドを通して残った値を入力値とするものです。これを応用すれば、逆に文字列だけを受け付ける TextField 等も作れます。

```java
    @Override
    public void replaceText(final int start, final int end, final String text) {
        if (validate(text)){
            super.replaceText(start, end, text);
        }
    }

    @Override
    public void replaceSelection(final String text) {
        if (validate(text)) {
            super.replaceSelection(text);
        }
    }

    private boolean validate(final String text) {
        return text.matches("[0-9]*");
    }
```

## [Resource.java](https://github.com/toastkidjp/noodle_timer/blob/08dfb92372bf63f97bb97959f0605e313b017c53/src/main/java/jp/toastkid/gui/jfx/noodle_timer/Resource.java)
アイコンと音声ファイルの置き場所を定数で保持しているクラスです。

# 改良点
1. JFoenix を使って Material Design 化
2. 秒単位でのタイマー設定
3. チャルメラ音ほか任意の効果音を設定可能にする
4. 残り時間に合わせて色を変える
5. デザイン修正

# ソースコード
[GitHub Repository](https://github.com/toastkidjp/noodle_timer)

# 参考
1. [Property “style” does not exist or is read-only](http://stackoverflow.com/questions/34202880/property-style-does-not-exist-or-is-read-only)
2. [How do you set the icon of a Dialog control Java FX/Java 8](http://stackoverflow.com/questions/27976345/how-do-you-set-the-icon-of-a-dialog-control-java-fx-java-8)
3. [JavaFX Dialogs (official)](http://code.makery.ch/blog/javafx-dialogs-official/)
4. [What is the recommended way to make a numeric TextField in JavaFX?](http://stackoverflow.com/questions/7555564/what-is-the-recommended-way-to-make-a-numeric-textfield-in-javafx)