Pythonで作るポップなポモドーロタイマー

## はじめに

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/08d229e1-94bf-dc8e-dea0-57327dca9e0e.png)

こんにちは！今回は、Pythonを使ってポモドーロタイマーを作成する過程を、要件定義から実装まで詳しく解説します。このプロジェクトを通じて、以下のスキルを身につけることができます：

- ソフトウェア開発のプロセス（要件定義、仕様策定、設計、実装）
- Pythonの基本的な構文とオブジェクト指向プログラミング
- Tkinterを使ったGUIアプリケーションの作成
- 時間管理の基本概念とその実装方法

それでは、プロジェクトの各段階を見ていきましょう。

## 1. 要件定義

まず、ポモドーロタイマーの基本的な要件を定義します。

### 1.1 機能要件

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/b76458cc-cf68-3165-f5a0-42b80b60ba24.png)

1. 25分の作業時間を計測するタイマー機能
2. 5分の短い休憩時間を計測するタイマー機能
3. 15分の長い休憩時間を計測するタイマー機能（4回の作業セッション後）
4. タイマーの開始、一時停止、再開、リセット機能
5. 現在のフェーズ（作業、短い休憩、長い休憩）の表示
6. 完了したポモドーロ（作業セッション）数の表示
7. 現在のセッション数の表示（1/4, 2/4, 3/4, 4/4）

### 1.2 非機能要件

![napkin-selection (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/0f8e0da6-a49e-9d7f-c036-624963abe460.png)

1. ユーザーフレンドリーなインターフェース
2. 視覚的にわかりやすいデザイン
3. 操作が直感的で簡単であること
4. プログラムの安定性と信頼性

## 2. 仕様策定

要件に基づいて、具体的な仕様を決定します。

### 2.1 タイマー仕様

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/7b74acdc-2e5a-6f9d-a639-2344302ae188.png)

- 作業時間: 25分
- 短い休憩時間: 5分
- 長い休憩時間: 15分
- タイマーは1秒ごとにカウントダウン
- 4回の作業セッション後に長い休憩

### 2.2 UI仕様

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/7de3d4ec-eec8-8883-5ee2-48dc5ec49d08.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/6f92ea29-2d9b-0e15-f792-7d2148088473.png)

- ウィンドウサイズ: 適切なサイズ（例：400x300ピクセル）
- 表示要素:
  - タイトル
  - 現在の時間（分:秒）
  - 現在のフェーズ表示
  - セッション進捗表示
  - 完了したポモドーロ数
- 操作ボタン:
  - スタート
  - 一時停止
  - 再開
  - リセット
  - スキップ（現在のフェーズをスキップ）

### 2.3 デザイン仕様

- カラーパレット:
  - 背景色: 暖かみのある色（例：#FFF8E1）
  - メインカラー: ビビッドな色（例：オレンジ #FF5722）
  - アクセントカラー: 補完的な色（例：ブルー #2196F3）
- フォント: 読みやすいサンセリフフォント（例：Helvetica）

## 3. 設計

仕様に基づいて、システムの設計を行います。

### 3.1 クラス設計

```mermaid
classDiagram
    class ポモドーロタイマー {
        -ウィンドウ: Tk
        -現在のフェーズ: フェーズ
        -状態: 状態
        -残り時間: 整数
        -完了ポモドーロ数: 整数
        -現在のセッション: 整数
        -タイマー: After
        +初期化()
        +UI設定()
        +タイマー開始()
        +タイマー一時停止()
        +タイマー再開()
        +タイマーリセット()
        +フェーズスキップ()
        +カウントダウン()
        +次のフェーズへ()
        +タイマー表示更新()
        +フェーズ表示更新()
        +ポモドーロカウント更新()
        +実行()
    }

    class フェーズ {
        <<列挙型>>
        作業
        短い休憩
        長い休憩
    }

    class 状態 {
        <<列挙型>>
        アイドル
        実行中
        一時停止中
    }

    class Tk {
        +title(文字列)
        +config(**キーワード引数)
        +mainloop()
    }

    class ラベル {
        +config(**キーワード引数)
        +pack(**キーワード引数)
        +grid(**キーワード引数)
    }

    class ボタン {
        +config(**キーワード引数)
        +pack(**キーワード引数)
        +grid(**キーワード引数)
    }

    class フレーム {
        +pack(**キーワード引数)
        +grid(**キーワード引数)
    }

    ポモドーロタイマー "1" *-- "1" Tk : 含む
    ポモドーロタイマー "1" *-- "1" フェーズ : 使用する
    ポモドーロタイマー "1" *-- "1" 状態 : 使用する
    ポモドーロタイマー "1" *-- "多" ラベル : 含む
    ポモドーロタイマー "1" *-- "多" ボタン : 含む
    ポモドーロタイマー "1" *-- "多" フレーム : 含む
    Tk "1" *-- "多" ラベル : 含む
    Tk "1" *-- "多" ボタン : 含む
    Tk "1" *-- "多" フレーム : 含む
```

### 3.2 状態遷移設計

```mermaid
stateDiagram-v2
    [*] --> アイドル
    state アイドル {
        [*] --> 作業準備: 初期化
    }
    アイドル --> 実行中: 開始
    state 実行中 {
        [*] --> 作業中
        作業中 --> 短い休憩中: 25分経過 (1-3セット目)
        作業中 --> 長い休憩中: 25分経過 (4セット目)
        短い休憩中 --> 作業中: 5分経過
        長い休憩中 --> 作業中: 15分経過
    }
    実行中 --> 一時停止中: 一時停止
    一時停止中 --> 実行中: 再開
    実行中 --> アイドル: リセット
    一時停止中 --> アイドル: リセット
    実行中 --> 実行中: スキップ
```

1. アイドル状態
   - 開始 → 実行中状態
   - リセット → アイドル状態（初期化）
2. 実行中状態
   - 一時停止 → 一時停止状態
   - 完了 → 次のフェーズのアイドル状態
   - スキップ → 次のフェーズのアイドル状態
3. 一時停止状態
   - 再開 → 実行中状態
   - リセット → アイドル状態

### 3.3 シーケンス図
```mermaid
sequenceDiagram
    actor ユーザー
    participant UI as UIコンポーネント
    participant PT as ポモドーロタイマー
    participant Timer as 内部タイマー

    Note over PT: 初期状態: アイドル

    ユーザー->>UI: 開始ボタンクリック
    UI->>PT: start_timer()
    PT->>PT: state = 実行中
    PT->>Timer: カウントダウン開始
    Timer->>PT: 1秒経過
    PT->>UI: タイマー表示更新

    loop 25分間
        Timer->>PT: 1秒経過
        PT->>UI: タイマー表示更新
    end

    PT->>PT: next_phase()
    PT->>UI: フェーズ表示更新（休憩中）

    ユーザー->>UI: 一時停止ボタンクリック
    UI->>PT: pause_timer()
    PT->>PT: state = 一時停止中
    PT->>Timer: カウントダウン停止

    ユーザー->>UI: 再開ボタンクリック
    UI->>PT: resume_timer()
    PT->>PT: state = 実行中
    PT->>Timer: カウントダウン再開

    ユーザー->>UI: スキップボタンクリック
    UI->>PT: skip_phase()
    PT->>PT: next_phase()
    PT->>UI: フェーズ表示更新

    ユーザー->>UI: リセットボタンクリック
    UI->>PT: reset_timer()
    PT->>PT: state = アイドル
    PT->>Timer: タイマーリセット
    PT->>UI: 全表示リセット

    Note over PT: 4セット目の作業後
    PT->>PT: next_phase()
    PT->>UI: フェーズ表示更新（長い休憩）

    loop 15分間
        Timer->>PT: 1秒経過
        PT->>UI: タイマー表示更新
    end

    PT->>PT: next_phase()
    PT->>UI: フェーズ表示更新（作業中）
    PT->>UI: セッションカウント更新
```

## 4. 実装

設計に基づいて、Pythonコードを実装します。以下は主要な部分の実装例です。

```python
import tkinter as tk
from enum import Enum
import math

class Phase(Enum):
    WORK = 1
    SHORT_BREAK = 2
    LONG_BREAK = 3

class State(Enum):
    IDLE = 1
    RUNNING = 2
    PAUSED = 3

class PomodoroTimer:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("ポモドーロタイマー")
        self.window.config(padx=20, pady=20, bg="#FFF8E1")  # 背景色を暖かい色に設定

        # 初期状態の設定
        self.current_phase = Phase.WORK
        self.state = State.IDLE
        self.time_left = 0
        self.pomodoros_completed = 0
        self.current_session = 1
        self.timer = None

        self.setup_ui()
        self.reset_timer()

    def setup_ui(self):
        # フォントとカラーの設定
        TITLE_FONT = ("Helvetica", 28, "bold")
        TIMER_FONT = ("Helvetica", 64, "bold")
        LABEL_FONT = ("Helvetica", 18)
        BUTTON_FONT = ("Helvetica", 14, "bold")

        MAIN_COLOR = "#FF5722"  # オレンジ
        SECONDARY_COLOR = "#4CAF50"  # グリーン
        ACCENT_COLOR = "#2196F3"  # ブルー

        # タイトル
        self.title_label = tk.Label(text="ポモドーロタイマー", fg=MAIN_COLOR, bg="#FFF8E1", font=TITLE_FONT)
        self.title_label.grid(column=0, row=0, columnspan=4, pady=(0, 20))

        # タイマー表示
        self.timer_frame = tk.Frame(self.window, bg="#FFE0B2", bd=2, relief="raised")
        self.timer_frame.grid(column=0, row=1, columnspan=4, pady=20, padx=20, sticky="nsew")

        self.timer_label = tk.Label(self.timer_frame, text="25:00", fg=MAIN_COLOR, bg="#FFE0B2", font=TIMER_FONT)
        self.timer_label.pack(pady=20)

        # フェーズと進捗表示
        self.info_frame = tk.Frame(self.window, bg="#FFF8E1")
        self.info_frame.grid(column=0, row=2, columnspan=4, pady=10)

        self.phase_label = tk.Label(self.info_frame, text="作業", fg=SECONDARY_COLOR, bg="#FFF8E1", font=LABEL_FONT)
        self.phase_label.pack(side="left", padx=10)

        self.session_label = tk.Label(self.info_frame, text="セッション: 1/4", fg=ACCENT_COLOR, bg="#FFF8E1", font=LABEL_FONT)
        self.session_label.pack(side="left", padx=10)

        # ボタン
        self.button_frame = tk.Frame(self.window, bg="#FFF8E1")
        self.button_frame.grid(column=0, row=3, columnspan=4, pady=20)

        button_width = 10
        button_height = 2

        self.start_button = tk.Button(self.button_frame, text="開始", command=self.start_timer, 
                                      bg=SECONDARY_COLOR, fg="white", font=BUTTON_FONT, 
                                      width=button_width, height=button_height)
        self.start_button.pack(side="left", padx=5)

        self.pause_button = tk.Button(self.button_frame, text="一時停止", command=self.pause_timer, 
                                      bg=MAIN_COLOR, fg="white", font=BUTTON_FONT, 
                                      width=button_width, height=button_height, state=tk.DISABLED)
        self.pause_button.pack(side="left", padx=5)

        self.resume_button = tk.Button(self.button_frame, text="再開", command=self.resume_timer, 
                                       bg=ACCENT_COLOR, fg="white", font=BUTTON_FONT, 
                                       width=button_width, height=button_height, state=tk.DISABLED)
        self.resume_button.pack(side="left", padx=5)

        self.reset_button = tk.Button(self.button_frame, text="リセット", command=self.reset_timer, 
                                      bg="#607D8B", fg="white", font=BUTTON_FONT, 
                                      width=button_width, height=button_height)
        self.reset_button.pack(side="left", padx=5)

        # スキップボタン
        self.skip_button = tk.Button(self.window, text="スキップ", command=self.skip_phase, 
                                     bg="#9C27B0", fg="white", font=BUTTON_FONT, 
                                     width=button_width, height=1, state=tk.DISABLED)
        self.skip_button.grid(column=0, row=4, columnspan=4, pady=10)

        # 完了したポモドーロ数
        self.pomodoro_label = tk.Label(text="完了したポモドーロ: 0", fg=MAIN_COLOR, bg="#FFF8E1", font=LABEL_FONT)
        self.pomodoro_label.grid(column=0, row=5, columnspan=4, pady=10)

    def start_timer(self):
        if self.state == State.IDLE:
            self.state = State.RUNNING
            self.start_button.config(state=tk.DISABLED)
            self.pause_button.config(state=tk.NORMAL)
            self.resume_button.config(state=tk.DISABLED)
            self.skip_button.config(state=tk.NORMAL)
            self.countdown()

    def pause_timer(self):
        if self.state == State.RUNNING:
            self.state = State.PAUSED
            self.pause_button.config(state=tk.DISABLED)
            self.resume_button.config(state=tk.NORMAL)
            if self.timer is not None:
                self.window.after_cancel(self.timer)
                self.timer = None

    def resume_timer(self):
        if self.state == State.PAUSED:
            self.state = State.RUNNING
            self.pause_button.config(state=tk.NORMAL)
            self.resume_button.config(state=tk.DISABLED)
            self.countdown()

    def reset_timer(self):
        if self.timer is not None:
            self.window.after_cancel(self.timer)
            self.timer = None
        self.state = State.IDLE
        self.current_phase = Phase.WORK
        self.time_left = 25 * 60  # 25分
        self.current_session = 1
        self.update_timer_display()
        self.update_phase_display()
        self.start_button.config(state=tk.NORMAL)
        self.pause_button.config(state=tk.DISABLED)
        self.resume_button.config(state=tk.DISABLED)
        self.skip_button.config(state=tk.DISABLED)

    def skip_phase(self):
        if self.timer is not None:
            self.window.after_cancel(self.timer)
            self.timer = None
        self.next_phase()

    def countdown(self):
        if self.state == State.RUNNING:
            if self.time_left > 0:
                self.time_left -= 1
                self.update_timer_display()
                self.timer = self.window.after(1000, self.countdown)
            else:
                self.next_phase()

    def next_phase(self):
        if self.current_phase == Phase.WORK:
            self.pomodoros_completed += 1
            self.update_pomodoro_count()
            if self.current_session % 4 == 0:
                self.current_phase = Phase.LONG_BREAK
                self.time_left = 15 * 60  # 15分
            else:
                self.current_phase = Phase.SHORT_BREAK
                self.time_left = 5 * 60  # 5分
        else:
            self.current_phase = Phase.WORK
            self.time_left = 25 * 60  # 25分
            self.current_session += 1
        
        self.update_phase_display()
        self.update_timer_display()
        self.state = State.IDLE
        self.start_button.config(state=tk.NORMAL)
        self.pause_button.config(state=tk.DISABLED)
        self.resume_button.config(state=tk.DISABLED)
        self.skip_button.config(state=tk.DISABLED)

    def update_timer_display(self):
        minutes, seconds = divmod(self.time_left, 60)
        self.timer_label.config(text=f"{minutes:02d}:{seconds:02d}")

    def update_phase_display(self):
        if self.current_phase == Phase.WORK:
            self.phase_label.config(text="作業", fg="#4CAF50")
        elif self.current_phase == Phase.SHORT_BREAK:
            self.phase_label.config(text="短い休憩", fg="#2196F3")
        else:
            self.phase_label.config(text="長い休憩", fg="#F44336")
        self.session_label.config(text=f"セッション: {self.current_session}/4")

    def update_pomodoro_count(self):
        self.pomodoro_label.config(text=f"完了したポモドーロ: {self.pomodoros_completed}")

    def run(self):
        self.window.mainloop()

if __name__ == "__main__":
    timer = PomodoroTimer()
    timer.run()
```
## 5. 実装のポイント

ポモドーロタイマーの実装において、以下のポイントに特に注意を払いました：

1. **クラスベースの設計**
   - `PomodoroTimer` クラスを中心に、アプリケーションの全機能をカプセル化しました。
   - これにより、コードの再利用性と保守性が向上します。

2. **状態管理の明確化**
   - `Phase` と `State` の列挙型を使用して、タイマーのフェーズと状態を明確に定義しました。
   - これにより、複雑な状態遷移を管理しやすくなります。

3. **イベントドリブンプログラミング**
   - Tkinterのイベントシステムを活用し、ユーザーのアクションに応じて適切なメソッドを呼び出します。
   - ボタンクリックなどのイベントと、それに対応するメソッドを明確に関連付けています。

4. **非同期タイマーの実装**
   - `after` メソッドを使用して、非ブロッキングなタイマー機能を実現しています。
   - これにより、UIの応答性を保ちながら、正確なタイミング管理が可能になります。

5. **ユーザーインターフェースの動的更新**
   - タイマーの状態やフェーズの変更に応じて、UIを動的に更新します。
   - ラベルやボタンの状態を適切に管理し、現在の状況を明確にユーザーに伝えます。

6. **エラー処理とエッジケースの考慮**
   - タイマーの一時停止や再開、リセット時の処理を適切に実装し、予期せぬ動作を防止しています。
   - 各メソッドで現在の状態をチェックし、不適切な操作を防いでいます。

7. **モジュール化と関数の分離**
   - 各機能（開始、一時停止、再開、リセットなど）を別々のメソッドとして実装し、コードの可読性を向上させています。
   - UI更新、タイマー制御、状態管理などの機能を明確に分離しています。

8. **定数の活用**
   - 作業時間、休憩時間、色などの設定を定数として定義し、容易にカスタマイズできるようにしています。

9. **Pythonの言語機能の活用**
   - f文字列を使用して、文字列フォーマットを簡潔に記述しています。
   - `divmod` 関数を使用して、秒を分と秒に効率的に変換しています。

10. **UIデザインの考慮**
    - 色やフォントを適切に選択し、視覚的に魅力的なインターフェースを作成しています。
    - ボタンのサイズや配置を工夫し、使いやすさを向上させています。

これらのポイントに注意を払うことで、機能的で保守性の高いポモドーロタイマーアプリケーションを実現しています。また、これらの実装技術は、他の類似のプロジェクトにも応用可能です。

## まとめ

![napkin-selection (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/3f9f11e1-c048-c047-8ed1-7a301213b928.png)

このポモドーロタイマープロジェクトを通じて、私たちは単なるタイマーアプリケーションの作成を超えて、ソフトウェア開発の包括的なプロセスを体験しました。以下に、このプロジェクトから得られる主要な学びと insights をまとめます：

1. **統合的なソフトウェア開発プロセス**
   - 要件定義から実装まで、ソフトウェア開発の全段階を実践的に学びました。
   - 各段階の重要性と相互関連性を理解することができました。

2. **Pythonとオブジェクト指向プログラミングの実践**
   - Pythonの強力な機能（列挙型、f文字列、divmod関数など）を実際のプロジェクトで活用しました。
   - クラスベースの設計を通じて、オブジェクト指向プログラミングの原則を適用しました。

3. **GUIプログラミングの基礎**
   - Tkinterを使用して、実用的でユーザーフレンドリーなインターフェースを設計・実装しました。
   - イベントドリブンプログラミングの概念を理解し、応用しました。

4. **状態管理と非同期プログラミング**
   - 複雑な状態遷移を管理する技術を学びました。
   - 非同期タイマーの実装を通じて、応答性の高いアプリケーション開発のテクニックを習得しました。

5. **ソフトウェアアーキテクチャの重要性**
   - モジュール化、関数の分離、適切な定数の使用など、保守性の高いコード構造の重要性を理解しました。

6. **実践的な問題解決スキル**
   - エラー処理やエッジケースの考慮など、実世界のアプリケーション開発で直面する課題に取り組みました。

このプロジェクトは、単にポモドーロタイマーを作るだけでなく、実践的なソフトウェア開発スキルを総合的に向上させる機会となりました。ここで学んだ技術と概念は、より複雑なアプリケーションの開発にも応用可能です。

今後の発展として、以下のような拡張が考えられます：

- タスク管理機能の追加
- ユーザー設定のカスタマイズ（作業時間、休憩時間の調整など）
- データの永続化（SQLiteなどのデータベースの使用）
- 統計情報の表示（生産性分析など）
- クロスプラットフォーム対応（Webアプリケーションやモバイルアプリへの展開）

このプロジェクトを基盤として、さらに機能を追加したり、異なるテクノロジーを探求したりすることで、ソフトウェア開発スキルを継続的に向上させることができるでしょう。

ポモドーロテクニックの実践と合わせて、このアプリケーションを日々の作業に活用し、生産性の向上を図ってください。そして、ここで得た知識と経験を、今後のプログラミング学習やプロジェクト開発に活かしていくことをお勧めします。
## 参考リソース

- [Python公式ドキュメント](https://docs.python.org/ja/3/)
- [Tkinterドキュメント](https://docs.python.org/3/library/tkinter.html)
- [ポモドーロ・テクニックについて](https://francescocirillo.com/pages/pomodoro-technique)

Happy coding!


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/29bc08e2-67dd-63e1-9da5-062fde160329.png)

## コメント対応 : 実装
指摘事項、改善の内容はコメント欄を参照ください。

```python
import tkinter as tk
from tkinter import ttk, messagebox
from enum import Enum

class Time:
    SECONDS = 1
    MINUTES = 60 * SECONDS

class Phase(Enum):
    WORK = 1
    SHORT_BREAK = 2
    LONG_BREAK = 3

class State(Enum):
    IDLE = 1
    RUNNING = 2
    PAUSED = 3

class PomodoroTimer:
    def __init__(self):
        self.window = tk.Tk()
        self.window.title("ポモドーロタイマー")
        self.window.config(padx=20, pady=20, bg="#FFF8E1")

        self.settings = {
            "work_time": 25,
            "short_break_time": 5,
            "long_break_time": 15,
            "phases_in_session": 4
        }

        self.current_phase = Phase.WORK
        self.state = State.IDLE
        self.time_left = 0
        self.pomodoros_completed = 0
        self.current_session = 1
        self.timer = None

        self.setup_ui()
        self.reset_timer()

    def setup_ui(self):
        # フォントとカラーの設定
        TITLE_FONT = ("Helvetica", 28, "bold")
        TIMER_FONT = ("Helvetica", 64, "bold")
        LABEL_FONT = ("Helvetica", 18)
        BUTTON_FONT = ("Helvetica", 14, "bold")

        MAIN_COLOR = "#FF5722"  # オレンジ
        SECONDARY_COLOR = "#4CAF50"  # グリーン
        ACCENT_COLOR = "#2196F3"  # ブルー

        # タイトル
        self.title_label = tk.Label(text="ポモドーロタイマー", fg=MAIN_COLOR, bg="#FFF8E1", font=TITLE_FONT)
        self.title_label.grid(column=0, row=0, columnspan=4, pady=(0, 20))

        # タイマー表示
        self.timer_frame = tk.Frame(self.window, bg="#FFE0B2", bd=2, relief="raised")
        self.timer_frame.grid(column=0, row=1, columnspan=4, pady=20, padx=20, sticky="nsew")

        self.timer_label = tk.Label(self.timer_frame, text="25:00", fg=MAIN_COLOR, bg="#FFE0B2", font=TIMER_FONT)
        self.timer_label.pack(pady=20)

        # フェーズと進捗表示
        self.info_frame = tk.Frame(self.window, bg="#FFF8E1")
        self.info_frame.grid(column=0, row=2, columnspan=4, pady=10)

        self.phase_label = tk.Label(self.info_frame, text="作業", fg=SECONDARY_COLOR, bg="#FFF8E1", font=LABEL_FONT)
        self.phase_label.pack(side="left", padx=10)

        self.session_label = tk.Label(self.info_frame, text=f"セッション: 1/{self.settings['phases_in_session']}", fg=ACCENT_COLOR, bg="#FFF8E1", font=LABEL_FONT)
        self.session_label.pack(side="left", padx=10)

        # ボタン
        self.button_frame = tk.Frame(self.window, bg="#FFF8E1")
        self.button_frame.grid(column=0, row=3, columnspan=4, pady=20)

        button_width = 10
        button_height = 2

        self.start_button = tk.Button(self.button_frame, text="開始", command=self.start_timer, 
                                      bg=SECONDARY_COLOR, fg="white", font=BUTTON_FONT, 
                                      width=button_width, height=button_height)
        self.start_button.pack(side="left", padx=5)

        self.pause_button = tk.Button(self.button_frame, text="一時停止", command=self.pause_timer, 
                                      bg=MAIN_COLOR, fg="white", font=BUTTON_FONT, 
                                      width=button_width, height=button_height, state=tk.DISABLED)
        self.pause_button.pack(side="left", padx=5)

        self.resume_button = tk.Button(self.button_frame, text="再開", command=self.resume_timer, 
                                       bg=ACCENT_COLOR, fg="white", font=BUTTON_FONT, 
                                       width=button_width, height=button_height, state=tk.DISABLED)
        self.resume_button.pack(side="left", padx=5)

        self.reset_button = tk.Button(self.button_frame, text="リセット", command=self.reset_timer, 
                                      bg="#607D8B", fg="white", font=BUTTON_FONT, 
                                      width=button_width, height=button_height)
        self.reset_button.pack(side="left", padx=5)

        # スキップボタン
        self.skip_button = tk.Button(self.window, text="スキップ", command=self.skip_phase, 
                                     bg="#9C27B0", fg="white", font=BUTTON_FONT, 
                                     width=button_width, height=1, state=tk.DISABLED)
        self.skip_button.grid(column=0, row=4, columnspan=4, pady=10)

        # 完了したポモドーロ数
        self.pomodoro_label = tk.Label(text="完了したポモドーロ: 0", fg=MAIN_COLOR, bg="#FFF8E1", font=LABEL_FONT)
        self.pomodoro_label.grid(column=0, row=5, columnspan=4, pady=10)

        # 設定ボタン
        self.settings_button = tk.Button(self.window, text="設定", command=self.open_settings,
                                         bg="#607D8B", fg="white", font=BUTTON_FONT,
                                         width=button_width, height=1)
        self.settings_button.grid(column=0, row=6, columnspan=4, pady=10)

    def open_settings(self):
        settings_window = tk.Toplevel(self.window)
        settings_window.title("タイマー設定")
        settings_window.config(padx=20, pady=20, bg="#FFF8E1")
        
        # メイン画面をモーダルにする
        settings_window.grab_set()
        settings_window.transient(self.window)
        settings_window.focus_set()

        ttk.Label(settings_window, text="作業時間 (分):").grid(column=0, row=0, padx=5, pady=5, sticky="e")
        work_time = ttk.Entry(settings_window)
        work_time.insert(0, str(self.settings["work_time"]))
        work_time.grid(column=1, row=0, padx=5, pady=5)

        ttk.Label(settings_window, text="短い休憩時間 (分):").grid(column=0, row=1, padx=5, pady=5, sticky="e")
        short_break = ttk.Entry(settings_window)
        short_break.insert(0, str(self.settings["short_break_time"]))
        short_break.grid(column=1, row=1, padx=5, pady=5)

        ttk.Label(settings_window, text="長い休憩時間 (分):").grid(column=0, row=2, padx=5, pady=5, sticky="e")
        long_break = ttk.Entry(settings_window)
        long_break.insert(0, str(self.settings["long_break_time"]))
        long_break.grid(column=1, row=2, padx=5, pady=5)

        ttk.Label(settings_window, text="セッション内のフェーズ数:").grid(column=0, row=3, padx=5, pady=5, sticky="e")
        phases = ttk.Entry(settings_window)
        phases.insert(0, str(self.settings["phases_in_session"]))
        phases.grid(column=1, row=3, padx=5, pady=5)

        def save_settings():
            try:
                self.settings["work_time"] = int(work_time.get())
                self.settings["short_break_time"] = int(short_break.get())
                self.settings["long_break_time"] = int(long_break.get())
                self.settings["phases_in_session"] = int(phases.get())
                self.reset_timer()
                settings_window.destroy()
            except ValueError:
                messagebox.showerror("エラー", "すべての値は整数である必要があります。")

        save_button = ttk.Button(settings_window, text="保存", command=save_settings)
        save_button.grid(column=0, row=4, columnspan=2, pady=10)

        # ウィンドウが閉じられたときに、grab_release を呼び出す
        settings_window.protocol("WM_DELETE_WINDOW", lambda: [settings_window.grab_release(), settings_window.destroy()])

    def start_timer(self):
        if self.state == State.IDLE:
            self.state = State.RUNNING
            self.start_button.config(state=tk.DISABLED)
            self.pause_button.config(state=tk.NORMAL)
            self.resume_button.config(state=tk.DISABLED)
            self.skip_button.config(state=tk.NORMAL)
            self.countdown()

    def pause_timer(self):
        if self.state == State.RUNNING:
            self.state = State.PAUSED
            self.pause_button.config(state=tk.DISABLED)
            self.resume_button.config(state=tk.NORMAL)
            if self.timer is not None:
                self.window.after_cancel(self.timer)
                self.timer = None

    def resume_timer(self):
        if self.state == State.PAUSED:
            self.state = State.RUNNING
            self.pause_button.config(state=tk.NORMAL)
            self.resume_button.config(state=tk.DISABLED)
            self.countdown()

    def reset_timer(self):
        if self.timer is not None:
            self.window.after_cancel(self.timer)
            self.timer = None
        self.state = State.IDLE
        self.current_phase = Phase.WORK
        self.time_left = self.settings["work_time"] * Time.MINUTES
        self.current_session = 1
        self.update_timer_display()
        self.update_phase_display()
        self.start_button.config(state=tk.NORMAL)
        self.pause_button.config(state=tk.DISABLED)
        self.resume_button.config(state=tk.DISABLED)
        self.skip_button.config(state=tk.DISABLED)

    def skip_phase(self):
        if self.timer is not None:
            self.window.after_cancel(self.timer)
            self.timer = None
        self.next_phase()

    def countdown(self):
        if self.state == State.RUNNING:
            if self.time_left > 0:
                self.time_left -= 1
                self.update_timer_display()
                self.timer = self.window.after(1000, self.countdown)
            else:
                self.next_phase()

    def next_phase(self):
        if self.current_phase == Phase.WORK:
            self.pomodoros_completed += 1
            self.update_pomodoro_count()
            self.current_session += 1
            
            if self.current_session > self.settings["phases_in_session"]:
                # すべてのセッションが終了した場合
                self.current_session = 1
                self.current_phase = Phase.LONG_BREAK
                self.time_left = self.settings["long_break_time"] * Time.MINUTES
            elif self.current_session == self.settings["phases_in_session"]:
                # 最後のセッションの場合
                self.current_phase = Phase.SHORT_BREAK
                self.time_left = self.settings["short_break_time"] * Time.MINUTES
            else:
                # 通常のセッションの場合
                self.current_phase = Phase.SHORT_BREAK
                self.time_left = self.settings["short_break_time"] * Time.MINUTES
        else:
            # 休憩フェーズが終了した場合
            self.current_phase = Phase.WORK
            self.time_left = self.settings["work_time"] * Time.MINUTES
        
        self.update_phase_display()
        self.update_timer_display()
        self.state = State.IDLE
        self.start_button.config(state=tk.NORMAL)
        self.pause_button.config(state=tk.DISABLED)
        self.resume_button.config(state=tk.DISABLED)
        self.skip_button.config(state=tk.DISABLED)

    def update_phase_display(self):
        if self.current_phase == Phase.WORK:
            self.phase_label.config(text="作業", fg="#4CAF50")
        elif self.current_phase == Phase.SHORT_BREAK:
            self.phase_label.config(text="短い休憩", fg="#2196F3")
        else:
            self.phase_label.config(text="長い休憩", fg="#F44336")
        self.session_label.config(text=f"セッション: {self.current_session}/{self.settings['phases_in_session']}")

    def update_timer_display(self):
        minutes, seconds = divmod(self.time_left, 60)
        self.timer_label.config(text=f"{minutes:02d}:{seconds:02d}")

    def update_phase_display(self):
        if self.current_phase == Phase.WORK:
            self.phase_label.config(text="作業", fg="#4CAF50")
        elif self.current_phase == Phase.SHORT_BREAK:
            self.phase_label.config(text="短い休憩", fg="#2196F3")
        else:
            self.phase_label.config(text="長い休憩", fg="#F44336")
        current_session_display = min(self.current_session, self.settings['phases_in_session'])  # セッション表示の上限を設定
        self.session_label.config(text=f"セッション: {current_session_display}/{self.settings['phases_in_session']}")

    def update_pomodoro_count(self):
        self.pomodoro_label.config(text=f"完了したポモドーロ: {self.pomodoros_completed}")

    def run(self):
        self.window.mainloop()

if __name__ == "__main__":
    timer = PomodoroTimer()
    timer.run()
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2648069/28002146-037d-9ffa-e163-4965f05921f1.png)

このポモドーロタイマーアプリでは、「設定」ボタンをクリックすることで、タイマーの詳細設定を変更できます。設定画面では以下の項目をカスタマイズ可能です：

作業時間（分）
短い休憩時間（分）
長い休憩時間（分）
セッション内のフェーズ数
これらの設定を調整することで、ユーザーは自分の作業スタイルに合わせてポモドーロテクニックを最適化できます。変更後は「保存」ボタンをクリックして設定を反映させます。