Ko-Window プログラミング、入門編 その1 「プログラムの基本」 ここではもっとも簡単なサンプルプログラムを具体例として説明を行います。 ●サンプルプログラム これは何も書いてない四角いウィンドウを開くだけのプログラムです。そのまま切 り取ってコンパイルできます。 ---------------------------------------------------------------------------- #include #include int WindowHeapSize= 0; EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[1]; switch( info->option ){ case EventOpen: WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: DrawSetClear( dbuf, ColorGray ); WindowDraw( wp, dbuf, 1 ); return TRUE; } return FALSE; } WindowMain() { WindowTitleOpen( 10, 10, 80, 80, NULL, "smpl", Close|Push, EventExec ); } ---------------------------------------------------------------------------- ● main() 関数はどこに? Ko のプログラムでは WindowMain() という名前の関数から実行されます。引数と して argc, argv を取ることができます。(envp はありません) ●ウィンドウの開き方 サンプルプログラムでは、WindowMain() の中身が1行しかありません。これでは、 1行だけ実行したらすぐプログラムは終了してしまうはずですが、実際その通りです。 その1行でタイトルバーつきウィンドウを1つ開いたら、すぐにコマンドラインに 戻ってきます。ウィンドウの開き方は以下の通りです。 WindowTitleOpen( 10, 10, 80, 80, NULL, "smpl", Close|Push, EventExec ); ↑ ↑ ↑ ↑ ↑ ↑ ↑ ウィンドウを開くX座標,Y座標 | | | | | | | | | | ウィンドウの大きさ 横&縦 | | | | | | | | ルートの中に開くことを意味します | | | | | | タイトルバーに表示する文字列です | | | | タイトルバーに並ぶスイッチを指定します | (他にも Resize,Icon,Zoom が指定できる) | | イベント関数を登録します これが重要!必ず必要 ●たった1行で終わりのプログラム サンプルプログラムでは、1つウィンドウを開き、そのウィンドウのイベント関数 は EventExec() だ、と宣言して、それでおしまいです。このプログラムは本当に1 行しかありません。 すぐにコマンドラインに戻っても、開いたウィンドウは残ります。Command.win の ps コマンドで見るとプロセスは残っているのはわかるでしょう。(この辺りは常駐 プログラムに近い) あとは、ウィンドウの移動やら、再描画やら、ウィンドウのクローズやらなにやら は、全部必要な時にサーバーが登録したイベント関数を随時呼び出しして勝手に処理 します。 タイトルバーの close ボタンをクリックすれば、ウィンドウは消えてプロセスも いなくなります。 Ko-Window のプログラミングが、いかに単純で簡単なものかわかっていただけまし たでしょうか? ●イベント関数 EventExec() 関数の引数 wp は開いたウィンドウ自分自身を示す ID です。info は発生したイベントの情報が入っている構造体を示しています。 switch 文を見てみます。これは、発生したイベントによって分岐し、それぞれの 処理を行っているだけに過ぎません。 switch( info->option ){ /* イベントの種類は info->option に入る */ case EventOpen: /* ウィンドウが開く時一度だけ発生する */ WindowRedraw( wp ); /* 自分自身のウィンドウを描画する */ return TRUE; case EventClose: /* ウィンドウが閉じられる時発生する */ WindowClose( wp ); /* 自分自身を close する */ WindowConnectionClose(); /* プログラムの終了宣言 */ return TRUE; case EventRedraw: /* ウィンドウ内を書き直す時呼ばれる */ /* 描画手続きに画面クリアを設定 */ DrawSetClear( dbuf, ColorGray ); WindowDraw( wp, dbuf, 1 ); /* 描画手続きに従って表示 */ return TRUE; } return FALSE; そのイベントを処理した時は TRUE を返し、処理しなかった場合は FALSE を返し ます。 ・EventOpen EventOpen は、最初に一度だけ呼び出されるので、初期設定を行います。このサン プルでは WindowRedraw() によってまずウィンドウ内部の描画を行っています。 ・EventRedraw ウィンドウ内部を表示する必要が生じた時に呼び出されます。例えば上に重なって いたウィンドウを閉じた場合、または WindowRedraw() を呼び出した場合です。 DrawSetClear( dbuf, ColorGray ) はウィンドウ内部を ColorGray (灰色) で塗り 潰せという意味です。dbuf は描画手続きを設定するバッファで、イベント関数の先 頭で DrawBuf dbuf[1]; として1つ分だけ確保されています。 WindowDraw( wp, dbuf, 1 ) は、描画手続き dbuf に従って実際に画面に表示しま す。Ko-Window ではいったん DrawBuf に表示手順を全部書き込んでおいてから、 WindowDraw() で一気に書き出す、という方法を取ります。DrawSet????() は手続き を登録するだけなので、描画そのものはしません。つまり WindowDraw() を実行しな ければ画面上に描画が現れないのです。引数の最後の '1' は、描画手続きが何ステッ プあるかを示しています。 ・EventClose close スイッチをクリックするなど、ウィンドウをクローズしろというイベントが 発生すれば呼び出されます。ここでは WindowClose( wp ) によって、自分自身を閉 じています。その後の WindowConnectionClose() によってそのプログラムの終了を 宣言し、このプロセスはメモリ上から消えます。 この2つは名称が似ていますが、全く異なる意味を持っています。WindowClose() はウィンドウを閉じる命令であって、WindowConnectionClose() はプロセスを終了さ せる命令です。 ●さまざまな描画 ウィンドウ内の描画を何か行ってみましょう。EventRedraw の部分を以下のように 修正してみて下さい。 ---------------------------------------------------------------------------- EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[2]; /* <= 描画ステップ数が増えたので[2]になる */ switch( info->option ){ case EventOpen: WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: DrawSetClear( dbuf, ColorGray ); /* 新しく増やした描画手続き ↓ */ DrawSetSymbol( dbuf+1, 3, 3, "文字列", AttrDefault, 16 ); WindowDraw( wp, dbuf, 2 ); /* <= ここの最後も 2 になる */ return TRUE; } return FALSE; } ---------------------------------------------------------------------------- DrawSetSymbol() は、文字列の描画手続きを DrawBuf に設定する関数です。 DrawSetSymbol( dbuf+1, 3, 3, "文字列", AttrDefault, 16 ); ↑ ↑ ↑ ↑ 文字列を表示する座標 X,Y | | 表示するフォントサイズ (ウィンドウ内部の座標) | |(10,12,16,24のうちどれか) | | 表示する文字列(安易でごめん) | | 表示色(ここではデフォルトカラー) 表示色は4種類で、ColorBlack(0:黒), ColorGray(1:灰色), ColorGraphic(2:透明), ColorWhite(3:白) となります。 さらに属性値を加えることができ、ColorWhite|AttrHighLight|AttrReverse のよ うに与えることができます。 AttrReverse 反転 AttrHighLight ハイライト(太文字) AttrMesh 網かけ AttrWhiteRev 白反転 AttrUnderLine 下線 AttrDefault は ColorGray|AttrReverse (灰色の反転,つまり灰色に黒文字) とし て定義されています。 ●いろいろな描画 これもいろいろな描画を行ってみた EventExec() ルーチンの例です。文字の大き さやアトリビュートを変えて表示しています。 ---------------------------------------------------------------------------- EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[20], *dp; /* 描画ステップ数が増えるたびにいちいち書き換えるより、最初から多目 に 10 や 20 くらい確保しておくといいでしょう */ switch( info->option ){ case EventOpen: WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: dp= dbuf; /* DrawBuf のポインタを用意しておく */ DrawSetClear( dp++, ColorGray ); DrawSetSymbol( dp++, 16, 7, "文字列", ColorGray, 16 ); DrawSetSymbol( dp++, 4, 28, "小さい文字列", ColorWhite, 12 ); DrawSetSymbol( dp++, 4, 44, "大きい", ColorWhite|AttrHighLight, 24 ); DrawSetLine( dp++, 1, 1, 78, 78, ShadowDown, OptionShadow ); WindowDraw( wp, dbuf, dp-dbuf ); /* このようにポインタを用いて DrawSet すると、数が増減したり 描画順番を入れ替えたりしても、いちいち描画ステップ数を数 えたり dbuf+n とかの番号を付け替えたりする必要がないので 便利です(ただし内部で割り算に展開される) */ return TRUE; } return FALSE; } ---------------------------------------------------------------------------- ●マウスクリック 今度は EventExec() を以下のように変えて、マウスクリックに反応するようにし てみましょう。ウィンドウ内でマウスの左ボタンをクリックすると、「ここ」という 文字がそのクリックした位置に移動します。 ---------------------------------------------------------------------------- EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[20], *dp= dbuf; static cursx, cursy; switch( info->option ){ case EventOpen: WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: DrawSetClear( dp++, ColorGray ); DrawSetSymbol( dp++, cursx, cursy, "ここ", AttrDefault, 16 ); WindowDraw( wp, dbuf, dp-dbuf ); return TRUE; case EventMouseSwitch: if( info->LeftON ){ cursx= info->x; cursy= info->y; WindowRedraw( wp ); } return TRUE; } return FALSE; } ---------------------------------------------------------------------------- マウスクリックに処理を行わせる時は、このように case EventMouseSwitch: とい う処理を書き加えます。どのボタンが押されたのかは info という変数を介して調べ ることができます。 info->LeftON 左ボタンが押された(前は押されてなかった) info->RightON 右ボタンが押された(前は押されてなかった) info->LeftOFF 左ボタンを離した(前は押されていた) info->RightOFF 右ボタンを離した(前は押されていた) info->LeftStat 左ボタンが押されている(前の状態に関係ない) info->RightStat 右ボタンが押されている(前の状態に関係ない) これらはマウスボタンが押された状態を示し、TRUE または FALSE の値を取ります。 もし左ボタンが押された時に何か行わせたい時は、上の例のように if( info->LeftON ){ なになに } と書きます。この部分を info->LeftON でなく info->LeftStat に変えたらどうなる でしょうか。ためしに上の例のプログラムで実験してみて下さい。「ここ」という文 字の表示の移動が、クリックした時だけでなく、ボタンを押してる間中行われるよう になるでしょう。 ボタンを押した時のカーソル座標は info->x マウスカーソルのX座標(ウィンドウ座標) info->y マウスカーソルのY座標(ウィンドウ座標) で得ることができます。もちろんこれは、画面全体の座標ではなくて、それぞれのウィ ンドウ内の HOME 位置を原点としたローカル座標です。 ●ちょっとは実用アプリケーション? 今までのウィンドウプログラム例を応用して、ちょっとだけ実用的なプログラムを 組んでみましょう。EventExec() を以下のように修正します。 ---------------------------------------------------------------------------- EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[10], *dp= dbuf; switch( info->option ){ case EventOpen: WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: DrawSetClear( dp++, ColorGray ); DrawSetSymbol( dp++, 0, 20, "0秒ADJUST", AttrDefault, 16 ); WindowDraw( wp, dbuf, dp-dbuf ); return TRUE; case EventMouseSwitch: if( info->LeftON ){ B_BPOKE( 0xe8a01b, B_BPEEK( 0xe8a01b )|1 ); /*bank1*/ B_BPOKE( 0xe8a003, 1 ); /* RTC adjust */ B_BPOKE( 0xe8a01b, B_BPEEK( 0xe8a01b )&0xfe);/*bank0*/ } return TRUE; } return FALSE; } ---------------------------------------------------------------------------- これは、ウィンドウ内をクリックすると、時計の「0秒調整」を行うようにするプ ログラムです。「0秒調整」というのはデジタルの腕時計等には必ずといっていいほ どついている、時報に合せて時計を簡単に修正できるたいへん便利な機能です。 内部時計の秒が 00〜29 の場合は 00 に戻し、30〜59 の場合は1分繰り上げしたの ち秒を 00 にします。つまり±30秒以内の遅れ進みならば、テレビやラジオの時報に 合せてこのウィンドウをクリックするだけでいつでも正確な時間に合わせることがで きるというものです。 残念ながらこのプログラムでは、それ以上の時計の狂いは修正できないので、他の コマンドで大ざっぱに時間を合わせたのち時報に合わせて0秒調整するといいでしょう。 プログラムでは、マウスの左ボタンが押された時に X68K 内蔵時計 RTC の adjust レジスタに 1 を書き込んでいます。ウィンドウデザインを変えたり、表示を直したり、 いろいろと手を加えてみて下さい。ウィンドウアプリケーションを作るのって、結構 簡単だというのがわかっていただけましたでしょうか。あとはアイデア次第です。 なお使うにあたってちょっとだけ注意点があります。このプログラムで 0秒調整 し ても、起動している他の時計プログラムの表示にそれが反映するまで若干のタイムラ グがあるということです。これは、その時計プログラムが表示の更新を1秒単位で行っ ているからです。だから内蔵時計の更新と必ずしも時計プログラムの表示は一致しま せん。時計を直して内部が正しい時間になっても、ウィンドウ上の時計の表示は最大 1秒遅れで表示が書き変わることがありますので注意して下さい。 ●マウスイベントの戻り値 ところで、ここまでのマウスボタンを使ったプログラム例では、右ボタンを押して も何も起こりません。最初の頃、いろいろな表示とか行わせていた時は、右ボタンを 押すとウィンドウマネージャーのポップアップメニューが出ていたのに、と疑問に思 う方もいるかもしれません。 それは、特に右ボタンに処理を行わせていないのにすでにイベント処理を行いまし た、という印の return TRUE; で終了しているからです。 だからその部分をちょっと修正して、左ボタンが押された時はなんらかの処理をし たのち TRUE を返し、そうでない時は FALSE を返すようにしてみましょう。 ---------------------------------------------------------------------------- case EventMouseSwitch: if( info->LeftON ){ ... return TRUE; } return FALSE; ---------------------------------------------------------------------------- こうすると右ボタンを押した場合はまだ何も処理していないと判断され、ウィンド ウマネージャー (ウィンドウ背景とかでポップアップメニューを出しているプログラ ム) がイベントを受け継いでポップアップメニューを出してくれます。 とにかく、マウスボタンを処理しない時は FALSE を返しておくと、ポップアップ メニューが勝手に出る、とだけ覚えておいて下さい。逆にポップアップメニューが出 て欲しくない時は、たとえ何も処理しなくても TRUE を返せばいいことになります。 ●ウィンドウの大きさを変える 今までは、小さな固定サイズのウィンドウを開いて内部に描画を行っていました。 次は自由に大きさを変えられるウィンドウを作ってみましょう。今度は WindowMain の中も少しだけ修正しましたので、再び全リストを掲載してみることにします。 ---------------------------------------------------------------------------- #include #include int WindowHeapSize= 0; EventExec( wp, info ) WindowID wp; EventInfo *info; { DrawBuf dbuf[10], *dp= dbuf; int h, v; switch( info->option ){ case EventOpen: WindowSetEventAttr( wp, EventAttrDefault|EventResizeON ); WindowRedraw( wp ); return TRUE; case EventClose: WindowClose( wp ); WindowConnectionClose(); return TRUE; case EventRedraw: WindowGetViewSize( wp, &h, &v ); /*ウィンドウの大きさを得ます*/ DrawSetClear( dp++, ColorGray ); DrawSetLine( dp++, 2, 2, h-3, v-3, ColorBlack, OptionLine ); DrawSetLine( dp++, 2, v-3, h-3, 2, ColorBlack, OptionLine ); DrawSetLine( dp++, h/4, v/4, h*3/4, v*3/4, ColorBlack, OptionBox ); WindowDraw( wp, dbuf, dp-dbuf ); return TRUE; case EventResize: WindowResize( wp, info->x, info->y, info->h, info->v ); return TRUE; } return FALSE; } WindowMain() { WindowTitleOpen( 10, 10, 80, 80, NULL, "smpl", Resize|Close|Push, EventExec ); } ---------------------------------------------------------------------------- ここで増えているのは、EventRedraw 内の WindowGetViewSize() と EventResize、 そして WindowMain() 内の Resize| の部分です。 実行すると、ウィンドウ内にいっぱいに「×」と「□」を重ねた図形を描きます。 タイトルバーにリサイズボックス「┌」があるので、そこをドラッグするとウィンド ウサイズを自由に変更することができます。大きさを変えても、中の表示はちゃんと ウィンドウいっぱいに描かれる、というわけです。 まず、WindowMain() の中にある Resize| の部分は、タイトルバーにリサイズボッ クスを追加することを意味しています。これがなくても、ウィンドウマネージャーの ポップアップメニューからリサイズを選べば、ウィンドウの大きさを変えることがで きます。 実際に大きさを変えているのは EventResize の部分です。ここでは変更すべき座 標が info の以下の変数に代入されて呼び出されます。 info->x リサイズ後移動したウィンドウ左上の X 座標 info->y リサイズ後移動したウィンドウ左上の Y 座標 info->h リサイズ後のウィンドウの大きさ、横サイズ info->v リサイズ後のウィンドウの大きさ、縦サイズ この値をもとにして WindowResize() という命令を呼び出せば、ウィンドウの大きさ を変更することができる、というわけです。 EventRedraw の部分では、ウィンドウの大きさに合せて描画を行わなければなりま せん。そのため描画前に WindowGetViewSize() で、ウィンドウの表示領域の大きさを 得ています。これは横と縦のドットサイズなので、この情報をもとに描画を行ってい ます。 ●起動時のオプション解析 ウィンドウプログラムには、作成する時にある程度の暗黙のルール、があります。 その1つが、起動時に -x, -y オプションで起動位置を指定できる、というものでしょ う。また起動時にウィンドウサイズを指定するには -h, -v オプションです。 例えば smpl -x300 -y20 として実行すると、画面上の (300,20) の位置に起動し ます。 これらのオプションは、すべてのアプリケーションで使われるため、ライブラリ化 されており、簡単に使うことができます。 先の例のリサイズプログラムの例で、WindowMain() を以下のように修正して下さ い。これで -x, -y, -h, -v オプションが使えるようになり、起動時にウィンドウ位 置や大きさを与えることができるようになります。 ---------------------------------------------------------------------------- WindowMain( argc, argv ) int argc; char **argv; { int x= 10, y= 10, h= 80, v= 80; argc= AnalyzeArgs( argc, argv, &x, &y, &h, &v ); WindowTitleOpen( x, y, h, v, NULL, "smpl", Resize|Close|Push, EventExec ); } ---------------------------------------------------------------------------- AnalyzeArgs() というのが引数解析を行う部分です。もし -x,-y,-h,-v のオプショ ン指定があれば必要な変数にその値を設定します。オプションが無い場合は変数の値 は変わりません。AnalyzeArgs() は処理したオプションを削除して新しい argc, argv を返すので、その後にアプリケーション固有のオプションスイッチや、ファイル名等 の引数を処理することができるようになります。 もしウィンドウサイズは固定で、-x, -y のオプションだけ使いたい、という場合は ---------------------------------------------------------------------------- WindowMain( argc, argv ) int argc; char **argv; { int x= 10, y= 10; argc= AnalyzeArgs( argc, argv, &x, &y, NULL, NULL ); WindowTitleOpen( x, y, 80, 80, NULL, "smpl", Resize|Close|Push, EventExec ); } ---------------------------------------------------------------------------- のように AnalyzeArgs() の不要な部分に NULL を与えれば OK です。(注意: &x, &y の部分に NULL を与えることはできません) 1994 8/22 小笠原博之 1995 9/15 修正 oga@dgw.yz.yamagata-u.ac.jp DenDenNET: DEN0006 COR.