ポモドーロタイマーとToDoリストの機能があるデスクトップアプリ作ってみた

## 1. はじめに

仕事中ポモドーロタイマーをよく使うのですが、時間表示とともにToDoリストも一緒に確認できたら良いのになと思っていました。そのようなアプリが無いか探してみたところ、[Forcus To-DO](https://www.sungrove.co.jp/focus-to-do/)というアプリがありました。

ですが、せっかくなので（自身のスキルアップのためにも）自分好みのテーマや機能が実装されたポモドーロタイマー＆ToDoリストを開発してみました。


## 2. アプリ概要

完成したアプリは以下のようなものです。
ポモドーロタイマーとToDoリストの2つの機能があります。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3577213/14ab8fcb-48b0-4c02-aec4-d1b95c359381.png" width="300">


- **ポモドーロタイマー機能**
  - 作業時間・休憩時間を設定可能
  - 残り時間の表示
  - 一時停止・再開・リセット機能
- **ToDoリスト機能**
  - タスクの追加・削除・並び替え
  - タスクの開始・終了時間設定
  - タスクの完了状態の管理

## 3. 使用したGUI
今回は `PySide6` を使用してGUIを構築しています。

#### **PySide6 の特徴と採用理由**

- Qt を Python で扱うための公式バインディング
- クロスプラットフォーム対応で Windows / Mac / Linux で動作可能
- **PyQt** と異なり LGPL ライセンスのため、商用利用時の制限が緩やか（商用利用の予定は無いですが）

## 4. コードの構成と解説

本アプリの主な構成は以下の通りです。
機能ごとにクラスや関数を分割することで、可読性や拡張性を向上しています。

```
WindowsClockStylePomodoro (QMainWindow)
├── タイマー管理
│   ├── toggle_timer() - タイマーの開始・停止を切り替え
│   ├── update_timer() - 1秒ごとにカウントダウン
│   ├── reset_timer() - タイマーのリセット
│   ├── update_work_time() - 作業時間の設定更新
│   ├── update_break_time() - 休憩時間の設定更新
│
├── ToDoリスト管理
│   ├── update_todo_listbox() - ToDoリストのUI更新
│   ├── save_todos() / load_todos() - タスクデータの保存・読み込み
│   ├── add_task() / remove_task() - タスクの追加・削除
│   ├── on_rows_moved() - タスクの並び替えを反映
│
TodoItemWidget (QWidget)
    ├── toggle_completed() - タスクの完了状態を切り替え
    ├── handle_double_click() - タスク名の編集開始
    ├── finish_editing() - タスク編集の終了
    ├── update_time() - タスクの開始・終了時間を更新
    ├── add_task_below() - タスクを下に追加
    ├── remove_this_task() - タスクを削除
    ├── update_number() - タスクの番号を更新
```

### **クラスの関係**
**WindowsClockStylePomodoro (QMainWindow)：**
アプリ全体のメインウィンドウを管理し、ポモドーロタイマーとToDoリストの両方を統括します。
【主な役割】
- タイマーの管理 (作業時間・休憩時間のカウントダウン、開始・停止、リセット)
- ToDoリストの管理 (タスクの追加・削除、並び替え、状態の保存・読み込み)
- UIの管理 (アプリ全体のレイアウト設定、日付と時刻の表示)


**TodoItemWidget (QWidget)：**
個々のタスクを管理し、ユーザーがToDoリストを操作するためのウィジェットです。
【主な役割】
- タスクの表示・編集・削除
- タスクの完了状態の管理
- タスクの時間 (開始・終了時間) の設定


**親子関係:**
`WindowsClockStylePomodoro` が `QListWidget` を使用して ToDo リストを管理し、その中の各タスクを `TodoItemWidget` として生成・管理します。`TodoItemWidget` は `WindowsClockStylePomodoro` のメソッドを呼び出してタスクの完了状態の更新やデータの保存を行います。

## 5. 工夫した点


1. **クラス設計の分割**
   - `WindowsClockStylePomodoro` クラスでタイマー機能を管理し、`TodoItemWidget` クラスでToDoリストの各タスクを管理することで、機能の分離と拡張性を向上させています。

2. **タスクのドラッグ&ドロップによる並び替え**
   - `QListWidget` を活用し、タスクを自由に並び替えられるようにしています。

3. **タスクごとに開始・終了時間を設定可能**
   - 各タスクに `QComboBox` を用いて開始・終了時間を設定できるようにし、スケジュール管理がしやすくなっています。

4. **スタイルの統一とWindowsらしいデザイン**
   - `qt-material` を使用してスタイルを整え、Windowsのデフォルト時計に近いデザインを目指しました（まだ違和感はありますが）。

5. **JSONファイルによるタスクの永続化**
   - アプリを閉じてもタスクが保存され、次回起動時に復元できるようにしています。

6. **アプリの上部に日時を表示**
   - update_date_time() 関数を使用し、アプリの上部に現在の日付と時刻をリアルタイムで表示。`QTimer`を用いることで、最新の日時が常に確認できるようになっています。

## 6. 内部処理の分岐と解説

ユーザーの操作やシステムの状態に応じてさまざまな分岐処理を実装しています。ここでは、代表的な処理を詳細に解説します。

### **(1) タイマー終了時の処理**

ポモドーロタイマーのカウントダウンが終了すると、作業時間・休憩時間のどちらなのかによって処理が分岐します。

```python
def update_timer(self):
    self.time_left -= 1
    
    if self.time_left <= 0:
        self.timer.stop()
        if not self.is_break:
            QMessageBox.information(self, "作業終了", "作業時間が終了しました！休憩しましょう。")
            self.is_break = True
            self.time_left = self.break_time_combo.currentData() * 60
        else:
            QMessageBox.information(self, "休憩終了", "休憩時間が終了しました！作業を再開しましょう。")
            self.is_break = False
            self.time_left = self.work_time_combo.currentData() * 60
    
    self.update_timer_display()
```

**解説：**
- `self.time_left <= 0` のとき、タイマーを一時停止
- `self.is_break` の状態によって、作業時間が終了したのか、休憩時間が終了したのかを判定
- `QMessageBox` でユーザーに通知を出し、`self.is_break` を切り替える
- 休憩時間が終わった場合は `self.time_left` に作業時間を設定し、次のサイクルに移行する

### **(2) タスクの完了状態の切り替え**

```python
def toggle_completed(self, event):
    self.task_data["completed"] = not self.task_data["completed"]
    self.check_label.setText("✓" if self.task_data["completed"] else "□")
    self.task_label.setStyleSheet("color: #707070;" if self.task_data["completed"] else "color: #000000; font-weight: bold;")
    self.main_window.save_todos()
```

**解説：**
- `self.task_data["completed"]` を `True` / `False` に切り替え
- チェックマーク (`✓` or `□`) を更新
- スタイルを変えて、完了タスクはグレーアウト
- `self.main_window.save_todos()` で変更を即座に保存

この処理によって、タスクをクリックするだけで状態を変更でき、完了したタスクは自動的に視認しやすい形で表示されます。

## 7. まとめ

今回はポモドーロタイマーとToDoリストを組み合わせた自作アプリについて紹介しました。
コードのすべては長いので割愛していますが、自分の好みや必要な機能だけをカスタマイズしたアプリが作れたので楽しかったです。今後も個人開発続けてみたいと思います！