Ko-Window プログラミング、入門編 その3 「便利なライブラリ」 ここではリアルタイム処理や、ボタンやポップアップメニューなど、便利なライブ ラリを使ったアプリケーションを作っていきます。 ● EventInterval まず、多分誰もが知りたがっているであろうリアルタイム処理について説明します。 ようするに、ユーザーが何もしなくても勝手に動いている時計などの作り方です。 以下はサンプルプログラムです。マウスカーソルの座標を監視し、ウィンドウ内に 報告します。左側の座標が画面全体に対する絶対位置で、右の座標が現在マウスカー ソルが載っているウィンドウローカルの座標になります。 ---------------------------------------------------------------------------- #include /* 0.05秒に1回座標をチェックし表示を更新する */ #define WAIT 5 int WindowHeapSize= 0; char msbuf[30]; /* 描画バッファを設定する */ DrawSetMouse( dp ) DrawBuf *dp; { DrawSetSymbol( dp, 4, 4, msbuf, AttrDefault, 12 ); return 1; /* 使用した DrawBuf 数 */ } /* マウス座標を求めて、表示文字列に変換する */ MouseSetPosition( info ) EventInfo *info; { static Old_x, Old_y; /* イベント情報を新たにバッファに獲得する */ WindowGetEventInfo( info ); /* 前と座標が同じなら何もしない */ if( info->x != Old_x || info->y != Old_y ){ int x, y, x2= 0, y2= 0; Old_x= x= info->x; Old_y= y= info->y; /* チャイルドウィンドウ内の座標に変換する */ if( WindowGetChild( WindowRootID, info ) ) x2= info->x, y2= info->y; /* 得たマウスカーソル座標を文字列に変換する */ sprintf( msbuf, "(%4d,%4d) (%4d,%4d)", x, y, x2, y2 ); return TRUE; } return FALSE; } EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[10], *dp= dbuf; static unsigned Time; /* 時間カウントワーク */ switch( info->option ){ case EventOpen: MouseSetPosition( info ); WindowRedraw( wp ); /* EventInterval を有効にする */ WindowSetEventAttr( wp, EventAttrDefault|EventIntervalON ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: DrawSetClear( dp++, ColorGray ); dp+= DrawSetMouse( dp ); WindowDraw( wp, dbuf, dp-dbuf ); return TRUE; case EventInterval: /* インターバルイベント */ if( IntervalWait( WAIT, &Time ) ){ if( MouseSetPosition( info ) ) WindowDraw( wp, dbuf, DrawSetMouse( dp ) ); return TRUE; } return FALSE; } return FALSE; } WindowMain( argc, argv ) char **argv; { int x= 10, y= 10; AnalyzeArgs( argc, argv, &x, &y, NULL, NULL ); WindowTitleOpen( x, y, 200, 20, NULL, "smpl", Close|Push, EventExec ); } ---------------------------------------------------------------------------- CPU が暇な間、常に発生し続けるのがこの EventInterval です。もちろん、あま りに頻繁に発生し続けられても困るので、適度な間隔をあけて自分の処理をしなけれ ばなりません。(例えば時計なら、書き換えは1秒に1回で十分でしょう) その処理を行っているのがこの部分です。 if( IntervalWait( WAIT, &Time ) ){ if( MouseSetPosition( info, TRUE ) ) WindowDraw( wp, dbuf, DrawSetMouse( dp ) ); return TRUE; } IntervalWait() という関数は、指定時間以上経過すると一度だけ TRUE を返します。 時間は 1/100 秒単位なので、この例では 0.05 秒待っていることになります。&Time というのは IntervalWait() 内部で使う時間計測のためのカウンタです。適当な変数 (必ず static か global) のアドレスを与えておいて下さい。 ●ポップアップメニュー ポップアップメニューは、parts.a(またはlibparts.a) の PopUpMenu() という関 数で、簡単に処理することができます。 このサンプルでは、マウスボタンのポップアップメニューで、ウィンドウ内に表示 している文字の大きさを変えることができます。 ---------------------------------------------------------------------------- #include int WindowHeapSize= 0; int Font= 16; /* 表示フォントサイズ */ /* メニュー文字列 */ char *menu[]= { "10dot フォント", "12dot フォント", "16dot フォント", "24dot フォント", }; int fontlist[]= { 10, 12, 16, 24 }; redraw( wp ) WindowID wp; { DrawBuf dbuf[10], *dp= dbuf; DrawSetClear( dp++, ColorGray ); DrawSetSymbol( dp++, 0, 0, "サンプル", AttrDefault, Font ); WindowDraw( wp, dbuf, dp-dbuf ); } /* ポップアップメニューの処理 */ popupmenu( wp, info ) WindowID wp; EventInfo *info; { int n, px, py; /* マウスカーソルのグローバル座標を得る */ /* まずウィンドウの HOME 位置の絶対座標を得る */ WindowGetScreenPosition( wp, &px, &py ); px+= info->x; /* それにマウスカーソル位置のローカル座標を加える */ py+= info->y; /* ポップアップメニュー*/ n= PopUpMenu( px-1, py, menu, sizeof menu/sizeof(char*), Font ); if( n >= 0 && n <= 3 ){ Font= fontlist[ n ]; redraw( wp ); } } EventExec( wp, info ) WindowID wp; EventInfo *info; { switch( info->option ){ case EventOpen: WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: redraw( wp ); return TRUE; case EventMouseSwitch: if( info->LeftON || info->RightON ){ popupmenu( wp, info ); return TRUE; } } return FALSE; } WindowMain( argc, argv ) char **argv; { int x= 10, y= 10; AnalyzeArgs( argc, argv, &x, &y, NULL, NULL ); WindowTitleOpen( x, y, 200, 50, NULL, "smpl", Close|Push, EventExec ); } ---------------------------------------------------------------------------- メニュー処理を行っているのは、popupmenu() 内の PopUpMenu() です。 PopUpMenu( x, y, メニュー文字列, 項目数, フォントサイズ ); x, y はメニューを表示する座標で、これはルートウィンドウに対する座標(グローバ ル座標)でなければなりません。しかし、EventMouseSwitch 時に info->x, info->y で与えられる値は、すでにローカル座標に変換されてしまっています。 そこで、まずローカル座標をグローバルに変換しなければなりません。 WindowGetScreenPosition( wp, &px, &py ); これは、それぞれのウィンドウ内の HOME 位置が、グローバル座標でどの位置に当た るかを返します。その HOME 位置の座標に、先程のローカル座標 info->x, info->y を加えてやればいいのです。 メニュー文字列は char ** で与えます。これは最初に menu[] というポインタ配列 で定義しています。メニューの項目数は、menu[] がポインタ配列だということを利 用して、コンパイラに計算させています。 sizeof menu / sizeof(char*) これが項目数に相当します。このように記述しておけば、menu[] を書き換えるだけ で簡単に内容の増減ができるというわけです。 さて PopUpMenu() の戻り値ですが、これはメニューのどれかが選ばれた場合、項 目番号(各項目の上から順に 0〜と振った番号)が返ってきます。つまり 0〜項目数 -1 の範囲になるわけです。どれも選択されなかった場合は -1 が返ります。 ●プッシュボタン プッシュボタンを作るには、まずボタンの形を描画して、EventMouseSwitch 発生時 に、マウスカーソルがどのボタンの上にあるか座標比較して求めなければなりません。 多くのアプリケーションは、この方法でウィンドウ内のマウス操作を行っています。 この処理はかなり面倒になるので、それらを一手に引き受けてくれる便利なライブ ラリが存在します。MgButton ライブラリです。これは corlib.a に含まれています。 ---------------------------------------------------------------------------- #include int WindowHeapSize= 1024*2; /* MgButton ライブラリは HEAP を使う! */ int Attr= AttrDefault; redraw( dp ) DrawBuf *dp; { DrawSetSymbol( dp, 0, 0, "サンプル", Attr, 24 ); return 1; } EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[30], *dp= dbuf; static MgButton mb; /* MgButton で使う構造体 */ switch( info->option ){ case EventOpen: MgButtonInit( &mb ); /* MgButton 初期化 */ /* ボタン定義 */ MgButtonSetSymbol( &mb, 4,34, 4, 1, " 灰色 ", AttrDefault,12 ); MgButtonSetSymbol( &mb,54,34, 4, 3, " 白 ", AttrDefault,12 ); MgButtonSetSymbol( &mb, 4,60, 4, 9, "灰反転", AttrDefault,12 ); MgButtonSetSymbol( &mb,54,60, 4,11, "白反転", AttrDefault,12 ); WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: DrawSetClear( dp++, ColorGray ); dp+= redraw( dp ); /* ボタン表示 */ WindowDraw( wp, dbuf, MgButtonSetDraw( &mb, dp ) + dp-dbuf ); return TRUE; case EventMouseSwitch: if( info->LeftON ){ int a; /* ボタン操作 */ if( a= MgButtonOperation( wp, info, &mb ) ){ Attr= a; WindowDraw( wp, dbuf, redraw( dbuf ) ); } return TRUE; } } return FALSE; } WindowMain( argc, argv ) char **argv; { int x= 10, y= 10; AnalyzeArgs( argc, argv, &x, &y, NULL, NULL ); WindowTitleOpen( x, y, 200,100, NULL, "smpl", Close|Push, EventExec ); } ---------------------------------------------------------------------------- 先程のポップアップメニューと似たようなプログラムを、プッシュボタンで作って みました。今回は大きさじゃなくて、色の変更を行っています。 まず、この MgButton ライブラリは HEAP を使うので(内部で malloc() している)、 WindowHeapSize の設定を忘れないで下さい。 MgButton ライブラリは、MgButton という構造体で管理されています。プログラム では MgButton mb; と宣言しています。これは必ず global か static にしてメモリ に割り付けて下さい。 最初に MgButtonInit( &mb ); で初期化したのち、それぞれボタンを定義していき ます。ここでは EventOpen 時に MgButtonSetSymbol() を使っています。これは四角 いボタンで、内部に任意の文字を書き込むことができます。 MgButtonSetSymbol( &mb, x, y, 枠幅, ボタンナンバー, 文字列, attr, font ); x, y はボタンを置く座標です。ボタンの大きさは、この場合文字列の文字数とフォ ントサイズで決まります。枠幅というのは、文字列から枠の四角までどれだけ間を開 けるか、を示しています。ボタンナンバーというのは、そのボタンが押された時に返 される値です。EventMouseSwitch の時に、この値によってどのボタンが押されたの かを判断するわけです。 この MgButtonSetSymbol() 以外にも、Sheet(絵)で書いたボタンや、トグルボタン など、さまざま定義することができます。 MgButtonSetDraw( &mb, DrawBuf ) これは各ボタンの描画設定をする関数です。戻り値は消費した DrawBuf の数です。 EventRedraw 時に使います。 MgButtonOperation( &mb, info, wp ) これは、EventMouseSwitch 時に、どのボタンが押されたのかを調べるために使いま す。もしどれかのボタンが押されていれば、そのボタンのボタンナンバーを返し、ど れもおされてなければ FALSE を返します。 このサンプルプログラムでは、ボタンナンバーに直接必要な値である文字アトリビュー ト(数値)を設定してしまい、戻り値をそのまま使うという方法を取っています。 ●文字列の入力 文字列の入力は、MgInput ライブラリを使うとたいへん簡単でかつ便利です。この ライブラリは corlib.a (または libcor.a) に含まれています。 ---------------------------------------------------------------------------- #include int WindowHeapSize= 0; EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[1]; static MgInput mp; switch( info->option ){ case EventOpen: WindowSetEventAttr( wp, EventAttrDefault|EventUserON ); /* 入力エリアをオープン */ MgInputScrollOpen( &mp, 10, 10, wp, 30, AttrDefault, 10 ); WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: DrawSetClear( dbuf, ColorGray ); WindowDraw( wp, dbuf, 1 ); /* 入力エリアの描画 */ MgInputRedraw( &mp ); return TRUE; case EventKey: /* 改行の入力で中身をクリアするだけ */ if( info->KeyCode == '\r' ){ MgInputClear( &mp ); return TRUE; } case EventMouseSwitch: case EventMouseEnter: case EventMouseOut: /*これ1つでまとめて処理してくれる*/ return MgInputSendEvent( &mp, info ); case EventUser: /* Paste は全部キー入力として受け取る */ return ClipGetKeyboardAll( wp, info ); } return FALSE; } WindowMain() { WindowTitleOpen( 10, 10, 200, 80, NULL, "mg", Push|Close, EventExec ); } ---------------------------------------------------------------------------- このプログラムは、単純に1行入力を行うだけのプログラムです。残念ながら文字 列を入力しても何も起こりません。ただ、この入力には行編集が可能で、ED.X 風キー 操作、または Emacs 風キー操作の双方でコントロールできます。デフォルトは ED.X キーバインドですが、あらかじめ環境変数 KOWINKEY に "iemacs" と書いておけばデ フォルトで Emacs キーバインドになります。また入力中に、[CTRL]+[_] [e] と入力 しても Emacs モードになります。 この関数は表示エリアが小さくても、キー入力に応じて内容をスクロールさせ、長 い文字列入力を可能にしています。ここではスクロールバーつき関数を使っています が、スクロールバーがじゃまな場合はスクロールバーなしの入力関数を使うこともで きます。 ●低レベルな文字列の入力 MgInput() を使えば行入力は極めて簡単ですが、parts.a(libparts.a) を使った 入力ルーチンもおまけで例としてあげておきます。MgInput() は比較的新しいライブ ラリなので、昔のプログラムでは全部この方法で記述されていました。参考用にどう ぞ。行編集機能は MgInput() と同じですが、エリア内のスクロールはしません。 ---------------------------------------------------------------------------- #include int WindowHeapSize= 0; char buffer[30]; /* キー入力した文字列が入る */ EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[30], *dp= dbuf; static InputClass input; /* Input 関数で使う構造体 */ switch( info->option ){ case EventOpen: /* 文字列入力位置の座標と、書き込むバッファ等を定義する */ InputSet( &input, 4, 10, buffer, 20, AttrDefault, 12 ); *buffer= '\0'; WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: DrawSetClear( dp++, ColorGray ); WindowDraw( wp, dbuf, InputSetDraw( dp, &input )+1 ); return TRUE; /* キー入力処理 */ case EventKey: if( info->KeyCode == '\r' ){ /* RETURN キーが押された場合*/ /* 取り敢えず、入力行をクリアするだけ */ info->KeyCode= 'u' & 0x1f; /* CTRL-U */ } WindowDraw( wp, dbuf, InputKey( dbuf, &input, info->KeyCode, info->ShiftStat ) ); return TRUE; /* マウスがウィンドウに出入りした時に、カーソルが ON/OFF して ウィンドウのフォーカスがわかるようにする処理 */ case EventMouseEnter: case EventMouseOut: WindowDraw( wp, dbuf, InputSetCursorVisible( dbuf, &input, info->option == EventMouseEnter ) ); return TRUE; } return FALSE; } WindowMain( argc, argv ) char **argv; { int x= 10, y= 10; AnalyzeArgs( argc, argv, &x, &y, NULL, NULL ); WindowTitleOpen( x, y, 200,50, NULL, "smpl", Close|Push, EventExec ); } ---------------------------------------------------------------------------- -- 1994 9/04 最初に作成 1995 9/15 ライブラリの追加で内容が古くなった部分をバッサリ入れ替え 小笠原博之 oga@dgw.yz.yamagata-u.ac.jp DenDenNET: DEN0006 COR.