# 通知設計書 15-AcceptDialog custom_action

## 概要

本ドキュメントは、Godotエンジンにおける `AcceptDialog` クラスの `custom_action` シグナルの設計と実装について記述する。このシグナルは、add_button()メソッドで追加されたカスタムボタンが押された時に発火し、拡張可能なダイアログ操作を実現するための通知機構である。

### 本通知の処理概要

`custom_action` シグナルは、標準のOK/キャンセル以外のカスタムアクションをダイアログに追加するための仕組みを提供する。開発者はadd_button()メソッドでカスタムボタンを追加し、そのボタンに任意のアクション文字列を関連付けることで、多様なダイアログ操作を実装できる。

**業務上の目的・背景**：多くのアプリケーションでは、単純なOK/キャンセル以外の選択肢が必要となる場面がある。例えば「保存」「保存しない」「キャンセル」の3択ダイアログ、「詳細を表示」「ヘルプ」などの補助的なボタン、設定ダイアログでの「リセット」「デフォルトに戻す」など。custom_actionシグナルは、これらの拡張的なダイアログ操作を統一的な方法で処理するための仕組みを提供する。

**通知の送信タイミング**：custom_actionシグナルは、add_button()メソッドでアクション文字列を指定して追加されたボタンがクリックされた時に発火する。アクション文字列が空の場合は発火しない。

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

**通知内容の概要**：custom_actionシグナルは、add_button()で指定されたアクション文字列（StringName型）をパラメータとして持つ。これにより、受信者はどのカスタムボタンが押されたかを識別し、適切な処理を実行できる。

**期待されるアクション**：受信者は、渡されたアクション文字列に基づいて適切な処理を分岐・実行する。ダイアログの非表示が必要な場合は、ハンドラ内で明示的にhide()を呼び出す必要がある。

## 通知種別

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

## 送信仕様

### 基本情報

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

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

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

## 通知テンプレート

### シグナル定義

```cpp
ADD_SIGNAL(MethodInfo("custom_action", PropertyInfo(Variant::STRING_NAME, "action")));
```

| 項目 | 内容 |
|-----|------|
| シグナル名 | custom_action |
| パラメータ | action (StringName) - アクション識別子 |
| 戻り値 | なし |

### GDScript使用例

```gdscript
extends Control

func _ready():
    var dialog = AcceptDialog.new()
    dialog.dialog_text = "ファイルを保存しますか？"

    # カスタムボタンを追加（アクション文字列を指定）
    dialog.add_button("保存して閉じる", true, "save_and_close")
    dialog.add_button("保存しない", false, "discard")

    add_child(dialog)

    # シグナルを接続
    dialog.confirmed.connect(_on_confirmed)
    dialog.canceled.connect(_on_canceled)
    dialog.custom_action.connect(_on_custom_action)

    dialog.popup_centered()

func _on_confirmed():
    # OKボタン（デフォルト動作）
    pass

func _on_canceled():
    # キャンセル
    pass

func _on_custom_action(action: StringName):
    match action:
        "save_and_close":
            save_file()
            close_application()
        "discard":
            close_application()

func save_file():
    print("ファイルを保存")

func close_application():
    print("アプリケーションを終了")
```

### add_button()メソッドの詳細

```cpp
Button *add_button(const String &p_text, bool p_right = false, const String &p_action = "");
```

| パラメータ | 型 | デフォルト値 | 説明 |
|----------|-----|------------|------|
| p_text | String | 必須 | ボタンに表示するテキスト |
| p_right | bool | false | trueの場合、ボタンを右側に配置 |
| p_action | String | "" | アクション識別子（空の場合custom_action未発火） |

## テンプレート変数

| 変数名 | 説明 | データ取得元 | 必須 |
|--------|------|-------------|-----|
| action | カスタムアクション識別子 | add_button()のp_actionパラメータ | Yes |

## 送信トリガー・条件

### トリガー一覧

| トリガー種別 | トリガーイベント | 送信条件 | 説明 |
|------------|----------------|---------|------|
| ユーザー操作 | カスタムボタンクリック | p_action が空でない | add_button()で追加されたボタンをクリック |

### 送信抑止条件

| 条件 | 説明 |
|-----|------|
| p_action が空文字列 | アクション文字列が指定されていない場合、シグナルは発火しない |

## 処理フロー

### 送信フロー

```mermaid
flowchart TD
    A[add_button呼び出し] --> B{p_action 空?}
    B -->|Yes| C[ボタン追加のみ]
    B -->|No| D[ボタン追加 + _custom_action接続]
    C --> E[ボタン表示]
    D --> E
    E --> F[ユーザーがボタンクリック]
    F --> G{p_action接続済み?}
    G -->|Yes| H[_custom_action呼び出し]
    G -->|No| I[処理なし]
    H --> J[emit_signal custom_action]
    J --> K[custom_action仮想関数]
    K --> L[接続されたハンドラ実行]
```

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

### 参照テーブル一覧

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

### 更新テーブル一覧

該当なし

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 対処方法 |
|----------|---------|---------|
| シグナル未接続 | custom_actionシグナルにハンドラが接続されていない | シグナルは発火するが何も起こらない |
| 不明なアクション | ハンドラで想定外のアクション文字列を受信 | ハンドラ内でデフォルト処理を実装 |

### リトライ仕様

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

## 配信設定

### レート制限

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

### 配信時間帯

制限なし

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

- アクション文字列は開発者が定義するため、予期しない文字列に対する防御コードを実装すること
- シグナルハンドラ内でユーザー入力を信頼せず、適切なバリデーションを行うこと
- シグナルハンドラ内で長時間のブロッキング処理を行わないこと

## 備考

- custom_actionシグナル発火後、ダイアログは自動的に非表示にならない
- ダイアログを閉じる必要がある場合は、ハンドラ内で明示的にhide()を呼び出すこと
- custom_action()仮想関数をオーバーライドすることで、派生クラスでのカスタム処理も実装可能
- remove_button()メソッドでカスタムボタンを削除可能

---

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

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

### 推奨読解順序

#### Step 1: add_button()を理解する

カスタムボタンの追加方法を確認する。

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

**読解のコツ**: p_actionが空でない場合のみ_custom_actionに接続される点に注目。

**主要処理フロー**:
1. **339-366行目**: add_button関数全体
2. **340-341行目**: Buttonオブジェクトの作成とテキスト設定
3. **343-351行目**: ボタンの配置位置決定（p_right）
4. **361-363行目**: p_actionが空でない場合の接続
   - `button->connect(SceneStringName(pressed), callable_mp(this, &AcceptDialog::_custom_action).bind(p_action))`

#### Step 2: _custom_action()を理解する

シグナル発火処理を確認する。

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

**主要処理フロー**:
1. **327-330行目**: _custom_action関数
2. **328行目**: `emit_signal(SNAME("custom_action"), p_action)`
3. **329行目**: `custom_action(p_action)` 仮想関数呼び出し

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

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

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

**主要処理フロー**:
- **432行目**: `ADD_SIGNAL(MethodInfo("custom_action", PropertyInfo(Variant::STRING_NAME, "action")))`

#### Step 4: remove_button()を理解する

カスタムボタンの削除方法を確認する。

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

**主要処理フロー**:
- **381-410行目**: remove_button関数全体
- **392-394行目**: _custom_actionとの接続解除

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

```
add_button("テキスト", right, "action")
    │
    ├─ Button オブジェクト作成
    │
    ├─ buttons_hbox に追加
    │
    └─ p_action が空でない場合
           └─ button.pressed.connect(_custom_action.bind(p_action))

カスタムボタンクリック
    │
    └─ Button::pressed シグナル
           │
           └─ AcceptDialog::_custom_action(p_action)
                  │
                  ├─ emit_signal("custom_action", p_action)
                  │      └─ 接続されたハンドラ実行
                  │
                  └─ custom_action(p_action) [仮想関数]
```

### データフロー図

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

add_button()呼び出し   ───▶ Button作成             ───▶ ボタン表示
（p_action指定）              │
                              └─▶ pressed接続（p_actionバインド）

カスタムボタンクリック  ───▶ Button::pressed        ───▶ _custom_action(action)
                                                         │
                                                         ├─▶ custom_actionシグナル発火
                                                         │       │
                                                         │       └─▶ action文字列を含む
                                                         │
                                                         └─▶ シグナルハンドラ実行
                                                                 │
                                                                 └─▶ actionに基づく分岐処理
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| dialogs.h | `scene/gui/dialogs.h` | ヘッダ | AcceptDialogクラス定義 |
| dialogs.cpp | `scene/gui/dialogs.cpp` | ソース | AcceptDialogクラス実装 |
| button.h | `scene/gui/button.h` | ヘッダ | カスタムボタンのButton定義 |
| AcceptDialog.xml | `doc/classes/AcceptDialog.xml` | ドキュメント | APIリファレンス |
