# 通知設計書 14-AcceptDialog canceled

## 概要

本ドキュメントは、Godotエンジンにおける `AcceptDialog` クラスの `canceled` シグナルの設計と実装について記述する。このシグナルは、ダイアログが閉じられた時またはキャンセルボタンが押された時に発火し、ユーザーのキャンセルアクションを通知するダイアログ通知機構である。

### 本通知の処理概要

`canceled` シグナルは、AcceptDialogおよびその派生クラス（ConfirmationDialogなど）において、ユーザーがダイアログをキャンセルしたことを通知するために使用される。これにより、確認を求めた操作が取り消されたことをアプリケーションが検知し、適切な後続処理を実行できる。

**業務上の目的・背景**：ユーザーインターフェース設計において、ユーザーが操作を取り消す選択肢を提供することは重要である。canceledシグナルは、ユーザーが「キャンセル」「閉じる」「Escape」などの操作を行った際に発火し、アプリケーションがキャンセル時の適切な処理（状態の復元、一時データの破棄など）を実行するための仕組みを提供する。

**通知の送信タイミング**：canceledシグナルは、以下の状況で発火する。(1) add_cancel_button()で追加されたキャンセルボタンがクリックされた時、(2) ウィンドウのクローズボタンがクリックされた時（NOTIFICATION_WM_CLOSE_REQUEST）、(3) ui_close_dialogアクション（デフォルトでEscapeキー）が押された時（close_on_escapeがtrueの場合）、(4) 非排他的ポップアップで親ウィンドウがフォーカスを取得した時。

**通知の受信者**：canceledシグナルに `connect()` メソッドで接続されたすべてのCallable（関数、メソッド、ラムダ）が受信者となる。

**通知内容の概要**：canceledシグナルはパラメータを持たない単純なシグナルである。ユーザーがキャンセルしたという事実のみを伝達する。

**期待されるアクション**：受信者は、キャンセル時の適切な処理を実行する。例えば、一時的な変更の破棄、編集前の状態への復元、リソースの解放などが期待される。

## 通知種別

ダイアログ通知（UIシグナル）

## 送信仕様

### 基本情報

| 項目 | 内容 |
|-----|------|
| 送信方式 | 同期 |
| 優先度 | 中 |
| リトライ | 無 |

### 送信先決定ロジック

シグナル接続による。AcceptDialogの `canceled` シグナルに `connect()` メソッドで接続されたすべてのCallableが受信者となる。

## 通知テンプレート

### シグナル定義

```cpp
ADD_SIGNAL(MethodInfo("canceled"));
```

| 項目 | 内容 |
|-----|------|
| シグナル名 | canceled |
| パラメータ | なし |
| 戻り値 | なし |

### GDScript使用例

```gdscript
extends Control

func _ready():
    var dialog = ConfirmationDialog.new()
    dialog.dialog_text = "変更を保存しますか？"
    add_child(dialog)
    dialog.confirmed.connect(_on_save_confirmed)
    dialog.canceled.connect(_on_save_canceled)
    dialog.popup_centered()

func _on_save_confirmed():
    save_changes()
    print("変更が保存されました")

func _on_save_canceled():
    discard_changes()
    print("変更が破棄されました")

func save_changes():
    # 保存処理
    pass

func discard_changes():
    # 破棄処理
    pass
```

### キャンセルボタンのカスタマイズ例

```gdscript
extends AcceptDialog

func _ready():
    # カスタムキャンセルボタンを追加
    var cancel_btn = add_cancel_button("取り消し")

    canceled.connect(_on_canceled)

func _on_canceled():
    print("ダイアログがキャンセルされました")
    # キャンセル時の処理
```

## テンプレート変数

canceledシグナルはパラメータを持たないため、テンプレート変数は存在しない。

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ユーザー操作 | キャンセルボタンクリック | なし | add_cancel_button()で追加されたボタンをクリック |
| ユーザー操作 | ウィンドウクローズ | なし | ウィンドウの閉じるボタンをクリック |
| ユーザー操作 | Escapeキー | close_on_escape == true | ui_close_dialogアクションが押された |
| 自動 | 親ウィンドウフォーカス | 非排他的ポップアップ | 親ウィンドウがフォーカスを取得した時 |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| close_on_escape == false | Escapeキーでの発火が抑止される |
| is_exclusive() && !get_flag(FLAG_POPUP) | 親フォーカスによる発火が抑止される |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[キャンセルボタンクリック] --> B[_cancel_pressed呼び出し]
    C[ウィンドウクローズ] --> B
    D[Escapeキー] --> E{close_on_escape?}
    E -->|Yes| B
    E -->|No| F[処理なし]
    G[親ウィンドウフォーカス] --> H{popped_up && !exclusive && popup?}
    H -->|Yes| B
    H -->|No| F
    B --> I[popped_up = false]
    I --> J[parent_visible切断]
    J --> K[hide deferred呼び出し]
    K --> L[emit_signal canceled]
    L --> M[cancel_pressed仮想関数]
    M --> N[set_input_as_handled]
    N --> O[接続されたハンドラ実行]
```

## データベース参照・更新仕様

### 参照テーブル一覧

該当なし（AcceptDialogはデータベースを使用しない）

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| シグナル未接続 | canceledシグナルにハンドラが接続されていない | シグナルは発火するが何も起こらない |

### リトライ仕様

| 項目 | 内容 |
|-----|------|
| リトライ回数 | 0（リトライなし） |
| リトライ間隔 | N/A |
| リトライ対象エラー | N/A |

## 配信設定

### レート制限

| 項目 | 内容 |
|-----|------|
| 1分あたり上限 | 制限なし |
| 1日あたり上限 | 制限なし |

### 配信時間帯

制限なし

## セキュリティ考慮事項

- canceledシグナルは、ユーザーの明示的なアクションに基づいて発火する
- キャンセル時にデータの整合性を保つ処理を確実に実装すること
- シグナルハンドラ内で長時間のブロッキング処理を行わないこと

## 備考

- close_on_escapeプロパティでEscapeキーによるキャンセルを制御可能（デフォルト: true）
- ダイアログの非表示はcallable_mp(...&Window::hide).call_deferred()で遅延実行される
- cancel_pressed()仮想関数をオーバーライドすることで、キャンセル時の追加処理を実装可能
- canceledシグナルはconfirmedシグナルより前に発火し、その後hide()が実行される

---

## コードリーディングガイド

本通知を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: シグナル発火ロジックを理解する

canceledシグナルがどのように発火されるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | dialogs.cpp | `scene/gui/dialogs.cpp` | _cancel_pressed()関数の実装を確認 |

**読解のコツ**: _cancel_pressed()は複数の場所から呼ばれ、すべてのキャンセル経路の終点となる。

**主要処理フロー**:
1. **144-162行目**: _cancel_pressed関数全体
2. **145行目**: `popped_up = false`
3. **146-150行目**: parent_visibleの切断処理
4. **152行目**: `callable_mp((Window *)this, &Window::hide).call_deferred()`
5. **154行目**: `emit_signal(SNAME("canceled"))`
6. **156行目**: `cancel_pressed()` 仮想関数呼び出し
7. **161行目**: `set_input_as_handled()`

#### Step 2: キャンセルトリガーを理解する

canceledが発火する各経路を確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | dialogs.cpp | `scene/gui/dialogs.cpp` | _notification内のNOTIFICATION_WM_CLOSE_REQUEST |
| 2-2 | dialogs.cpp | `scene/gui/dialogs.cpp` | _input_from_window内のui_close_dialog処理 |
| 2-3 | dialogs.cpp | `scene/gui/dialogs.cpp` | _parent_focused()での処理 |
| 2-4 | dialogs.cpp | `scene/gui/dialogs.cpp` | add_cancel_button()でのボタン接続 |

**主要処理フロー**:
- **116-118行目**: `case NOTIFICATION_WM_CLOSE_REQUEST: _cancel_pressed()`
- **39-43行目**: `_input_from_window` でのEscapeキー処理
- **46-49行目**: `_parent_focused()` での親フォーカス処理
- **376行目**: `b->connect(...callable_mp(this, &AcceptDialog::_cancel_pressed))`

#### Step 3: シグナル登録を理解する

シグナルがどのように登録されるかを確認する。

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | dialogs.cpp | `scene/gui/dialogs.cpp` | _bind_methods()内のADD_SIGNAL |

**主要処理フロー**:
- **431行目**: `ADD_SIGNAL(MethodInfo("canceled"))`

### プログラム呼び出し階層図

```
キャンセルトリガー
    │
    ├─ キャンセルボタンクリック
    │      └─ Button::pressed シグナル
    │             └─ AcceptDialog::_cancel_pressed()
    │
    ├─ ウィンドウクローズボタン
    │      └─ NOTIFICATION_WM_CLOSE_REQUEST
    │             └─ AcceptDialog::_cancel_pressed()
    │
    ├─ Escapeキー（ui_close_dialog）
    │      └─ _input_from_window()
    │             └─ AcceptDialog::_cancel_pressed()
    │
    └─ 親ウィンドウフォーカス
           └─ _parent_focused()
                  └─ AcceptDialog::_cancel_pressed()
                         │
                         ├─ popped_up = false
                         │
                         ├─ parent_visible切断
                         │
                         ├─ hide().call_deferred()
                         │
                         ├─ emit_signal("canceled")
                         │      └─ 接続されたハンドラ実行
                         │
                         ├─ cancel_pressed() [仮想関数]
                         │
                         └─ set_input_as_handled()
```

### データフロー図

```
[入力]                              [処理]                              [出力]

キャンセルボタン        ───▶ Button::pressed           ───▶ _cancel_pressed()
                                                              │
ウィンドウクローズ      ───▶ WM_CLOSE_REQUEST          ───▶ _cancel_pressed()
                                                              │
Escapeキー             ───▶ _input_from_window()      ───▶ _cancel_pressed()
                                                              │
親フォーカス            ───▶ _parent_focused()         ───▶ _cancel_pressed()
                                                              │
                                                              ├─▶ canceledシグナル発火
                                                              │       │
                                                              │       └─▶ シグナルハンドラ実行
                                                              │
                                                              └─▶ ダイアログ非表示（遅延）
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| dialogs.h | `scene/gui/dialogs.h` | ヘッダ | AcceptDialogクラス定義 |
| dialogs.cpp | `scene/gui/dialogs.cpp` | ソース | AcceptDialogクラス実装 |
| window.h | `scene/main/window.h` | ヘッダ | 基底クラスWindow定義 |
| window.cpp | `scene/main/window.cpp` | ソース | 基底クラスWindow実装 |
| AcceptDialog.xml | `doc/classes/AcceptDialog.xml` | ドキュメント | APIリファレンス |
