151
186

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2024-09-14

はじめに

image.png

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

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

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

1. 要件定義

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

1.1 機能要件

image.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

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

2. 仕様策定

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

2.1 タイマー仕様

image.png

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

2.2 UI仕様

image.png

image.png

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

2.3 デザイン仕様

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

3. 設計

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

3.1 クラス設計

3.2 状態遷移設計

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

3.3 シーケンス図

4. 実装

設計に基づいて、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. 状態管理の明確化

    • PhaseState の列挙型を使用して、タイマーのフェーズと状態を明確に定義しました。
    • これにより、複雑な状態遷移を管理しやすくなります。
  3. イベントドリブンプログラミング

    • Tkinterのイベントシステムを活用し、ユーザーのアクションに応じて適切なメソッドを呼び出します。
    • ボタンクリックなどのイベントと、それに対応するメソッドを明確に関連付けています。
  4. 非同期タイマーの実装

    • after メソッドを使用して、非ブロッキングなタイマー機能を実現しています。
    • これにより、UIの応答性を保ちながら、正確なタイミング管理が可能になります。
  5. ユーザーインターフェースの動的更新

    • タイマーの状態やフェーズの変更に応じて、UIを動的に更新します。
    • ラベルやボタンの状態を適切に管理し、現在の状況を明確にユーザーに伝えます。
  6. エラー処理とエッジケースの考慮

    • タイマーの一時停止や再開、リセット時の処理を適切に実装し、予期せぬ動作を防止しています。
    • 各メソッドで現在の状態をチェックし、不適切な操作を防いでいます。
  7. モジュール化と関数の分離

    • 各機能(開始、一時停止、再開、リセットなど)を別々のメソッドとして実装し、コードの可読性を向上させています。
    • UI更新、タイマー制御、状態管理などの機能を明確に分離しています。
  8. 定数の活用

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

    • f文字列を使用して、文字列フォーマットを簡潔に記述しています。
    • divmod 関数を使用して、秒を分と秒に効率的に変換しています。
  10. UIデザインの考慮

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

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

まとめ

napkin-selection (3).png

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

  1. 統合的なソフトウェア開発プロセス

    • 要件定義から実装まで、ソフトウェア開発の全段階を実践的に学びました。
    • 各段階の重要性と相互関連性を理解することができました。
  2. Pythonとオブジェクト指向プログラミングの実践

    • Pythonの強力な機能(列挙型、f文字列、divmod関数など)を実際のプロジェクトで活用しました。
    • クラスベースの設計を通じて、オブジェクト指向プログラミングの原則を適用しました。
  3. GUIプログラミングの基礎

    • Tkinterを使用して、実用的でユーザーフレンドリーなインターフェースを設計・実装しました。
    • イベントドリブンプログラミングの概念を理解し、応用しました。
  4. 状態管理と非同期プログラミング

    • 複雑な状態遷移を管理する技術を学びました。
    • 非同期タイマーの実装を通じて、応答性の高いアプリケーション開発のテクニックを習得しました。
  5. ソフトウェアアーキテクチャの重要性

    • モジュール化、関数の分離、適切な定数の使用など、保守性の高いコード構造の重要性を理解しました。
  6. 実践的な問題解決スキル

    • エラー処理やエッジケースの考慮など、実世界のアプリケーション開発で直面する課題に取り組みました。

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

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

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

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

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

参考リソース

Happy coding!

image.png

コメント対応 : 実装

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

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

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

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

151
186
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
151
186

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?