目次

はじめに

この文章は、mnwの入門の解説です。僕自身ちゃんと理解してないので、結構いい加減な部分が多いですが、はじめてmnwを使う、という人にはいいんじゃなでしょうか?この文は、mnwapi.htmlという古川さんの書いたドキュメントをよんだ前提で書いてますけど、これから読んでも、けっこうわかるようにしたつもりです。

MNWとは

mnwとは、古川さんがつくった、ポケットBSDでイベント駆動スタイルのプログラムを作るためのライブラリです。 結構(というかかなり)本格的です。 個人で作ったとは思えない程いろいろとそろってます。 ライブラリがシンプルなので、実行時の動作を一から理解する事もできます。 動きを理解してやらないとちゃんと動かない時もありますが、しかし、それはC言語の流儀にあっているとおもいます。 つまりCっぽくGUIのイベント駆動式プログラムが出来る、というのがmnwの長所だと思います。 短所はそのまま長所なので、長所から考えてください。 ただ、リソースが乏しい事を考えると、けっこう完成されているのでは?と思います。 ここは直して欲しい、という部分は僕はないです。 あと、短所としてドキュメントが少ないです。mnwのソース、又はmnwを使ったアプリの ソースを読まないと使い方はわからないでしょう。 その短所がこれで少しでもカバーできれば幸いです(でもたぶん無理)。

HelloWorld

ちっともmnwっぽくないですけど、はじまりはHelloWorldと昔から決まっているらしいので、それから。 始めにいっとくと、このプログラムの終了はqです。

#include "mnw.h"

int main(void){
  mnwEvent e,*event;
  mnwLabel label;
  mnwRect rect;
  char label_handle[256];

  mnwSetup();

  /* ルートウインドウの可視化	*/
  mnwEvent_SetAll(&e, kEvent_Show, nil, 0, 0);
  mnwEvent_SendTo(&e, gRootWindow);

  /* ラベルの登録 */
  mnwRect_SetXYWH(&rect, 110, 40, 50, 22);
  mnwWindow_Register((mnwWindow *)&label,
		     (mnwWindowCallback)mnwLabel_Callback,
		     (mnwWindow *)gRootWindow, &rect);

  mnwEvent_SetAll(&e, kEvent_SetBuffer, label_handle, 256, 0);
  mnwEvent_SendTo(&e, (mnwWindow *)&label);
  mnwEvent_SetAll(&e, kEvent_SetText, "hello world", 11, 0);
  mnwEvent_SendTo(&e, (mnwWindow *)&label);

    while ((event = mnwEvent_GetNext(true)) != nil){
	if (!mnwEvent_Translate(event)){
	  if (event->type == kEvent_KeyDown){
	    if (event->param1 == 'q'){
	      return 0;
	    }
	  }
	  mnwEvent_Dispatch(event);
	}
      }


}
ここで、コンパイルは、
gcc -I/usr/local/lib/mgl -I/usr/local/include hello.c -L/usr/local/lib/mgl -L/usr/local/lib/mgl -lmnw -lmgl
です。このコマンドをいちいち打つのはメンドいので、普通はMakefileを書きます。Makeなんかいれてねーっすよ!という人は、
#!/bin/sh

gcc  -I/usr/local/lib/mgl -I/usr/local/include $* -L/usr/local/lib/mgl -L/usr/local/lib/mgl -lmnw  -lmgl

とかいうシェルスクリプトでも書けばいいかな?mnwccとかいう名前で保存して、mnwcc hello.cとか実行してみるといいんじゃないっすか?でもMakeは便利なんでいれといてもいいかも。*.mkをいれわすれるというのもポケビの基本らしい。

一応Makefileも書いときます。そんなの自分で書けるよ、というつっこみが飛んできそうですが。


CFLAGS = -I/usr/local/lib/mgl -I/usr/local/include
LFLAGS = -L/usr/local/lib/mgl -lmgl -L/usr/local/lib/mgl -lmnw  #-ldl
CC = gcc

hello: hello.c 
	$(CC) $(CFLAGS) -o hello hello.c $(LFLAGS)

sample: sample.c
	$(CC) $(CFLAGS) -o sample sample.c $(LFLAGS)

Linuxの場合は-ldlの前の#を消してください。 そんで、make helloでオッケーだと思います。

解説

結構ごついっすね。もっと簡単な方法もあるけど、mnwLabelを使ってみました。んで、解説。

  mnwEvent e,*event;
  mnwLabel label;
  mnwRect rect;
  char label_handle[256];

宣言です。mnwEvent型のeは、イベントを作って送る用です。*eventは、whileでイベントを回す時に使います。mnwLabelは、文字列をはりつけるのに使います。mnwRectは、領域を確保するのに使ってます、とかいってますけど、僕もよく理解してません。label_handleは、はりつける文字列を記憶する場所です。これを確保するのがmnwっぽさって感じがしません?

  mnwSetup();	

こいつはおまじないらしい。何をやってるのかは知りません(^^;

  /* ルートウインドウの可視化	*/
  mnwEvent_SetAll(&e, kEvent_Show, nil, 0, 0);
  mnwEvent_SendTo(&e, gRootWindow);

結構そのまんま。ルートウィンドウの可視化です。ここで、mnwの基本スタイルである、イベントを作ってそれを送る、という事をやってます。SetAllでShowってイベントを作り、SendToで送ります。内部的には、キューにイベントを入れる、という事をやっている気がします。

  mnwRect_SetXYWH(&rect, 110, 40, 50, 22);
  mnwWindow_Register((mnwWindow *)&label,
		     (mnwWindowCallback)mnwLabel_Callback,
		     (mnwWindow *)gRootWindow, &rect);

ついにラベルに入ります。はじめのRect_SetXYWHで、領域を確保している気がします。 この関数の引き数の2番目と3番目の値は、左上を0,0とした時のxとy座標です。 右と下が正です。 4番目と5番目の値は、そこから右にいくつ、下にいくつの領域をとるかです。 GUI関係のプログラムをした事がある人にはおなじみでしょう。 このSetXYWHと次のRegisterはセットで使用します。きっとセットでない使い道もあるのでしょうが、僕は知りません(こんなのばっか)。
それでRegister。最初の引き数で登録する物を、次の引き数でそのCallback関数を、その次の引き数で、何に登録するかを、その次にそいつが使う画面の領域をあらわしてる気がします。 Callbackとは、mnwEvent_Dispatchでイベントが配送される時に呼び出される関数です。 どちらかというと、Callbackを呼び出す事でイベント配送、という事が行われている感じです。まあよくわからなければ、とりあえずそんな感じという事で流して下さい。

  mnwEvent_SetAll(&e, kEvent_SetBuffer, label_handle, 256, 0);
  mnwEvent_SendTo(&e, (mnwWindow *)&label);

ラベルが文字列を記憶する場所を指定します。つまり、ラベルに表示される文字列は、label_handleという配列に格納される訳です。これもlabel_handleをセットするというイベントを作り、そいつを送ってます。おくり先が今度はlabelになっています。

  mnwEvent_SetAll(&e, kEvent_SetText, "hello world", 11, 0);
  mnwEvent_SendTo(&e, (mnwWindow *)&label);

ここで文字列をセットしています。 この文字列のセットも、イベントを作り、それを送る、という事をしています。 このSetTextの引き数の11というのはセットする文字列の長さです。 その後の0っていうのは僕も知りません。

    while ((event = mnwEvent_GetNext(true)) != nil){
	if (!mnwEvent_Translate(event)){
	  if (event->type == kEvent_KeyDown){
	    if (event->param1 == 'q'){
	      return 0;
	    }
	  }
	  mnwEvent_Dispatch(event);
	}
      }

キューに入っているイベントを順番に処理していきます。実は僕にとってはけっこうおまじないだったりします(こんなのばっか)。

    while ((event = mnwEvent_GetNext(true)) != nil){
	if (!mnwEvent_Translate(event)){
		ここにイベントに対応した処理を書く
		  mnwEvent_Dispatch(event);
	}
     }

って感じでいつも使ってます。 処理しないイベントは、mnwEvent_Dispatch(event)で現在フォーカスのあたっている要素のコールバックにまかせる訳です。で、今回は、

	  if (event->type == kEvent_KeyDown){
	    if (event->param1 == 'q'){
	      return 0;
	    }
	  }

キーが押されて、それがqならreturn 0を実行している訳です。

以上をまとめると、

という基本(?)が見えてくると思います。

サンプルを読もう

このsampleは結構いろいろやっているので参考になると思います。 まず、mainの中のtest1()という所をコメントアウトしてtest2() という所のコメントをはずして下さい。test1()はあまりmglと かわらなく見えるので(ちゃんとよんでないけど)。
実際に読むときは、けっこうコメントがあるので、上記のhello.cを理解していればすんなりと読めると思います。 それで、新しい部分について解説すると、Label,Button,Dialog,Edit,Listについての解説になります。


    /* TAB stopの設定 */
    mnwEvent_SetAll(&e, kEvent_SetTabStop, &win4, 0, 0);
    mnwEvent_SendTo(&e, (mnwWindow *)&win1);
    mnwEvent_SetAll(&e, kEvent_SetTabStop, &win5, 0, 0);
    mnwEvent_SendTo(&e, (mnwWindow *)&win1);
    mnwEvent_SetAll(&e, kEvent_SetTabStop, &win6, 0, 0);
    mnwEvent_SendTo(&e, (mnwWindow *)&win1);
    mnwEvent_SetAll(&e, kEvent_SetTabStop, &win7, 0, 0);
    mnwEvent_SendTo(&e, (mnwWindow *)&win1);

この、SetTabStopという奴は、Dialog関係です。 これはDialog、win1に登録した奴をTabで移動するのに必要です。 実際にsample.cを動かして、Tabキーを押してみればわかると思います。 コンパイルは先ほどのmnwccで出来そうです。
ここからは、便宜上登録するDialogを親、登録される要素を子と呼びます。 一度TabStopをセットすれば、もし親にフォーカスがあたっていたら、Tabキーを押す毎にその子たちにフォーカスを順番にうつす、という事をしてくれます。たぶんmnwDialog_Callbackあたりがやっているんじゃないかな?(あてずっぽう)


    mnwEvent_SetAll(&e, kEvent_SetStyle, nil, kStyle_Popup, 0);
    mnwEvent_SendTo(&e, (mnwWindow *)&win3);
    mnwEvent_SetAll(&e, kEvent_SetAlign, nil, kAlign_Center, 0);
    mnwEvent_SendTo(&e, (mnwWindow *)&win3);

これはポップアップ、つまりラベルをちょっと立体的にするんですね。 その次のkAlign_Centerっていうのは僕も知りません。


    mnwEvent_SetAll(&e, kEvent_SetCommand, nil, 0x999, 0);
    mnwEvent_SendTo(&e, (mnwWindow *)&win4);

これはボタンの所ですね。ボタンが押されると、たぶんeventのどっかに0x999というのを入れて配送される気がします。event->param1だったかな?その他の引き数のnilと0は知りません。 このプログラムではボタンは一つなので、この0x999はつかわれませんね。

    mnwEvent_SetAll(&e, kEvent_SetMultiLine, nil, 0, 0);
    mnwEvent_SendTo(&e, (mnwWindow *)&win5);

これはエディットコンボですね。複数の行を扱えるようにする、という事です。

    mnwEvent_SetAll(&e, kEvent_SetText, "0", 0, 1);
    mnwEvent_SendTo(&e, (mnwWindow *)&win6);
    mnwEvent_SetAll(&e, kEvent_SetText, "1", 1, 1);
    mnwEvent_SendTo(&e, (mnwWindow *)&win6);
    mnwEvent_SetAll(&e, kEvent_SetText, "2", 2, 1);
    mnwEvent_SendTo(&e, (mnwWindow *)&win6);
    mnwEvent_SetAll(&e, kEvent_SetText, "3", 3, 1);
    mnwEvent_SendTo(&e, (mnwWindow *)&win6);
    mnwEvent_SetAll(&e, kEvent_SetText, "4", 4, 1);
    mnwEvent_SendTo(&e, (mnwWindow *)&win6);
    mnwEvent_SetAll(&e, kEvent_Redraw, nil, 0, 0);
    mnwEvent_SendTo(&e, (mnwWindow *)&win6);

これはリストですね。SetTextで0,1,2,3,4を順番に登録していきます。 ダブルクォーテーションの次にある引数はリストの上から何番目か、という事をあらわしている気がします。その次の引数はセットする文字列の長さです。


	    mnwPrintEvent(event);

おくられてきたイベントの種類をただ標準エラー出力にだらだらと出力します。 どちらかといえばデバッグ用なのかな?

	    if (event->type == kEvent_Command){
		mnwMsgBox("a message box", kMsgBox_Type_OKCANCEL);
		continue;
	    }

ここでボタンの処理をします。 kEvent_Commandというイベントは、ボタンが押された、という時に発生するイベントです。 このとき、event->param1には、セットした時に指定した値が入っています。 この場合は、mnwMsgBoxという関数を呼び出します。すると、こいつに処理がうつり、この場合はOK,Cancelの二つのボタンをもったメッセージボックスがあらわれます。どっちにしてもこの処理はすてられちゃいますけど。 そしてcontinueで次のイベントに進みます。

後はだいたい一緒ですか?

サンプルをいじろう

ここでは、複数のボタンの区別、選択されているリストに対しての操作、Editコンボの内容を操作する、といった事についてすこしやってみようと思ってます。

いじる目次

OKとCancelの区別

サンプルで、Tabを押していくと、やがてOKとかかれたボタンにフォーカスがうつると思います。 そこでリターンを押すと、OKとCancelとかかれたボタンをもったメッセージボックスが出てくると思います。 このサンプルでは、このどちらを選ぼうと、結果は同じになっていますが、これを区別してみます。 以下のソースを、sample2.cという名前で保存して、EUCコードにした後にコンパイルして実行してみると、sampleと同じ画面が出てくると思います。 ここで、さきほどのOK,Cancelを選ぶダイアログを出して、そのうちのどちらかを選択すると、hogeと書いてある所が、OKまたはCancelにかわります。えらぶには、TabでOKとCANCELを移動します。 Makefileに、
sample2: sample2.c
	$(CC) $(CFLAGS) -o sample2 sample2.c $(LFLAGS)
なんて記述をたして、Make sample2でsample2をつくって実行してみて下さい。
sample2のソース
ほとんどsampleと同じなので、違う所だけ解説していきます(以下も同様)。 まず、intのresultという変数を用意して、

	result = mnwMsgBox("a message box", kMsgBox_Type_OKCANCEL);
	if(result== kMsgBox_CANCEL){
	  mnwEvent_SetAll(&e, kEvent_SetText, "cancel", 6, 0);
	  mnwEvent_SendTo(&e, (mnwWindow *)&win3);
	}
      else
	if(result == kMsgBox_OK){
	  mnwEvent_SetAll(&e, kEvent_SetText, "ok", 2, 0);
	  mnwEvent_SendTo(&e, (mnwWindow *)&win3);
	}

としてます。 mnwMsgBox関数は、第一引数に表示する文字列、第2にそのメッセージボックスのタイプを指定します。 このタイプには、mnw.hのソースを見てみると、
    kMsgBox_Type_OK = 0x01,
    kMsgBox_Type_OKNG = 0x02,
    kMsgBox_Type_OKCANCEL = 0x04,
    kMsgBox_Type_OKNGCANCEL = 0x08,
の4種類がありそうです。実際、mnwmsgbx.cの方をみてみても、
    switch (msgbox_type & 0xf){
	case kMsgBox_Type_OK:
	button_num = 1;
	break;
	case kMsgBox_Type_OKNG:
	button_num = 2;
	break;
	case kMsgBox_Type_OKCANCEL:
	button_num = 2;
	break;
	case kMsgBox_Type_OKNGCANCEL:
	button_num = 3;
	/* TODO */
	break;
    }
とあるので、これで全部っぽいです。 それで、戻り値は、OKがえらばれるとkMsgBox_OKが、 CancelがえらばれるとkMsgBox_CANCELが返されます。 その返された値をresultに代入して、どちらがえらばれたのかを見ます。 それでそれがkMsgBox_CANCELなら、
  mnwEvent_SetAll(&e, kEvent_SetText, "cancel", 6, 0);
  mnwEvent_SendTo(&e, (mnwWindow *)&win3);
を実行してます。 これは、win3(mnwLabel)にcancelという文字をはりつける、というイベントをつくって送ってます。 okの時も同様です。

複数のボタンの区別

今回は、複数のボタンの区別について。 中は結構簡単だったりします。 複数のボタンを区別する場合、ボタンが押された時に発行されるイベントが持っている値を区別する事により出来ます。 基本的な事はsample2.cの時と同じです。 Makefileも、
sample3: sample3.c
	$(CC) $(CFLAGS) -o sample3 sample3.c $(LFLAGS)
という記述を足して、make sample3でコンパイルしてください。
sample3のソース
このsample3.cでは、これまでのmnwWindow型のwin2をなくして、 その場所にbutt1,2,3を、そして、それまでwin4という名前だった 物を、butt4という名前にして登録しています。 そして、それぞれのボタンに対して、
mnwEvent_SetAll(&e, kEvent_SetCommand, nil, 0x01, 0);

mnwEvent_SetAll(&e, kEvent_SetCommand, nil, 0x02, 0);

mnwEvent_SetAll(&e, kEvent_SetCommand, nil, 0x03, 0);

mnwEvent_SetAll(&e, kEvent_SetCommand, nil, 0x04, 0);
というイベントを作り、送ってます。 そうするとボタンが押された時に配送されるmnwEvent *eventには、 event->typeには同じkEvent_Commandが入りますが、event->param1にはそれぞれ0x01,0x02,0x03,0x04の数字が入ります。 だから、どのボタンが押されたのかを区別するには、kEvent_Commandが流れてきたら、そのevent->param1の値を比べればいいわけです。 sample3.cで、それに相当している場所は、
	    if (event->type == kEvent_Command){
	      switch(event->param1){
	      case 1:		  
		mnwEvent_SetAll(&e, kEvent_SetText, "1", 1, 0);
		mnwEvent_SendTo(&e, (mnwWindow *)&win3);
		break;
	      case 2:
		mnwEvent_SetAll(&e, kEvent_SetText, "2", 1, 0);
		mnwEvent_SendTo(&e, (mnwWindow *)&win3);
		break;
	      case 3:
		mnwEvent_SetAll(&e, kEvent_SetText, "3", 1, 0);
		mnwEvent_SendTo(&e, (mnwWindow *)&win3);
		break;
	      case 4:
		mnwEvent_SetAll(&e, kEvent_SetText, "ダ〜〜", 6, 0);
		mnwEvent_SendTo(&e, (mnwWindow *)&win3);
		break;
	      default:
		mnwEvent_SetAll(&e, kEvent_SetText, "naze", 4, 0);
		mnwEvent_SendTo(&e, (mnwWindow *)&win3);
		break;
	      }
です。1が0x01と同じなのはいいでしょう。 それならなぜ0x01をつかったのかは謎です。その時の気分でして。 それで、event->param1が1ならbutt1が、2ならbutt2が、という風にどのボタンが押されたのかが解ります。 それで、ここでは、それぞれのボタンに対応した文字列をwin3にはっつけています。 ここで気をつけないといけないのが、これらを表示させたいなら、Redrawしないといけないという事です。 前回のsample2ではRedrawしてないのに何故うまくいったのかはよくわかりませんが、きっとモーダルなのと関係ある気がします(うそかも)。 という訳でRedrawしてます。
	mnwEvent_SetAll(&e, kEvent_Redraw, nil, 0, 0);
	mnwEvent_SendTo(&e, (mnwWindow *)&win3);
これでボタン毎の動作のきりかえ方がわかったと思います。 実際のプログラムではこの運ばれる値は適当にマクロ定義する事になるでしょう。 今回の例なら、
#define BUTT1 0x01
#define BUTT2 0x02
#define BUTT3 0x03
#define BUTT4 0x04
という感じで、これ以降は、BUTT1,BUTT2,BUTT3,BUTT4を数字のかわりに使うわけです。 ここらへんはmnwに特有という訳ではないので解説しません。

リスト

今回は、リストのどこが選択されているかと、選択されている文字列の入手のしかたです。 ソースは
sample4
です。 今回もプログラムのうち重要な所だけ書いていきます。 今回重要なのは、GetListNumberと、GetListStringです。 前者は現在リストの何番目が選ばれているかを返す関数で、 後者は現在選ばれている文字列を返す関数です。 ソースを見ると、

int GetListNumber(mnwList *list){
  return list->origin_row + list->cursor_row;
}

となっています。ここで、二つの要素、origin_rowと、cursor_rowが登場してます。 これらは、mnwListの構造体の要素で、現在のカーソルに対応した値がはいってます。 origin_rowは、登録されている要素の中で、現在見えている所は上から何番目かを保持しています。 つまり何マススクロールしたかを保存している訳ですね。 そして、cursor_rowは、現在選ばれている場所は、見えている所の一番上から何マス目かを表しています。 つまり、

 +-----+<-一番上
 |  0  |
 |-----|
 |  1  |<-ここは見えないとする
-+-----+- <-ここから見えてるとする
 |  2  |
 |-----|
 |  3  |<-ここが選ばれているとする
 |-----|
 |  4  |
 |-----|
 |  5  |
 +-----+
この上の図(もどき)の場合、origin_rowは2で、cursor_rowは1、という訳です。 origin_rowは、現在見えている所の一番上(つまり2)が、登録されている一番上(つまり0)から何番目か、という事です。 cursor_rowは、現在見えている一番上(つまり2)から何番目かです。 2の場所は0で、3の場所は1です。 この説明より、実際にテストプログラムを見た方がわかりやすいと思います。
テストプログラムのソース
こいつを実行すれば,cursor_rowとorigin_rowについては解るでしょう。 コンパイルなんかの方法も、これまでのsampleと一緒です。 で、origin_row+cursor_rowで、一番上から何マス目かがわかる訳です。 これがGetListNumberの中です。

次は、GetListStringです。


char *GetListString(mnwList *list){
  int lnumber;
  char *p;
  
  lnumber = GetListNumber(list)*list->max_size;
  p = list->buffer+lnumber;  
  return p;
}

少し解りにくいかもしれません。
list->bufferは文字の多次元配列です。 やってる事は、
(list->buffer)[GetListNumber(list)]という物を返しているだけです。 まあ直接そこをアクセスするのがC言語っぽいかな?という理由で、ソースは上の様になっています。
(list->buffer)[GetListNumber(list)]は、
list->buffer+GetListNumber(list)*list->max_sizeからはじまっている訳ですね。

以上でsample4.cの解説を終ります。

エディット

今回は、プログラムを書いた瞬間に、あんまし解説する事がない事が判明してしまったので、短い解説です。 まずソースはこれ
sample5のソース
Makefileもいつもといっしょです。 今回は、mnwEditに文字列を貼る方法と、mnwEditの文字列を受けとる方法を解説するつもりです。 でも実は、これらはとても簡単で、まず貼りつけは、

	mnwEvent_SetAll(&e, kEvent_SetText, GetListString(&win6), 
			strlen(GetListString(&win6)), 0);
	mnwEvent_SendTo(&e, (mnwWindow *)&win7);

で、こんなのLabelと一緒じゃん、といわれるとそれまでです。 GetListStringは、sample4で書いた奴ですね。 この関数で文字列を受け取り、その文字列をSetTextイベントを作って送る事により、登録する訳です。 そんで、文字列の受け取り方は、

	regist(win5.buffer,GetListNumber(&win6));
	mnwEvent_SetAll(&e, kEvent_SetText, win7.buffer, 
			GetListNumber(&win6),strlen(win7.buffer) );
	mnwEvent_SendTo(&e, (mnwWindow *)&win6);

の部分ですが、この「きも」は、win5.bufferと、win7.bufferです。 ここに文字列が入ってる訳ですね。
困った。もう解説が終ってしまった。 という事で今回は日本語入力についても少し触れます。 mnwEditでは、MGLIMという環境変数に、imcanna.so又は手前みそになりますが、 imskk.soが入っていれば、仮名漢字変換が出来ます(他のimもそのうち出来るのでしょうか?)。 起動はCtrlを押しながらoで、終了も同じです。 そして、mnwEditのbufferにはEUCコードの文字列が入る事になります。 バックスペースや、右矢印、左矢印、C-fやC-bなんかは、ちゃんと処理してくれます。 便利ですね。以上で日本語入力の解説を終了します。

ファンクションキー

今回も解説はあんましなかったりします。 mnw以外のコードがいろいろと増えてきてしまってますが、これらは普通のCプログラムなので、読まなくてもいいでしょう。 今回のソースはこれです。
sample6のソース
今回は保存とLoadを作ってみました。 これらの作業をしているのはLoadFileとSaveFileという関数です。 中は普通のCなので解説はしませんが、いっかしょだけ。
    if(j > 20){
      printf("ToManyItems\n");
      exit(1);
    }
ここで、20行以上ならerrorとして終了しています。 なぜ20行かというと、mnwListにある程度以上たくさんの要素を登録しようとすると、mnwEvent_SendToを連続で何度も呼びだす事になる訳ですが、このSendで入れるキューが、イベントの数があんまり大きいと、あふれちゃうからです。 だから、20程度を限界としてます。 イベントを処理するループにいかないとイベントは処理されないので、forループやwhileループの中などで、イベントを作って送る場合は、送りすぎに注意が必要です。 また、最初の登録の場面などでは、多くのイベントが送られるので、注意が必要です。 Taskで、たくさんのマス目を使っていますが、あれは直接DrawLineした物にDrawTextしている訳ですから、一つ一つがWindowという訳ではないらしいです。 だから、例えばボタンをキーと同じ数だけ用意してキータッチ練習ソフトを作ったりするには、少しトリックが必要になると思います(SendToDirectなどでいけるのだろうか?)。 まぁ、リアルタイムなアプリを書くには、mnwのイベントの仕組みは適しているとはいいがたい、という気もしますが。
話がそれましたが、よくつきあたる問題な気がしたので、解説しました。
それで、FunctionKeyの説明に入ります。 ほとんど、

    mnwEvent_SetAll(&e, kEvent_UnsetText, nil, 0, 0);
    mnwEvent_SendTo(&e, gFunctionGuide);
    mnwEvent_SetAll(&e, kEvent_SetText, "登録", 1, 0x02);
    mnwEvent_SendTo(&e, gFunctionGuide);
    mnwEvent_SetAll(&e, kEvent_UnsetText, nil, 2, 0);
    mnwEvent_SendTo(&e, gFunctionGuide);
    mnwEvent_SetAll(&e, kEvent_UnsetText, nil, 3, 0);
    mnwEvent_SendTo(&e, gFunctionGuide);
    mnwEvent_SetAll(&e, kEvent_SetText, "表示", 4, 0x01);
    mnwEvent_SendTo(&e, gFunctionGuide);
    mnwEvent_SetAll(&e, kEvent_UnsetText, nil, 5, 0);
    mnwEvent_SendTo(&e, gFunctionGuide);
    mnwEvent_SetAll(&e, kEvent_UnsetText, nil, 6, 0);
    mnwEvent_SendTo(&e, gFunctionGuide);
    mnwEvent_SetAll(&e, kEvent_SetText, "保存", 7, 0x03);
    mnwEvent_SendTo(&e, gFunctionGuide);
    mnwEvent_SetAll(&e, kEvent_SetText, "ロード", 8, 0x04);
    mnwEvent_SendTo(&e, gFunctionGuide);
    mnwEvent_SetAll(&e, kEvent_UnsetText, nil, 9, 0);
    mnwEvent_SendTo(&e, gFunctionGuide);

に集約されてます。 gFunctionGuideというのは、gRootWindowなどと同じように、最初から宣言されている物で、こいつに例えば、

    mnwEvent_SetAll(&e, kEvent_SetText, "登録", 1, 0x02);
    mnwEvent_SendTo(&e, gFunctionGuide);

という風にSetTextイベントを送ってやると、画面下部のこのファンクションキーに対応する部分に、この文字列が表示されます。 SetTextの次の次の引数に、FunctionKeyの何番目かが入ります。 この場合は、1ですので、0から数えて2番目なのでF2になります(ややこしい)。 その次の引数は、このファンクションキーが押された時に配送されるイベントのevent->param1に入る数字です。 ボタンの時はkEvent_SetCommandでしていた作業が出来る訳ですね。 いいわすれてましたけど、ファンクションキーが押された時のevent->typeは、ボタンと同じkEvent_Commandです。 つまり配送されるイベントに関しては、ボタンと同じなんですね。 扱いもボタンと同じで大丈夫です。 また、何も表示しない時は、例えばF3なら、

    mnwEvent_SetAll(&e, kEvent_UnsetText, nil, 2, 0);
    mnwEvent_SendTo(&e, gFunctionGuide);

とするといいらしいです。 kEvent_UnsetTextの意味は知りません。 それで、ファンクションキーが押された時の対応は、

    if (event->type == kEvent_Command){

のifの中で処理されてます。

      switch(event->param1){
      case 1:		  
	表示の処理
	break;
      case 2:
	登録の処理
	break;
      case 3:
	SaveFile("data.txt",&win6);
	break;

      case 4:
	LoadFile("data.txt",&win6);
	break;


という感じです。 ここで注意として、F2Keyと、登録ボタンの区別は今回は出来ません。 もし区別したいなら、F2Keyが押された時にeventに入れる値をかえなくてはいけません。 それどころか、

mnwEvent_SetAll(&e,kEvent_Command,nil,0x01);
mnwEvent_SendTo(&e,gRootWindow);

などとやってもボタンと区別出来ません。 これは、うまく使えば、処理を一箇所にまとめる事が出来て便利です。 例えば、キーによるショートカットを作るのが容易です。
以上でファンクションキーの解説を終ります。 ここらへんまでくれば、それなりに実用的な物も書けない事もないんじゃないでしょうか?

Callbackと、キーによるショートカット

今回はちょっと変更が多いです。 また、てぬきの為win1をグローバル変数にしました。 ほんとは、gRootWindowのコールバックを書き換えて、Tabキーをwin1に配送するなり、イベント処理ループでwin1に配送するのが普通なんでしょうけど、めんどいので、ズル。 今回のソーソはこちら
sample7のソース
それで、今回のテーマであるコールバックは、二つ作りました。 myList_Callbackと、myEdit_Callbackです。 これらは、リストと複数行の方のエディットのコールバックです。 つまり、win6とwin5ですね。

どういう動きをするかというと、リストは、上下のキーが入れられるたびに、 mnwEditの方に変更を反映する、という事をします。 内部的には、これらのキーが押される毎に、butt1を押したのと同じイベントを配送しています。 これについては、後ほど解説します。

他には、C-lでF9のロードと同じ事を、C-sでF8の保存と同じ動きをします。 これらがキーによるショートカットです。 また、リターンが押されると、TABキーが3回押されたのと同じ動きをします。 これは、名前の所に移動する訳ですね。

ここで、TABStopについて。 Tabキーで移動する順番は、SetTabStopStopを送った順番ではなく、ダイアログにregisterされた順番です。 だから、望みどおりの順番に移動するようにregisterの順番を変えました。

それで、myEdit_Callbackは、複数の行がある方のEditのコールバックとして使われます。 C-s、C-lはリストと同じですが、その他に、Returnが押されると、butt2が押されたのと同じイベントを配送した後、Tabキーが押されたのと同じ動きをします。 つまりmnwEditの中身をリストなどに登録する訳ですね。

以上でこのサンプルプログラムの使い方の解説をおわって、いよいよソースの解説です。
まずmyListCallbackでは、


bool myList_Callback(mnwList *self,mnwEvent *event){

と、かえり値にbool,引数にmnwListとmnwEventのポインタを取ります。 いままで解説なしでつかってましたが、boolというのは、falseとtrueというふたつの型で、中身は0と1です(たぶん)。 arch.hという所で定義されていて、

#ifndef false
#define false	(0!=0)
#define true	(!false)
#endif

だそうで。 これはmnw.hでインクルードされているので、mnw.hをインクルードすればそのまま使えます。 コールバックは、それが登録される物(Window)自身へのポインタと、その時配送されているイベントのポインタを引数に呼び出されます。 だから、宣言する時もそのように宣言して下さい(たぶん)。
中の処理を見て行くと、ボタンが押された時は、

  if(event->type==kEvent_KeyDown){
    switch(event->param1){
    case 's':
	sの処理
    case 'l':
	lの処理
    case kKey_UP:
	上矢印の処理
	:
	:

  }


という部分で実行されています。 最初のif文でボタンが押されたのかどうかを確認して、それ以後のswitch文で何のキーが押されたのかを調べます。 イベントループでの処理と同じですね。 それで、例えばsキーが押されたら、

     if(event->param2 & kKey_CONTROL){
	mnwEvent_SetAll(&e,kEvent_Command,nil,3,0);
	mnwEvent_SendTo(&e,gRootWindow);
	return true;
	break;
      }
      else{
	return mnwList_Callback(self,event);
	break;
      }

という処理を実行しています。 event->param2には、ctrlが押されてたらkKey_CONTROLが入ります。 Shiftが押されていればkKey_SHIFTが入ります。 でもCAPSとの区別とかは確かできなかったと思います。 ATLは今の所サポートされてなさそうです。 それで、もしsが押されてかつCtrlが押されていれば、3という数字がparam1に入ったkEvent_Commandというタイプのイベントを作り、それを送ります。 この3というのは、Functionキーの所で、保存というF7キーのコマンドとして登録しています。

    mnwEvent_SetAll(&e, kEvent_SetText, "保存", 7, 0x03);
    mnwEvent_SendTo(&e, gFunctionGuide);

この部分です。 それで、この0x03と同じコマンドをながして、イベント処理ループの所で一括して処理します。 これなら、処理が一箇所にまとまるので、保守もいいです。

それで、処理が終ったら、return true;しています。 このように、Callbackでは、自分が処理をするイベントならば、処理をしてtrueを返します。 すると、mnwEvent_Dispatchがイベントを配る時に、そこで処理されたとみなしてそこから先にはイベントを渡しません。 逆にfalseを返せば、たとえ処理をしても処理をしたとはみなされなくて、別のウィンドウにイベントが渡されます。

また、ただのsキーが押されて、ctrlがなかった場合など、普通にsキーを押した動作をmnwListコンポにしてもらいたいので、return mnwList_Callbackl(self,event);しています。 別にsキーは特にリストでは処理されないのでreturn trueでも問題ないとは思うんですが。

このように、一部だけ動作を変えたい時には、変える部分だけ自分で実装して、それ以外は元のコールバックを使う事で、差分プログラムが出来ます。 オブジェクト指向なんかでおなじみの継承みたいな事ができる訳です。 でも変数はどうやって追加するんだろう?

まあおいといて、mnwとは少しずれますが、C-pなどでは処理をしてないように見えるかもしれないので解説しておくと、


    case 'p':
      if(event->param2 & kKey_CONTROL){
	/* goto kKey_UP */
      }

の部分ですが、breakが書いてないので、次の
case kKey_UP:
に処理が移るわけです。
myEdit_Callbackの方でも同じですね。

次回予告

次回は特にネタなしです。今回で最終回かな?要望があれば考えますが。次はapi集でも作りたいですね。