今回はBTRONのGUIにおいて不可欠といっても良い、"メニュー"の使い方について説明したいと思います。メニューが実装できるようになると、なんとなくBTRONらしいアプリケーションが出来るようになります。
ところでイベントループを書く際に、手書きでイベントループを書く方法・ライブラリを用いて中身だけを書く方法の二通りの方法があることは、もうすでにご存じのことと思います。(第4, 5回を参照してください。) メニューを使用する場合も同様で、二通りの方法があります。それぞれ長所・短所がありますから、両方の使い方を書いてみたいと思います。
今回のプログラム全体を固めたものはこちらです: lecture9.tar.gz
いわゆる"BTRONらしい"動作をするメニューを作ろうと思ったときに、やらなければならないことは以下の通りです。
Ctrl+W
とか)に対応する処理実行結構やることは多いように見えますが、それほど悲惨なわけではありません。以下、細かくやることを見ていきましょう。
まずは、何より始めに今回のソースコードを見てもらうのが早いと思います。例によって前回のソースコードをベースに手を加えたものですから、前回のものと比較しながら読み進めてもらうと良いと思います。
まず、何より始めに、メニュー項目の内容を用意する必要があります。例を見ながら説明していきたいと思います。以下のように用意します。
// メニュー関連の変数
static TC m_finish[] = {MC_STR|MC_KEY1, L"E終了"};
static TC m_disp[] = {MC_STR, L"表示" | MC_STR, L"再表示"};
static TC m_color[] = {MC_STR, L"色選択" | MC_STR, L"赤" | MC_STR, L"青"};
static TC m_clear[] = {MC_STR, L"すべて消去"};
static MENUITEM mitems[] = {
{0L, 0L, 0, 0, m_finish},
{0L, 0L, 0, 0, m_disp},
{0L, 0L, 0, 0, m_color},
{0L, 0L, 0, 0, m_clear},
{0L, 0L, 0, 0, NULL}, // ウィンドウ
{0L, 0L, 0, 0, NULL}, // 小物
};
上の指定では、"表示"のサブ項目として"再表示"が、"色選択"のサブ項目として"赤"・"青"の二つが表示されます。他の項目は全てトップレベルのメニューとなっています。
分かりにくくて恐縮なのですが、
(TC[]){MC_STR, L"項目名"}
(TC[]){MC_STR, L"親項目名" | MC_STR, L"一つ目の子項目名" | MC_STR, L"二つ目の…" | ...(以下同様)... }
のようにして、トップレベルの項目を作っておきます。上の例では、m_finish
, m_disp
, m_color
, m_clear
がそれです。
なお、MC_KEY1
をMC_STR
にOR結合しておくと、項目名の中の一つ目の文字がショートカットキー扱いされます。上の例では、メニュー項目として"E終了"と表示されるわけではなくて、"終了"とだけ表示され、Ctrl+E
を押したときにメニュー項目が実行されるようになります。
作ったトップレベル項目は、MENUITEM
という構造体の配列に順に格納しておきます。格納順序がそのまま表示順序になると考えておいてください。
ところで、「ウィンドウ」・「小物」の項目の所は、なぜかNULL
が代入されています。これは、メニュー項目がプログラムを書いている時点では決定できないからです。使用者がどんなウィンドウを開いているか、あるいはどんな小物をシステムにインストールしているか、分かるはずがありませんね。この部分については、メニューを表示する際に動的に設定することになります。
基本的には、イベントループの前にメニューの登録処理を行います。mcre_men関数の第一引数としては、親項目数(トップレベル項目数)を渡します。
// メニューの登録
mid = mcre_men(n_mitems, mitems, NULL);
if (mid < 0){
printf("mcre_men: %d\n", mid);
ext_prc(1);
}
イベントループの後ではメニューの解放処理を行います。データボックスの解放関数などとも同じような、よくあるシステムコールですね。
// メニューの削除
mdel_men(mid);
これだけです。簡単ですよね。
ここが今回の山場となります。やることが多く混乱してしまいそうですが、頭の中を整理しながら読み進めてもらえれば、と思います。
まず、もう一度メニュー項目の起動方法を整理しておくと、
の二種類の方法があります。一緒にすると紛らわしいので、分けて説明したいと思います。(Divide and Conquerですね。)
まず、一つ目の、マウスの右クリックでメニューが使われる場合について説明します。
マウスの右クリックが行われると、ウィンドウイベントとしてEV_MENU
というイベントが飛んできます。そのEV_MENU
イベントを処理する関数は、wfunc
の中では
NULL, // menufn
となっていますから、まずここを、
menu_fn, // menufn
と書き換えた上で、W menu_fn()
の中身を書いていくことになります。
それでは、menu_fnの中身を順に見ていきましょう。
まず、メニュー項目のうち、ウィンドウ・小物の項目の中身はとりあえずNULLを代入してあるはずです。なので、そのNULLになっている場所をまず埋めることになります。(これは決まり文句だと思っていただいて差し支えないでしょう。)
// ウィンドウ・小物のメニュー項目を補充
wget_dmn(&(mitems[n_mitems-2].ptr));
mset_itm(mid, n_mitems-2, &(mitems[n_mitems-2]));
oget_men(0, NULL, &(mitems[n_mitems-1].ptr), NULL, NULL);
mset_itm(mid, n_mitems-1, &(mitems[n_mitems-1]));
次に、メニューを表示して、どの項目が選択されたか、その項目番号を取得する必要がありますが、これらの一連の流れをmsel_men
という関数がすべてまとめて処理してくれます。なので、
// メニューを表示し、選択動作を行い、選択されたメニュー番号を得る
sel = msel_men(mid, wevt.s.pos);
たったのこれだけで十分です。この時点で、何が選択されたかの項目番号がselの中に入っています。
項目番号の表現は少し分かりにくいのですが、selの中の右から15〜8ビット目の部分に親項目番号(0から始まります)、7〜0ビット目に子項目番号が入っています。トップレベル項目、つまり子項目がない場合、子項目番号は1になります。
なので、親項目番号は(sel >> 8) & 0xff
として、子項目番号はsel & 0xff
として取り出せます。これらの中身に応じて処理を行い、menu_fnを終了すればOKです。全体としてはこんな感じになります。
if (sel < 0){
return 0; // 何も選択されなかった
}
// 選択されたメニューに応じた処理を行う
switch (sel >> 8){
case 0: // 終了
return -1;
case 1: // 再表示
wreq_dsp(winfo[0].wid);
break;
case 2: // 色選択
switch (sel & 0xff){ // 子項目を取り出す
case 1: // 赤
pdsp_msg((TC[]){L"赤が選択されました。"});
break;
case 2: // 青
pdsp_msg((TC[]){L"青が選択されました"});
break;
default:
break;
}
break;
case 3: // 全て消去
pdsp_msg((TC[]){L"全て消去"});
break;
case 4: // ウィンドウ
wexe_dmn(sel);
break;
case 5: // 小物
oexe_apg(0, sel);
break;
default:
break;
}
ウィンドウ・小物項目が選択したときに、wexe_dmn
, oexe_apg
をそれぞれ実行していますが、これは決まり文句だと思ってください。よほど特殊な動作をさせない限りはここをいじることはないでしょう。
ショートカットキーが押された時のイベントは、EV_KEYDWNで受け取ることが出来ます。evt_loop関数を使うと、keyfnで受け取ることになるので、そのエントリを定義します。(くどいようですが、そこら辺は仕様書を見て対応関係を把握しておきましょう。) key_fnのエントリ定義は、menu_fnの定義と同じやり方で問題ありませんから、ここでは説明を省略します。
EV_KEYDWNが起きただけでは、単にキーが押された、ということしか分かりませんから、key_fnの中では、それが本当にメニュー項目だったか、などを調べ、もしメニュー項目だった場合にはそれに応じた動作を行う必要があります。
if (wevt.s.stat & ES_CMD){
W i = mfnd_key(mid, wevt.s.wid);
if (i > 0){
return select_menu(i);
}
}
これもほぼ決まり文句なので、そのまま自分のプログラムに流用してもらって大丈夫です。やっていることは、Ctrlキーが押されているかどうかを調べ、もしそうならmfnd_keyでそのメニュー項目番号を取り出す、という感じの流れになっています。(したがってiには項目番号が入ります。
以上で、とりあえずメニューの使い方の説明は終わりです。実際に動かして動作を確認してみると良いと思います。今回は特に、いじる部分が広範囲に及んでいてなかなか紛らわしいですが、仕様書を参照しつつ、頑張ってみてください。
いかがでしたでしょか? ライブラリ関数とデータボックスを用いてメニューを扱う方法は今回は説明できなかったので、また別の機会に紹介したいと思います。
[ 戻る ]