C言語によるMacintosh
Toolboxプログラミング〜
広松 悠介著
1・初めに
題はすごそうだったりするんですが、かく言う僕自身も、プログラミングを始めたのは大学に入学してからです。ですから、せいぜいほんの触りくらいしかできないと思うので、もし、これ以上やってみたいと思ったのなら、参考文献でも買って読んでみて下さい。むしろ、やってみたい方は参考文献を読んだ方がいいかもしれません。
2・必要なもの
実際にプログラムを動かしたいのなら、MacOSが当然必要です。
後、プログラミングの環境が必要です。幾つかありますが、僕が使っているのはCodeWarriorです。pro版は本当に高いので、買うのなら、アカデミック版か、Leaning
Editionを買いましょう。Discover版の方は、ある程度開発環境が制限されていますが、ここに書いてあるものは、多分大丈夫だと思います。そもそも、僕が使っているのがDiscover版ですので。
それと、ResEdit。これは大体のMacintoshに付属していると思いますが、もしない場合は、インターネット上でダウンロードしてきてください。これは無料です。
後、C言語を知っていないと、プログラムが読めません。『C言語なんてなにやらさっぱりだけど、プログラミングはしてみたい!』という方は、まずC言語を学びましょう。さすがにそこまではサポートしませんので、インターネットで調べたり、本屋で売っている入門の本を買って読みましょう。"C"ですよ?"VC"ではありません、ご注意を。
ああ、そうだ。忘れていましたが、かなり気合いが必要です。一番必要かもしれません。
3・Macintoshプログラミング
警告!! これ以上先に行くのは、C言語を勉強してからにしてください。そうしないと魂抜かれます(?)。
なお、今回は全部で4つのプログラムを作ります。
3.1ウィンドウを出すプログラム
ではさっさと行きましょう。題名の通り、ウィンドウを出して、少しだけ待ってから終了するプログラムです。内容が少ないようですが、プログラムもやっぱり短いです。CodeWarriorを使っているのなら、プロジェクトはToolboxからたちあげて下さい。
/*******関数宣言********/
void
ToolboxInitialize(void); /*Toolboxを初期化する関数*/
/*******main関数*******/
void
main(void)
{
WindowPtr a_window; /*ウィンドウのポインタを格納する(Toolboxの定義する構造体)*/
unsigned
long delaytime;
ToolboxInitialize();
a_window =
GetNewCWindow(128, 0, (WindowPtr)-1); /*ウィンドウを表示する*/
Delay(120,
&delaytime); /*1/60 × 120
秒だけ待つ*/
DisposeWindow(a_window); /*ウィンドウを削除する*/
}
/*この関数郡をちゃんと呼ばないと、大変なことになるので注意!*/
void
ToolboxInitialize(void)
{
InitGraf(&qd.thePort);
/*QuickDraw(グラフィック環境のマネージャ)を初期化。引数は現在のグラフポート*/
InitFonts(); /*フォントマネージャを初期化*/
InitWindows(); /*ウィンドウマネージャを初期化*/
InitMenus(); /*メニューマネージャを初期化*/
TEInit(); /*テキストエディットを初期化*/
InitDialogs(0L); /*ダイアログマネージャを初期化*/
FlushEvents(everyEvent,
0); /*イベントを初期化*/
InitCursor(); /*カーソルの形状を初期化。(矢印)*/
}
3.1の解説の始め
本当に最初のプログラムですね。ウィンドウが出てきて2秒してから消えます。これで解説終わり…といきたいところですが、実はここのプログラムをまるごと書き込んだだけでは動きません。エラーがでること間違いなしです。というのも、カラーウィンドウを作る作業をしている、GetNewCWindowの最初の引数"128"というのは、リソースの番号だからです。
解説1・リソースって何なのか?
う〜ん、そういえば、あんまり気にしてなかったな〜、とふと思いましたが、簡単に言うと、ややこしい作業が必要なものを、あらかじめ作っておいたものでしょうか?基本的に、ResEditで作ります。リソースがないと、どれくらいややこしくなるかというと、例えば、今回のプログラム内では、GetNewCWindowという関数でウィンドウを作っていますが、こいつをリソースなしで同じように再現しようとすると、NewCWindowという関数を使います。しかし、その引数が、頭が痛くなるくらい多いのです。
WindowPtr
NewCWindow(Ptr, Rect *, Str255, Boolean, short, WindowPtr, Boolean,
long);
返値はGetNewCWindowと同じで、ウィンドウへのポインタです。では、引数を左から説明していくと、
Ptrはウィンドウを作るためのメモリへのポインタです。GetNewCWindowの第二引数に相当します。ここを0にすると、システムが勝手にメモリを確保してくれます。僕も今のところ0以外をいれたことがありません。
Rect
*はRectのポインタ…といっても、RectなんてCにありませんでした。では、これは何か?これはToolboxで定義されている構造体で、
typedef
struct{
short top;
short left;
short right;
short
bottom;
}Rect;
となっています。ここではウィンドウの大きさをあらわしています。
Str255はウィンドウの名前です。
Boolean。これもCにはありません。ですが、Rectと同様、typedef
Boolean
char;と定義されているようです。Booleanは論理値で、true、falseを格納するのに使います。今回のBooleanは、ウィンドウが最初から表示されるか(true)、そうでないか(false)を格納します。
shortはちょっとややこしいので、あまり説明はしませんが(というより、力不足で説明できないので)、ここでウィンドウの見栄えを決めています。
ここで示すウィンドウの後ろに、新しく作ったウィンドウが出現します。GetNewCWindowの第三引数もこれと同じです。一番後ろにしたいときは0を、一番先頭にしたいときには-1を、それぞれWindowPtrでキャストしてください。
二つめのBooleanは、ズームボックスをつけるかどうかです。もちろん、つける時はtrueです。
最後のlongはプログラマが自由に値を格納できる場所(RefCon)です。
というわけで、とても疲れる作業になってます。ややこしい。これでは本来の場所に入る前に燃え尽きます。それは嫌なので、ResEditでもう少しわかりやすく、楽しくウィンドウを作りましょう。
1・まず、ResEditを開きます。
2・ビックリ箱のスタート画面がでてきたのなら、クリックします。
3・Newを選んでリソース画面を新たに作成します。
4・ResourceのNewResourceを選びます。
5・なにやらたくさんありますが、とりあえずWINDを選びます。
6・開いたリソース内で、さらにNewResourceを選びます。すると、ウィンドウのリソースが一個できます。
7・できたらさっさとセーブして、(CodeWarriorの場合は)プロジェクトに『ファイルを追加』で組み込んでください。
8・できあがり。組み込んだ後でも、編集できます。
となります。後は今回のプログラムを実行するだけです。
注意! リソースナンバーはなぜか128から始まりますが、それ以下にはしないほうがいいです。あと、25000くらいを超えるのも、止めておいた方が、いいです。それらのナンバーはシステムで使うらしいので。なお、リソースナンバーを変えた場合は、GetNewCWindowの第一引数も変化するので注意。
解説2・Delayおよび、DisposeWindow
というわけで、次の関数に入りたいと思います。
Delay( (数値), unsigned long
*);
数値×(1/60)だけ待ちます。待っている時間を格納するのが、unsigned long
*です。あまり使い道がないです。
DisposeWindow(WindowPtr);
WindowPtrで指すウィンドウを破棄します。
以上で、今回の解説は終了です。なお、構造体WindowPtrの解説はしません。解説するには、僕が力不足だし、しかもとてもややこしい所だからです。
3.2ウィンドウにPICT画像を描画し、マウスがクリックされるまで終わらないプログラム
急に色々増えましたね…いや、気にしないでください(ヲイ)
/*******関数宣言********/
void
ToolboxInitialize(void); /*ここは変わらないので、下に書いてません。*/
void
EventLoop(void);
/*******main関数********/
void
main(void)
{
WindowPtr a_window;
PicHandle picture; /*リソースから取り出したPICTを格納する構造体*/
Rect pict_rect; /*絵の大きさを決める*/
ToolboxInitialize();
a_window
= GetNewCWindow(128, 0, (WindowPtr)-1);
picture =
GetPicture(128); /*PICTを格納*/
/*PICTの大きさを決める*/
pict_rect.top =
10;
pict_rect.left = 10;
pict_rect.right =
pict_rect.top + 50;
pict_rect.bottom = pict_rect.right +
50;
SetPort(a_window); /*描画の環境をウィンドウに設定する*/
DrawPicture(picture,
&pict_rect);
/*絵をウィンドウに描画する*/
EventLoop(); /*イベントループ*/
DisposeWindow(a_window);
ReleaseResource((Handle)picture);
/*PICTを解放*/
}
/*******マウスがクリックされるまで続く関数********/
void
EventLoop(void)
{
EventRecord
mouseloop; /*イベントを格納する構造体*/
while(1){
/*イベントを待ち、イベントが起こったら何のイベントか調べる。*/
if(WaitNextEvent(everyEvent,
&mouseloop, 0, 0L) ==
true){
switch(mouseloop.what){
case
mouseDown: /*マウスがクリックされたのなら終了*/
return;
case
autoKey:
case
keyDown:
break;
}
}
}
}
3・2の解説
解説1・PICT画像を出力する
これが、今回の前半です。では、まずPICTリソースの作り方から。
これはウィンドウの時と同じです。既にWINDリソースのあるところに、さらにPICTリソースを作りましょう。後は、適当に棒人間やらボールやらでも、描画アプリケーションで落書きして、PICTリソースとして保存してから、リソース内にコピー&ペーストしましょう。
PicHandle
GetPicture(short);
shortの番号のPICT画像を、PicHandleにして返します。PicHandleはちょっと説明が…なんで(多いような気がする)、とりあえずPICT画像を格納する定義と考えておいてください。
void
SetPort(WindowPtr);
WindowPtrに示すウィンドウに、描画環境を変更します。一度に描画環境になれるのは一つのみです。
void
DrawPicture(PicHandle, Rect
*);
Rectの大きさにしたPicHandleに格納された絵を描画します。
ReleaseResource(Handle);
読み込んだリソースを解放します。今回の場合は、別に描画してすぐにこれをしてもいいです。これは他の読み込んだリソースにも使うもので、Handleにキャストして下さい。
解説2・イベントループ
後半です。EventRecordから解説しましょう。
typedef
struct{
short what;
long message;
long when;
Point
where;
short
modifiers;
}EventRecord;
実はこれらは起こるイベントによって何が入るかが結構変わってしまいます。大体のところは、
what
どんなイベントが起こったかです。
when イベントの起こった時間です。
where
これも構造体。イベントの起こったx,y座標です。
残りの2つは固有情報が入ります。主にmessageで、modifiersは補助です。
Boolean
WaitNextEvent(short, EventRecord *, unsigned long,
RgnHandle);
イベントが起こったらtrueを返す関数です。
shortは欲しいイベント(キーとかマウスとか)大体はeveryEventで全部とっていていいです。
EventRecord
*に、起こったイベントを格納します。
unsigned
longは、イベント入力可能になるまでの時間です。やっぱり1/60秒が基本単位です。
RgnHandleは、ちょっとややこしいところです。特定の位置にカーソルがある場合とかで指示が出せるようですが、0Lにしておけば問題ないです。
switchの中身(what)
mouseDown
マウスクリックのイベントだった場合は、これがwhatに格納されます。今回は、これが来たらreturnを返して終了です。
autoKey
コマンドキーやオプションキーなどが押しっぱなしだった時のイベントです。
keyDown
上記のキー以外の、記号やアルファベットなどが押された場合のイベントです。
2つも新しいことをやったにもかかわらず、3・1より短い気がしますね。多分気のせいでしょう。
3・3メニュー・メニューコマンドを作る
またまた二つ同時にやります。ところで、今までのプログラムでは、メニューが表示されていませんでしたよね?あれは実は自分で作らなければならない所なのです。というわけで、早速やってみましょう。
#define
NO_EVENT 0 /*この2つはコマンドがあったかどうかのフラグ*/
#define QUIT 1
void
ToolboxInitialize(void);/*ここは変わらないので、下に書いてません。*/
void
EventLoop(void);
int MouseEvent(EventRecord *); /*マウスからの場合*/
int
KeyEvent(EventRecord *); /*キーからの場合*/
int
EventMenuChoice(long); /*メニューによるコマンドを見る*/
int
FileMenuChoice(short);
void
main(void)
{
WindowPtr a_window;
PicHandle picture;
Handle a_menu; /*メニューのリソースを格納する*/
Rect pict_rect;
ToolboxInitialize();
a_menu
= GetNewMBar(128); /*メニューのリソースを手に入れる*/
if(a_menu ==
NULL) ExitToShell();
/*失敗したら、プログラムを終了*/
SetMenuBar(a_menu); /*メニューをセット*/
DrawMenuBar(); /*メニューを表示*/
a_window
= GetNewCWindow(128, 0, (WindowPtr)-1);
picture =
GetPicture(128);
pict_rect.top =
10;
pict_rect.left = 10;
pict_rect.right =
pict_rect.top + 50;
pict_rect.bottom = pict_rect.right +
50;
SetPort(a_window);
DrawPicture(picture,
&pict_rect);
EventLoop();
DisposeWindow(a_window);
ReleaseResource((Handle)picture);
}
void
EventLoop(void)
{
EventRecord
loop;
while(1){
if(WaitNextEvent(everyEvent, &loop, 0,
0L) == true){
switch(loop.what){
case
mouseDown:
if(MouseEvent(&loop) ==
QUIT) return;
break;
case
autoKey:
case keyDown:
if(KeyEvent(&loop) == QUIT)
return;
break;
}
}
}
}
/*****マウスから起こるイベント*****/
int
MouseEvent(EventRecord *event)
{
WindowPtr
a_window;
switch(FindWindow(event->where,
&a_window)){
case inMenuBar:
return
EventMenuChoice(MenuSelect(event->where));
case
inDrag:
break;
case
inContent:
break;
}
return
NO_EVENT;
}
/*****キーから起こるイベント*****/
int KeyEvent(EventRecord
*event)
{
if( (cmdKey & event->modifiers) == 0) return
NO_EVENT;
return EventMenuChoice(MenuKey(charCodeMask &
event->message));
}
/*****メニューを選ぶ*****/
int EventMenuChoice(long
menucode)
{
short undercode;
undercode =
LoWord(menucode);
switch(HiWord(menucode)){
case 128:
return
FileMenuChoice(undercode);
}
return
NO_EVENT;
}
int FileMenuChoice(short
undercode)
{
switch(undercode){
case 1:
return
QUIT;
}
return
NO_EVENT;
}
3・3の解説
一気に関数の量が増えましたが、やっていることはメニューを表示して、選ばせるだけです。
解説1・メニューの描画
メニューのリソースの一つは、メニュー1項目毎のリソースMENU。もう一つは幾つかのメニューを一つにまとめ、実際に手に入れるメニューの固まりMBAR。MBARはリソースの中で新規作成することにより、格納できるメニューの欄ができます。そこに、作ったメニュー(今回の場合は"終了"の項目の入った"ファイル"でいいと思います)の番号を入れておきます。それと、MENUの"終了"の項目のcommandの部分に、Qを入れておきましょう。
Handle
GetMenuBar(short);
shortで指定した番号のリソースのメニューのハンドルを返します。
void
ExitToShell(void);
アプリケーションを終了する関数です。今回は、メニューのリソースが見つからなかった場合にこれを呼び出しています。
void
SetMenuBar(Handle);
手に入れたメニューへのハンドルを入れます。
void
DrawMenuBar(void);
メニューを表示します。
解説2・メニューの選択
メニューを選択したかどうか見るのは二ケ所ありますが、結局は同じ関数に入っています。
int
MouseEvent(EventRecord
*);
マウスイベントが起こった場合に呼ばれる関数です。終了が選ばれたらQUITを返します。
int
KeyEvent(EventRecord *);
キーイベントで呼ばれる関数です。MouseEventと用途は同じです。
short
FindWindow(Point, WindowPtr
*);
Pointはマウスの位置を入れ、WindowPtrはイベントの起こったウィンドウへのWindowPtrが格納されます。返値はウィンドウのどの部分かを示すものです。
inMenuBar
今回のイベント処理で使う、メニューにイベントが起こった場合です。
inDrag
ウィンドウの上部です。
inContent
ウィンドウ内を指しています。
long
MenuSelect(Point);
位置から、どのメニューが選ばれたかを、返値longにします。
if( (cmdKey &
event->modifiers) == 0) return
NO_EVENT;
ここでのmodifiersは、押しっぱなしになっていたキーで、コマンドキーが押しっぱなしになっているかどうかを見ています。
charCodeMask
&
event->message
ここでのmessageはコマンドキーと一緒に押されたキーを指しています。それを、charCodeMaskで、文字、数字などのみに絞っています。
long
MenuKey(short);
指定された文字コードを持つメニューのショートカットの値を返値longとします。
int
EventMenuChoice(long);
ここで2つが1つに収束します。ファイルの終了が選ばれたかどうかを見ます。
short
LoWord(long);
longの下16ビットをshort型として返します。
short
HiWord(long);
longの上16ビットをshort型として返します。
switch(HiWord...
long型に入れられた情報の上16ビットがどのメニューかで、下16ビットがどのコマンドかをあらわしています。
int
FileMenuChoice(short);
終了が選ばれた場合に、QUITを返します。なお、メニューバー内の項目は、一番上が1なのに注意して下さい。
さて、今回作ったメニューはファイルの終了だけですが、本当はアップル、ファイル、編集メニューは最低必要で、アップルメニューはアバウトが、編集メニューの取り消し、カット、コピー、ペーストが必要らしいですが、今回はそのあたりを作ってません。やってみてもいいと思います。
3・4ウィンドウをドラッグ&アップデートイベント
ところで、これまでのプログラムでは、ウィンドウの位置を動かすことはできないし、しかも、一度別のウィンドウが上に被さると、その部分の絵が消えてしまうという、不自然なものしかできません。というわけで、今回はウィンドウをドラッグできようにし、さらに、他のウィンドウが上に被さった時に、ウィンドウの状態を補正する、アップデートイベントについて行います。
/*今回変わったのは、イベントの部分とマウスイベントの部分です。*/
void
ToolboxInitialize(void);
void EventLoop(void);
void
EventUpdate(EventRecord *);
int MouseEvent(EventRecord *);
int
KeyEvent(EventRecord *);
int EventMenuChoice(long);
int
FileMenuChoice(short);
void
main(void)
{
WindowPtr a_window;
PicHandle picture;
Handle a_menu;
Rect pict_rect;
ToolboxInitialize();
a_menu
= GetNewMBar(128);
if(a_menu ==
NULL) ExitToShell();
SetMenuBar(a_menu);
DrawMenuBar();
a_window
= GetNewCWindow(128, 0, (WindowPtr)-1);
picture =
GetPicture(128);
pict_rect.top =
10;
pict_rect.left = 10;
pict_rect.right =
pict_rect.top + 50;
pict_rect.bottom = pict_rect.right +
50;
SetPort(a_window);
DrawPicture(picture,
&pict_rect);
EventLoop();
DisposeWindow(a_window);
ReleaseResource((Handle)picture);
}
void
EventLoop(void)
{
EventRecord
loop;
while(1){
if(WaitNextEvent(everyEvent, &loop, 0,
0L) == true){
switch(loop.what){
case
mouseDown:
if(MouseEvent(&loop) ==
QUIT) return;
break;
case
autoKey:
case keyDown:
if(KeyEvent(&loop) == QUIT)
return;
break;
case
updateEvt:/*アップデートイベント*/
EventUpdate(&loop);
break;
}
}
}
}
/*アップデートイベントの処理*/
void
EventUpdate(EventRecord *event)
{
WindowPtr
a_window;
PicHandle picture;
Rect pict_rect;
a_window
= (WindowPtr)event->message; /*今回のmessageはWindowPtr*/
picture
= GetPicture(128);
pict_rect.top =
10;
pict_rect.left = 10;
pict_rect.right =
pict_rect.top + 50;
pict_rect.bottom = pict_rect.right +
50;
SetPort(a_window); /*描画環境をウィンドウに切り替える*/
BeginUpdate(a_window);
/*アップデート開始*/
DrawPicture(picture,
&pict_rect);
EndUpdate(a_window);
/*アップデート終了*/
ReleaseResource(picture);
}
int
MouseEvent(EventRecord *event)
{
WindowPtr
a_window;
Rect dragrect;
switch(FindWindow(event->where,
&a_window)){
case inMenuBar:
return
EventMenuChoice(MenuSelect(event->where));
case
inDrag: /*ドラッグの処理*/
dragrect =
qd.screenBits.bounds;
InsetRect(&dragrect, 5,
5);
DragWindow(a_window, event->where,
&dragrect);
break;
case
inContent:
break;
}
return
NO_EVENT;
}
3・4の解説
ラストを締めくくるにしては、結構少ない気がしないでもないですが、やはりこれができないとメニューがないのと同じくらい違和感があるでしょう。
解説1・アップデートイベント
case
updateEvt:
イベントにアップデートイベントが返された時にここに返ります。
void
EventUpdate(EventRecord
*);
ここの中では、最初に出した絵を、同じ位置に出しなおしています。
BeginUpdate(WindowPtr);
EndUpdate(WindowPtr);
ともに、アップデートで描画をする直前に呼ぶ関数です。これらを使うと、描画が高速化されるらしいです。
以上で、アップデートイベントの処理は終わりです。今回のように同じものをだすのなら、このように簡単なのですが、普通はもっと複雑な内容が書かれていると思うので、ここの処理もややこしくなるでしょう。
解説2・ドラッグ
qd.screenBits.bounds
ウィンドウの外枠をしめしています。これは直接使えないので、移しかえてから使っています。
void
InSetRect(Rect *, short,
short);
Rect変化させたいRectを入れ、さらに変化後の値を返します。short二つは、横、縦に縮める、拡げるのドット指定で、+が縮み、-が拡がります。ここのRectは、後でドラッグの範囲を決めるのに使います。
void
DragWindow(WindowPtr, Point, Rect
*);
ここでウィンドウをドラッグしながら動かします。
WindowPtrは移動するウィンドウを、Pointはマウスボタンが押された時の座標を、Rect
*はドラッグで移動できる範囲です。
これで、ウィンドウの絵が半分消えたり、ウィンドウを動かせなかったりすることはなくなったはずです。
4・終わり
これにて、Macintosh
Toolboxプログラミングの基礎の基礎みたいなものができたと思います。本当に基礎の基礎ですが。そもそも、書いている本人が、まだ使いこなせていない状態なので、これくらいで勘弁しておいてください。
5・参考文献
さて、お待ちかねの参考文献です。
進め!コードウォリア・真紀 俊男 著
CodeWarriorでプログラミングを始めるのなら、この本がお勧めです。というより、僕のスキルはほとんどこれから得ました。貸してくれた島村君に本当に感謝しています。
My
Inside
Macintosh(HTML)・SHIN氏 作
プログラミングをしながら開いていたのが、このHTMLです。ある程度のToolboxの関数が載っているので、これがあれば結構便利になります。『マッキントッシュ素人プログラマの会』のページから見られ、ダウンロードもできます。