今日は雪が降ってかなり寒いのですが、こんばんはぺろりです。

今回はREAPERのメインメニューにあるExtensionメニューの実装について書いていきます。

まずはDLLのエントリポイントで以下のようなコールバック登録とExtensionMenu作成を行います。
// Menuに反応するコールバック
void HookCustomActionMenu(const char* menustr, HMENU hMenu, int flag);
// Actionの有効・無効の問い合わせでMenu意外でもあちこちから呼ばれるコールバック
int HookToggleAction( int actionID, int flag );
// REAPER_PLUGIN_ENTRYPOINT()でのコールバック登録 plugin_register( "toggleaction", (void*)HookToggleAction ); plugin_register( "hookcustommenu", (void*)HookCustomActionMenu );

// ExtensionMenuを作成
// (SWSなど他のExtensionが先に行っているという場合もあるがかぶっても問題なし)
AddExtensionsMainMenu();
これでMainMenuの仕組みからコールバックが呼ばれるようになります。

また、呼ばれるタイミングはメニュー生成時とメニュー呼び出し時の2種類があります。
呼び出し元はExtensionメニューだけでなく、あらゆるMainメニュー項目から呼び出されるのですが、どこから呼ばれたかは上の例だと HookCustomActionMenu() の引数 menustr を見ると分かります。
(MainメニューのExtensionsをクリックすると"Main extensions"という文字列がやってきます)

■Menu生成時の処理
例えば、MainメニューのExtensionsを初めてクリックした時、Extensions以下のメニュー項目の作成を行うため、コールバック(HookCustomActionMenu)が呼ばれます。
この時、引数の flag=0 になっていますので、以下のような実装でメニュー項目を追加します。
// Extensionメニューにセパレータ、サブメニュー、メニュー項目を追加してみるテスト
if ( 0 == flag )
{
	if ( 0 == strcmp( menustr, "Main extensions" ) )
	{
		MENUITEMINFO mi = { sizeof(MENUITEMINFO), };
		// まずセパレータ追加
		mi.fType = MFT_SEPARATOR;
		mi.fMask = MIIM_TYPE;
		InsertMenuItem( hMenu, GetMenuItemCount( hMenu ), true, &mi );
		// サブメニューを追加
		HMENU hSubMenu = CreatePopupMenu();
		mi.fMask    = MIIM_SUBMENU | MIIM_TYPE | MIIM_STATE;
		mi.fState   = MFS_UNCHECKED;
		mi.fType    = MFT_STRING;
		mi.hSubMenu = hSubMenu;
		mi.dwTypeData = L"Pelori";
		InsertMenuItem( hMenu, GetMenuItemCount( hMenu ), true, &mi );
		// メニュー項目追加
		const int cmdID = 0; // ここは自分で事前に登録したActionIDをセット
		mi.fMask  = MIIM_TYPE | MIIM_ID | MIIM_STATE;
		mi.fType  = MFT_STRING;
		mi.dwTypeData = L"Test Action"; // Menuに表示されるテキスト
		mi.fState = MFS_UNCHECKED;
		mi.wID    = cmdID; // このメニュー項目で実行するActionIDを指定
		InsertMenuItem( hSubMenu, GetMenuItemCount( hSubMenu ), true, &mi );
	}
}
これでメニュー項目の登録が完了。次はこれを使う時の処理です。
(ちなみにこれはWindows版の場合です。何をやっているか意味不明な方はWin32APIのメニューまわりを調べてください。Mac版とかの方はよく知りませんごめんなさい)


■Menu呼び出し時の処理
Extensionsメニューなどをクリックすると、今度は flag=1 になって HookCustomActionMenu() が呼ばれますが、今のところはここで特に何もする必要はありません。
そして HookCustomActionMenu() の次に HookToggleAction() コールバックが呼び出されます。
これはメニュー項目のチェック表示をON/OFFするために、ActionIDに対する有効・無効状態をREAPERが問い合わせてくるというものです。
int HookToggleAction( int actionID, int flag )
{
	// 戻り値の仕様
	// -1: 自分のExtensionと関係ない、もしくはON/OFFといったトグルを行わない
	//  0: actionIDに対応するアクションは無効状態です
	//  1: actionIDに対応するアクションは有効状態です
	return -1;
}
今回の例では上のコードのように -1 を返してスルーで問題ありません。
ここで 1 を返すと、以下の画像のようにチェックが付きます。
QS_20180122-233248

そして、今回メニューに追加した"Test Action"をクリックすると、ここに登録したActionIDのアクションが実行されます。具体的には以下のコールバックが呼び出されます。
・plugin_register()に "hookcommand2" で登録したコールバック

まだ特に触れていませんでしたが、↑のコールバックでfalseを返すと、以下のコールバックも順に呼び出されたりします(ここでは不要なので割愛します)
・plugin_register()に "hookcommand" で登録したコールバック
・plugin_register()に "hookpostcommand" で登録したコールバック

REAPERのあらゆるメインメニュー選択時のActionIDは"hookcommand2"コールバックに飛んでくるので、Extension以外のビルトインメニューに反応して何かする、といったことも出来ちゃいます。
いやもうなんというかやりたい放題できますなぐひひ便利なツールですね。

今回はMainメニュー拡張方法について説明しましたがいかがでしたでしょうか。
ではまた次の記事でお会いしましょう。