# the\_app

アプリケーションは`setup()`,`loop()`の２つの関数で記述されますが、例えば１画面ごとに大きく振る舞いが違うもの（ここではサブアプリと呼びます）を複数定義して、メニューで切り替えるといった動作を行う場合は、そのまま`setup()`,`loop()`内に記述しては煩雑になります。また変数についても管理する必要があります。

setup(), loop(), 関連する変数の切り替えを簡素化するためMWM5ライブラリでは以下の2つの仕組みを実装しています。

* サブアプリ : `setup()`, `loop()`, `関連変数`をクラスとしてまとめ、切り替える
* アプリハンドラ: サブアプリ内で `setup()`, `loop()` を切り替える

## サブアプリ

```cpp
#include <mwm5.h>

// サブアプリ１
class App_Foo : public class APP_DEF {
public:
  void setup() { ... }
  void loop() { ... }
}

// サブアプリ２
class App_Bar : public class APP_DEF {
public:
  void setup() { ... }
  void loop() { ... }
}

// サブアプリ切り替え関数
static int s_change_app(
              TWE::APP_MGR& the_app, int n_appsel
            , int prev_app, int exit_id) {
    if (n_appsel == 1) the_app.new_app<App_Foo>();
    else if (n_appsel == 2) the_app.new_app<App_Bar>();
    else n_appsel = 0;
    
    return n_appsel;
}

// setup
void setup() {
    the_app.setup(s_change_app); // サブアプリ切り替え関数の登録
    the_app.new_app<App_Foo>(); // 最初に起動するサブアプリ
}

// loop()
void loop() {
  	the_app.loop(); // 必須の呼び出し
}
```

上記例は説明に必要な最小限のコードのみを記述します。`APP_DEF`を継承した`App_Foo`,`App_Bar`が、２種類のサブアプリの定義となり、各々が`setup()`,`loop()`メンバー関数を定義しています。実際には振る舞いごとに必要なメンバー変数やその他メンバー関数が定義されます。サブアプリの登録や切り替えはライブラリ定義のクラスオブジェクト`the_app`に対して操作を行います。`s_change_app()`関数はサブアプリを切り替える要求（サブアプリ終了時など）に対して、実際の切り替え部を実装していて、この関数を`setup()`中で呼び出す`the_app.setup()`のパラメータとして渡しておきます。`loop()`中には`the_app.loop()`を記述しておきます。

### サブアプリの定義

```cpp
class App_Foo : public class APP_DEF {
public:
  void setup() { ... }
  void loop() { ... }
}
```

`void setup()`と`void loop()`を記述しておきます。setup()はサブアプリが初期化されるときに1度だけ呼び出されます。`loop()`は`the_app.loop()`関数中で呼び出されます。

### サブアプリの切り替え

```cpp
// APP_MGR内のtypedef
typedef int (*PF_CHANGE_APP)(
    THE_APP_MGR& the_app,
    int n_appsel,
    int prev_app,
    int exit_id);

// 例: App_Foo と App_Bar で切り替える
int s_change_app(
              TWE::APP_MGR& the_app, int n_appsel
            , int prev_app, int exit_id) {
    if (n_appsel == 1 || n_appsel == -1)
        the_app.new_app<App_Foo>();
    else if (n_appsel == 2)
        the_app.new_app<App_Bar>();
    else n_appsel = 0;
    
    return n_appsel;
}
```

実行中のサブアプリが終了する場合に呼び出されます。サブアプリは1以上のIDにより種別を管理し、切り替えのために関数`int (*)(THE_APP_MGR&, int, int, int)`を実装しておきます。

パラメータ`n_appsel`には、切り替えたいアプリケーションのIDが渡されます。パラメータは`the_app.exit()`で指定した値です。`-1`が渡された場合は、デフォルトのサブアプリに切り替えるよう記述しておきます。

* パラメータ`prev_app`には、直前のサブアプリIDです。直前が無ければ0となります。
* パラメータ`exit_id`は、サブアプリが`the_app.exit()`で指定した値です。

{% hint style="info" %}
`n_appsel`で次に動作させるサブアプリを指定し、`exit_id`はサブアプリに対するパラメータとして用います。
{% endhint %}

サブアプリ切り替え関数内部では`the_app.new_app<T>()`（`T`は次に生成するサブアプリのクラス）を呼び出しています。この呼び出しによりクラス`T`のオブジェクトが生成され、同時にこれまで動作していたサブアプリのオブジェクトは破棄されます。

### the\_app.new\_app\<T>()

```cpp
template <class T, class... Args>
void APP_MGR::new_app(Args&&... args)

// setup 例
void setup() {
    the_app.setup(s_change_app);
    the_app.new_app<App_Foo>(); // 最初に起動するサブアプリ
}

// サブアプリ切り替え関数例
static int s_change_app(
              TWE::APP_MGR& the_app, int n_appsel
            , int prev_app, int exit_id) {
    if (n_appsel == 1 || n_appsel == -1)
        the_app.new_app<App_Bar>();
    }
}
```

`setup()`とサブアプリ切り替え関数内で呼び出し、サブアプリのオブジェクトを生成し、同時に現在のサブアプリを破棄します。

{% hint style="info" %}
最初に動作させるサブアプリは`setup()`で生成します。
{% endhint %}

* テンプレートパラメータ`T`はサブアプリ定義クラスで`APP_DEF`を継承している必要があります。
* テンプレートパラメータ`args`は、コンストラクタに渡されるパラメータです。オブジェクトは`new T(std::forward(args)...)`で生成されます。サブアプリがパラメータを指定しないデフォルトコンストラクタで構築される場合は何も指定しません。パラメータを指定する場合はサブアプリのコンストラクタの定義が必要です（例えば`App_Foo::App_Bar(int)`を定義しておいて`the_app.new_app<App_Bar>(exit_id)`）。

### the\_app.exit()

```cpp
void APP_MGR::exit(int exit_code, int next_app = NEXT_APP_DEFAULT)

// loop() の呼び出し階層
loop() // 大本のloop()
  the_app.loop() // APP_MGRのloop()
  
    App_Foo::loop() // サブアプリのloop()
      ...
      if (..) { // 何かの条件でサブアプリを終了する
        the_app.exit(0);
        return; // 通常はここでreturn
      }
    // App_Foo::loop()が終了した
    
    // exit がコールされていた場合は、
    // サブアプリ切り替え関数を呼び出し、
    // 次のサブアプリに切り替える。
  }
}
```

サブアプリの`loop()`内で呼び出し、**`loop()`を抜けた直後に**サブアプリを切り替えます。

上記の例のように大本の`loop()`からは`the_app.loop()`が呼び出され、さらにサブアプリの`loop()`が呼び出されます。サブアプリの`loop()`中で`the_app.exit()`が呼び出されると、`the_app.loop()`内でアプリケーションの切り替えが行われます。実際に切り替えているのはユーザが定義するサブアプリ切り替え関数です。

* パラメータ`exit_code`は、サブアプリの終了コードを指定します。任意の数を指定できます。
* パラメータ`next_app`は、次のサブアプリを明示的に指定する意味合いの識別子です。`-1`はデフォルトのサブアプリ、`0`は切り替えを行わない、`1`以上が次のサブアプリの識別子と意味付けをしています。

{% hint style="info" %}
`exit_code`と`next_app`は、そのままサブアプリ切り替え関数に渡されます。サブアプリ切り替え関数は、その2つのパラメータを参照して次のサブアプリを決定します。
{% endhint %}

### the\_app.query\_appobj()

```cpp
APP_DEF* APP_MGR::query_appobj() // the_app.qurery_appobj()

// サブアプリ定義クラス内で利用
void APPDEF::set_appobj(void* p)
void* APPDEF::get_appobj()

// サブアプリ例
class App_Foo {
  TWETerm_M5_Console _scr;
public:
  App_Foo : _scr(..) {
    // 共通オブジェクトの登録
    set_appobj((void*)static_cast<ITerm*>(&_scr));
  }
  void setup();
  void loop();
};

// 大本のloop()
void loop() {
  ..
  // APPDEFオブジェクトの取得
  if (auto p = the_app.query_appobj()) {
    // 登録されたvoid*をITerm*へ強制キャスト
    if (ITerm* ptrm = reinterpret_cast<ITerm*>(p->get_appobj())) {
      ... // ptrm を用いた操作
    }
  }
}


```

大本の`loop()`から`the_app`で生成したサブアプリのクラスオブジェクトを参照し、さらにサブアプリが指定した`void*`型のポインタデータを取得します。

例えば、サブアプリ構築時に共通のクラスオブジェクトを持っていて（例えばメイン画面となるターミナル画面）、大本の`loop()`から共通のオブジェクトにアクセスします（例えばメイン画面上の文字列を取得）。

## アプリハンドラ

アプリハンドラ`APP_HNDLR`は、**サブアプリ中**での簡易的な`setup()`,`loop()`処理の切り替えを行います。例えば、リスト一覧→リスト選択後、エラーといった複数の画面を切り替えるような動作です。

サブアプリの実装では`APPDEF`を継承しましたが、さらに`APP_HNDLR<S>`（Sはサブアプリクラスを指定します）を継承します。サブアプリ中に処理関数（アプリハンドラ）を実装し、アプリハンドラを切り替えます。

```cpp
class App_Foo : public APPDEF, public APP_HNDLR<App_Foo> {
  // アプリハンドラ１
	void hndr_scr_def(event_type ev, arg_type arg = 0) {
		switch (ev) {
			case EV_SETUP:
			  // setup()に相当、最初に1回だけ呼ばれる
			break;
			case EV_LOOP:
			  // loop()に相当
        if (..) { // ある条件で次のアプリハンドラに遷移したい
          // アプリハンドラ切り替え
          APP_HNDLR::new_hndlr(&App_Foo::hndlr_scr_next);
          return; // 通常はここでreturn
        }
			break;
			case EV_EXIT;
			  // 終了時＝アプリハンドラ切り替え時に呼び出される
			break;
	}
	
	// ハンドラ２
	void hndlr_scr_next(event_type ev, arg_type arg = 0) {
	  ..
	}
	
public:
  ~App_Foo() { 
    APP_HNDLR::on_close();
  }
  void setup() {
    ...
    
    // 最初のアプリハンドラを登録する
    APP_HNDLR::new_hndlr(&App_Commander::hndr_screen_simple);
  }
  
  void loop() {
    // 必須呼び出し
    APP_HNDLR::loop();
    ...
  }
};
```

上記がアプリハンドラを用いたときのソース構造の例です。

`hndr_scr_def()`と`hndr_scr_next()`がアプリハンドラ関数です。パラメータとして`ev`と`arg`が与えられ、`ev`は`EV_SETUP`,`EV_LOOP`,`EV_EXIT`のいずれかになり、`setup()`,`loop()`と終了処理に対応します。

### アプリハンドラ関数

```cpp
// APP_HNDLR<T>内のtypedef
typedef void (T::* tpf_func_handler)(event_type ev, arg_type evarg)
```

アプリハンドラ関数は`setup()`,`loop()`に相当する処理を行います。加えてハンドラが切り替えられる、つまり現在登録されているハンドラが終了するときの処理を記述できます。

```cpp
class App_Foo : public APP_DEF, public APP_HNDLR<App_Foo> {
  ..
	void hndr_scr_def(event_type ev, arg_type arg = 0) {
		switch (ev) {
			case EV_SETUP:
			  // setup()に相当、最初に1回だけ呼ばれる
			break;
			case EV_LOOP:
			  // loop()に相当
        if (..) { // ある条件で次のアプリハンドラに遷移したい
          // アプリハンドラ切り替え
          APP_HNDLR::new_hndlr(&App_Foo::hndlr_scr_next);
          return; // 通常はここでreturn
        }
			break;
			case EV_EXIT;
			  // 終了時＝アプリハンドラ切り替え時に呼び出される
			  // 注: サブアプリのデストラクタで APPHNDLR::on_close() を
			  //     記述してください
			break;
	}
};
```

パラメータ`ev`に対応した処理を記述します。

|            |                                      |
| ---------- | ------------------------------------ |
| `EV_SETUP` | setup()に相当し、アプリハンドラが登録・切替時に呼び出される。   |
| `EV_LOOP`  | loop()に相当し、都度呼び出される。                 |
| `EV_EXIT`  | アプリハンドラの切り替えられ、現在のハンドラが終了する際に呼び出される。 |

パラメータ`arg`は、`APP_HNDLR::new_hnbdlr()`や`APP_HNDLR::loop()`でパラメータが与えられたときに、値が格納されます。

### APP\_HNDLR::new\_hndlr()

```cpp
void APP_HNDLR::new_hndlr(tpf_func_handler hnd_next, arg_type arg = 0)
```

アプリハンドラを初回生成する（サブアプリクラスの`setup()`）、またはアプリハンドラ内から次のアプリハンドラへ切り替えます。

* パラメータ`hnd_next`はアプリハンドラ関数（メンバー関数のポインタ）を指定します。
* パラメータ`arg`はアプリハンドラ関数の`EV_SETUP`呼び出し時のパラメータを指定します。（`EV_SETUP`による初期化時の処理を分岐したいときに利用します）

### APP\_HNDLR::loop()

```cpp
void APP_HNDLR::loop(arg_type arg = 0)
```

サブアプリの`loop()`から毎回呼び出します。

* パラメータ`arg`を指定するとアプリハンドラにその値が渡されます。

### APP\_HNDLR::on\_close()

```cpp
void APP_HNDLR::on_close()
```

サブアプリのデストラクタから呼び出すようにしてください。

この記述を省略した場合、サブアプリのオブジェクトが破棄されるときに、アプリハンドラも破棄されますが `EX_EXIT` メッセージが呼び出されません。

## アプリハンドラごとの独自データ

アプリハンドラは、サブアプリ内でsetup(),loop()コンテキスト切り替えの仕組みで、サブアプリ下でのメンバー関数の実行という携帯です。そのため、アクセスできるデータはサブアプリのメンバー変数となります。

ここでは、アプリハンドラ独自のデータを保持するクラスを追加します。

### データを格納するクラスの準備

APPHNDLR\_DCを継承したクラスを準備します。このクラスにはアプリハンドラ内で使用するデータやメンバー関数を定義します。以下の例では`DC_MyAppHndlr`というクラスを定義しています。

```cpp
/// APPHNDLR_DCを継承したデータ格納クラスの定義 
// publicアクセスで良いのでclassではなくstructで定義しています
struct DC_MyAppHndlr : public APPHNDLR_DC {
  static const int CLS_ID = 0;
	int get_class_id() { return CLS_ID; }
	
	MySubApp &app;
	
	// メンバ変数と操作関数
  int data1;
  void incr() { data1++ }
  
  // サブアプリ内のthe_screenに対する操作
  void display() { app.the_screen << data1; }
  
  // コンストラクタ
  DC_MyAppHndlr(MySubApp& app_) : 
              app(app_) // サブアプリオブジェクトを保存
            , APPHNDLR_DC(CLS_ID) // APPHNDLR_DCの初期化
            , data1(0) // メンバ変数の初期化
            {}
}
```

以下の定義を行う必要があります。

* `static const int CLS_ID` : データ格納クラス種別を識別するための個別のIDです。
* `int get_class_id()` : `CLS_ID`を戻すメンバ関数です。
* コンストラクタ : サブアプリクラスオブジェクトをパラメータとします。このオブジェクトは上記の例ではappに保存し`DC_MyAppHndlr`からサブアプリにアクセスする目的で利用します。
* 継承したAPPHNDLR\_DCを`APPHNDLR_DC(CLS_ID)`として初期化します。

### サブアプリでのfriend宣言

`DC_MyAppHndlr`はサブアプリのメンバー変数や関数にアクセスすることも多いため`friend`宣言しておきます。もちろん一般のクラス設計のようにpublicインタフェースを用意してカプセル化してもよいのですが、この場合、カプセル化の利点があまりなく記述が煩雑になるためです。

```cpp
struct DC_My_AppHndlr;

//サブアプリ定義
class MySubApp : public TWE::APP_DEF, public APP_HNDLR<MySubApp> {
  ..
  
  void hndr_MyApp(event_type ev, arg_type arg = 0); // アプリハンドラ関数
  frined struct DC_MyAppHndlr; // friend宣言
}
```

### アプリハンドラ内での利用

アプリハンドラでは`APP_HNDLR::use<>()`を用いてデータオブジェクトを取得します。オブジェクトの生成・破棄は暗黙に行われます。別のアプリハンドラに切り替えられたときにデータオブジェクトが破棄されます。

```cpp
void MySubApp::hndr_MyApp(event_type ev, arg_type arg) {
  auto&& dc = APP_HNDLR::use<DC_My_AppHndlr>();
  
  switch(ev) {
  case EV_SETUP:
    ...
  break;
  
  case EV_LOOP:
    if (..) dc.incr();
  break;
  
  case EV_EXIT:
  break;
  }
}
```

上記の例では、アプリハンドラ関数の最初で`dc`を取得しています。初回呼び出しにオブジェクトが生成され、それ以降は生成されたオブジェクトを参照します。

あとは生成された`dc`オブジェクトを操作します。
