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の関数が載っているので、これがあれば結構便利になります。『マッキントッシュ素人プログラマの会』のページから見られ、ダウンロードもできます。