home link download back number special issue

HMDT - Back Number / Feburary, 2001


Feburary, 2001

Feburary 28 - Cocoa Programming Tips 101 追加
keywords: Cocoa programming, NSApplication, NSStringFromClass

Cocoa Programming Tips 101 に以下の項目を追加したっす。

  • NSApplication - 実行中の NSApplication のインスタンスを取得する
  • NSApplication - NSApplicationMain では何をしてるの?
  • Class の名前を表示する

1/M くらいに書いたものをまとめた感じです。

Feburary 28 - Cocoa Programming Tips 101
keywords: Cocoa programming

バックナンバーをつらつら見返していたんですが、Cocoa プログラミングの記事もけっこう書いたな〜、って思ったんですよ。このまま書きっぱなしじゃもったいな、っとも思ったんで、ここはひとつまとめてみることにしました。というわけで、新たな Special Issue、"Cocoa Programming Tips 101" を立ち上げました。あんまりひねりのないネーミングだね。Cocoa プログラミングの際に役立つ、サンプルコードを数多く集める予定です。

Feburary 24 - すべてをなくした後に、残っていたもの

や、ごぶさたしおりました。

おいおい、花柄 iMac も発表されたとゆーのに、なにやってたんだい?うん、あのね。mkino は PowerBook をメインに使っているんだよ。メインもなにも、これ一台しかないんだよ。だけど、最近不調だったんだ。最初のきっかけはモデムがつながんなくなって。そのあと PC カードが認識されたり、されなくなったり。外付けモデムをつないでみたけど、つながんなかったり。mkino の PowerBook は WallStreet なんだけど、かれこれ 3 年近く使っているのかな。HD 入れ替えたり、チップをのせかえたりいろいろしたよ。まだまだ使っていくつもりさ。だから、まぁ、気合いを入れなおすために、再インストールをしようと思ったんだよ。Mac OS X が出てからにしようかな、って思ってたけど、ちょっと早めにやってみたよ。バックアップはもちろんとったさ。CitiDISK を引っ張り出してきて全部コピー。これでオッケー。CD から起動してフォーマット。んで、再インストール。ここまでは早かったよ。一息ついたんで、じゃあ寝るかって、一寝入り。翌日、じゃあコピーしてもどすかぁっ、って思って CitiDISK を入れたら、『このディスクは読めません。フォーマットしますか?』って。

なんじゃぁぁぁぁああ!?

あたまが真っ白になるってこういうことだったのね。他にはどこにもバックアップないよ。バックアップのバックアップって必要なんだ。いろいろ手を尽くしたよ。でも、どーしても取りだせない。Mac ユーザ歴何年だっけ、8 年ぐらいだけど、いまだにこんなことやっちゃうんだ。ぱーだよ。ぜんぶ消えちゃった。

だけど、よく考えたら、残ってたんだ。そう、Web サーバにこのページの文章だけ残ってたんだ。そこに気付いてみると、ショックが消えちゃったんだ。そりゃ、消えてしまったデータには、せこせこいっしょうけんめい集めたり、インストールしたものがあったよ。でも、一番大事なものは、ぜんぶ文書に書き残して Web サイトで公開していたんだ。そうすることによって、プログラミング技術をたしかに自分の身につけていったんだ。だから、このページのバックナンバーを読んで、再構築することができるはずだ。そう考えると、むしろすっきりしてしまった感もあるね。勉強のしっぱなしじゃなくて、文章にして、人に見せれるようにするっていうのは、とても自分のためになる作業だということが身にしみたよ。

そんなわけで、しばらくは復旧作業がメインになると思います。ま、先を急ぐわけでもないし、復習の時間ができたとでも考えますか。

Feburary 18 - Project Sketch-BP (8):NSNotificationCenter の使われ方を調べる
keywords: NSNotificationCenter, addObserver, postNotificationName

さて、前回は IPC の話をしたけど、今度は Sketch の中でどう使われているのか見てみよう。とりあげるのは NSNotification だ。Notification を使うと、あるオブジェクトから他のオブジェクトにあるイベントを投げれる。Sketch で使われている一つの例は、ToolPalleteController(ツールパレットのコントローラ)が、「ツールパレットでツールが選択された」というイベントを Notification として、他のオブジェクトに投げているんだ。

そのために NSNotificationCenter を使う。NSNotificationCenter は Notification を投げるための中継地だ。各タスクはデフォルトで一個 NSNotificationCenter を持つ。そいつは NSNotificationCenter のクラスメソッドで取り出すことができるんだ:

FoundationKit/NSNotification.h
+ (id)defaultCenter;

これでデフォルトの NSNotoficationCenter のインスタンスが得られる。で、使い方だけど、まず「イベントを受けたい」オブジェクトは addObserver:::: を使って NSNotificationCenter に自身と selector を登録する。そのときに「なんのイベントで呼ばれるか」っていうことを name で、「あるオブジェクトが含まれているとき」っていうことを object で指定するんだ:

FoundationKit/NSNotification.h
- (void)addObserver:(id)observer 
                    selector: (SEL)aSelector 
                    name:(NSString *)aName 
                    object:(id)anObject;

observer が自分自身。selector が呼び出されるメソッドだ。で、イベントの名前を指定するときは、name にイベントを指定する。nil のときはすべてのイベントのとき呼び出される。object にオブジェクトを指定すると、Notification の投げ手があるオブジェクトを指定するんだけど、それとマッチしたときだけ呼び出される。nil のときはすべての場合呼び出される、ってわけ。

次。投げる方を見てみよう。postNotificationName:: を使う:

FoundationKit/NSNotification.h
- (void)postNotificationName:(NSString *)notificationName 
                             object:(id)anObject

notificationNameobject を指定してイベントを投げるわけね。

じゃ、Sketch での使われ方を見てみよう。Notification を投げる方は、ToolPaletteController で使われている。パレット上のツールが選択されたときに投げるんだ。まず、ツールの選択が変わると、ツールをあらわしている NSMatrix が selectToolAction: を呼び出す。これは Interface Builder を使って結び付けられているんだ。selectToolAction: メソッドの中を見てみると、postNotificationName:: を呼んでいる:

Sketch/ToolPalleteController.m
- (IBAction)selectToolAction:(id)sender {
    [[NSNotificationCenter defaultCenter]
         postNotificationName:SelectedToolDidChangeNotification
                              object:self];
}

名前として SelectedToolDidChangeNotification を指定してるね。こいつは ToolPalleteController.h に宣言がしてあって、このファイルを import することによって、他のオブジェクトと共有する。実体は ToolPalleteController.m にある:

Sketch/ToolPalleteController.m
NSString *SelectedToolDidChangeNotification = @"SelectedToolDidChange";

次に受け取り側を見てみよう。たとえば DrawWindowController クラスがこの Notification を受け取っている。windowDidLoad メソッドで addObserver しているんだ:

Sketch/DrawWindowController.m
- (void)windowDidLoad {
    ...

    [[NSNotificationCenter defaultCenter] 
            addObserver:self 
                        selector:@selector(selectedToolDidChange:) 
                        name:SelectedToolDidChangeNotification 
                        object: [ToolPaletteController               
                                          sharedToolPaletteController]];
}

selector として selectedToolDidChange メソッドを指定している。name はさっきと同じ SelectedToolDidChangeNotificationobject には ToolPaletteController のインスタンスを指定している。受け取るメソッド selectedToolDidChange はこんなかたちになってる:

Sketch/ToolPalleteController.m
- (void)selectedToolDidChange:(NSNotification *)notification {
    // Just set the correct cursor
    Class theClass = [[ToolPaletteController 
                         sharedToolPaletteController] currentGraphicClass];
    NSCursor *theCursor = nil;
    if (theClass) {
        theCursor = [theClass creationCursor];
    }
    if (!theCursor) {
        theCursor = [NSCursor arrowCursor];
    }
    [[graphicView enclosingScrollView] setDocumentCursor:theCursor];
}

引き数として NSNotification をとる。けどこの値は使われていないね。このメソッドの中では、先日作ったように、Graphic クラスの creationCursor を呼び出して、カーソルをセットしているんだ。

NSNotificationCenter の扱い方を見たけれど、かなり楽だよね。これっていうのも Cocoa がタスクごとにデフォルトの NSNotificationCenter を作ってくれてるからだ。Cocoa 環境はほんと使いやすいっす。

Feburary 18 - Cocoa の IPC
keywords: Cocoa, IPC, NSPort, NSLock, NSNotification

きょうの話題は IPC だ。IPC ?IP を使ったチャット?iMac の PC 版である iPC?うんにゃ、違う。Inter Process Communication(プロセス間通信)ね。

Cocoa は、主に FoundationKit で、IPC のクラスを提供している。このドキュメントを読んでみると、、、おぉっ!?けっこう特殊っつーか、奇妙っつーか、面白い。よし、解説を試みてみよう。あまり熟読していないから、間違いがあるかもしれないけど、そのときは指摘してくれたらうれしいです。

FoundationKit が提供する IPC 関連のクラスは以下のようなものだ:

  • ポート(ローカルのタスク間通信)
    NSMessagePort
  • ソケット(リモートの通信)
    NSSocketPort
  • ロック
    NSLock、NSConditionLock、NSRecursiveLock
  • ノティファイ
    NSNotification

基本となるのはポートだ。ポートってのは、Mach でも基本として使われている概念で、各タスクにある、他からのメッセージを受け取る口だ。メッセージキューのようなもんだ、と考えると分かりやすいかもしんない。Mach の各モジュールも、他のモジュールと通信するときはポートを用いることになってる(建て前は)。FoundationKit が提供する NSPort クラスとそのサブクラス(NSMessagePort、NSMachPort など)は、この Mach レベルでのポートをラップするものみたいだ。たぶん Cocoa の深いところをいじろうと思わない限り、NSPort 系のオブジェクトを直接使うことはないんじゃないかな。

ポートの概念を拡張して、リモートのタスクと通信できるようにしたのが NSSocketPort。これは UNIX 系のソケットを意識してるんでしょう。ただし、UNIX のソケットがファイルデスクリプタを経由した、プリミティブなタスク間通信を意味してるのに対して、NSSocketPort は TCP/IP などを使ったリモート通信に特化しているようだ。意味合いがけっこう違うみたい?

実際にアプリケーションを組む時によく使うことになるのは、ロックとノティファイでしょう。ロックはセマフォの拡張概念で、プリミティブなセマフォ(バイナリセマフォ)よりリッチな API を提供している。たとえば、排他制御したり、条件をつけれたりとかね。ノティファイは、あるイベントが起こったとき、待機しているオブジェクトにそれを伝える仕組み。UNIX 系でいうとシグナルに相当するのか?ただし、シグナルより相当使いやすい。ちなみに、ノティファイって日本語に訳すとき、なんていうのがふつうなんだろ?

ロックとノティファイは、スレッド間通信(オブジェクト間通信)と、タスク間通信(アプリケーション間通信)で、きっちり分けられているようだ。スレッド間のときは NSLock、タスク間のときは NSDistributedLock を使う。実装にどういう差があるのかは、ソースを見ないと分かんないよねー。ただ、Mach は、Linux みたいにいいかげんなスレッド(clone みたいな)じゃなくて、きちんとしたスレッドを実装しているから、けっこう差があるのかもしれない。

こんなところかな。あれ?メッセージキューは?共有メモリは?えっと、たぶん汎用的なメッセージキューはなくて、ポートを使うんじゃないかな。共有メモリの方は、うーんと、分かんねーぞ。NSObject を alloc するとき zone を指定するけど、そこでできるのかな?または POSIX API を使うっていう手もあるね。だけど、いまさら POSIX ってのもなぁ、、、

てな感じで IPC の API を見てみたけど、そのへんの UNIX、つーか Linux と似ているのかな、と思ってたら、かなり違うね。これって Mach の根底にポートを使うべし、っていう思想が流れているからかな。このへんが、ソケットをかなりプリミティブに使っている Linux との違いかな。いろいろ書いたけど、mkino は OS 屋としては、趣味の素人の域を出ないので、間違いがあったら、専門科の方指摘して下さい。<(..)>

Feburary 15, 2001

■Project Sketch-BP (8):カーソルを変える

じゃ、きのう調べた NSCursor を使って、Sketch のカーソルを変えてみよう。まず、各ツールをあらわす Graphics クラスからみてみよう。このクラスには creationCursor っていう、そのツールに対応したカーソルを取得するためのメソッドがある。ルートクラスである Graphics クラスでは、こうなっている:
Sketch/Graphic.m
+ (NSCursor *)creationCursor {
    // By default we use the crosshair cursor
    static NSCursor *crosshairCursor = nil;
    if (!crosshairCursor) {
        NSImage *crosshairImage = [NSImage imageNamed:@"Cross"];
        NSSize imageSize = [crosshairImage size];
        crosshairCursor = [[NSCursor allocWithZone:[self zone]]
               initWithImage:crosshairImage
                             hotSpot:NSMakePoint
                                     ((imageSize.width / 2.0),
                                     (imageSize.height / 2.0))];
    }
    return crosshairCursor;
}

コメントに書いてある通り、デフォルトでは十字カーソルを使うわけだ。カーソルは NSCursor の initWithImage:: を使う、カスタムカーソルを作るやり方で作られている。そのために、まず imageNamed: を使って、Cross というイメージを取ってくる。ホットスポットは、そのイメージの中心に設定する、と。ちょっと話しそれるけど、このホットスポットの設定の仕方はちょっといやだよね。やっぱり、カーソルの画像を用意したとき、同時にホットスポットも設定したいじゃない?だから、カーソルリソースのようなものを用意して、そのリソースに画像とホットスポットの情報の両方を設定しておく、っていうのが正解だと思うけどな。だけどそれをやると、また独自フォーマットになっちゃうわけね、、、

さて、ためしに他のツールのカーソルを変えてみよう。ただ単に creationCursor をオーバーライドしてやればいい。まずテキストツールから。TextArea.m を変更する:

Sketch/TextArea.m
// mkino [start]
+ (NSCursor*)creationCursor {
    return [NSCursor IBeamCursor];
}
// mkino [end]

これだけで OK。これでテキストツールを使うときは、I ビームカーソルになる。つづいて、Scribble クラス。カーソルとして、Sketch プロジェクトに含まれていた Pencil.tiff を使ってみる:

Sketch/Scribble.m (this file is created by mkino)
+ (NSCursor*)creationCursor {
    static NSCursor*	pencilCursor = nil;
    if(!pencilCursor) {
        NSImage*	pencilImage = [NSImage imageNamed:@"Pencil"];
        NSSize		imageSize = [pencilImage size];
        pencilCursor = [[NSCursor allocWithZone:[self zone]]
                            initWithImage:pencilImage
                                          hotSpot:NSMakePoint(2, 2)];
    }
    return pencilCursor;
}

これで、できる。ホットスポットは Pencil.tiff の画像を調べて、設定してみた。これで、ツールに応じてカーソルが変わるよ。

Feburary 14, 2001

■カーソルを設定してみる by NSCursor

スケッチを使っていると、ツールを選択した時に、カーソルが十字形に変わっているのが分かる。でも、テキスト入力ツールでも十字カーソルなんだよな。やっぱり、テキストのときは、I ビームだよね。というわけで、カーソルを変えることを考えてみよう。

カーソルを設定するために使うのは、NSCursor クラスだ。そのまんまで分かりやすいぞ。設定するために、まずインスタンスを使わなきゃいけないんだけど、その方法は大きくわけて二つある。一つ目は、矢印カーソルとか、I ビームとか、すでにシステムに設定してあるカーソルを使うとき。これらに対応したクラスメソッドが用意されている:

AppKit/NSCursor.h
+ (NSCursor *)arrowCursor;
+ (NSCursor *)IBeamCursor;

これを使えば、簡単にインスタンスが取得できる。もう一つは、自分で用意した Image を使う方法。カスタムカーソルを設定できる:

AppKit/NSCursor.h
- (id)initWithImage:(NSImage *)newImage hotSpot:(NSPoint)aPoint;
- (id)initWithImage:(NSImage *)newImage	
                    foregroundColorHint:(NSColor *)fg 
                    backgroundColorHint:(NSColor *)bg 
                    hotSpot:(NSPoint)hotSpot;

この initWithImage:: を使えば OK。newImage に画像をセットして、aPoint にホットスポットを指定するわけだ。NSCursor の Reference によると、NSCurosr で有効なのは 16x16 の画像らしい。大きいカーソルを作ったり、アニメーションさせる方法はドキュメントからは分からない。

カーソルを作った後は、set メソッドを使えば、設定できる:

AppKit/NSCursor.h
- (void)set;

これで、おしまい。じゃ、HMDT の名がすたる!?NSCurosr をいろいろ調べたところ、上にあげた二つのクラスメソッド以外にもクラスメソッドがあることが分かったよ。それらを、ずずぃ、っと書き出してみよう:

NSCursor class method (not written in NSCursor.h)
+ (NSCursor*) _bottomLeftResizeCursor;
+ (NSCursor*) _bottomRightResizeCursor;
+ (NSCursor*) _copyDragCursor;
+ (NSCursor*) _crosshairCursor;
+ (NSCursor*) _genericDragCursor;
+ (NSCursor*) _handCursor;
+ (NSCursor*) _horizontalResizeCursor;
+ (NSCursor*) _linkDragCursor;
+ (NSCursor*) _moveCursor;
+ (NSCursor*) _topLeftResizeCursor;
+ (NSCursor*) _topRightResizeCursor;
+ (NSCursor*) _verticalResizeCursor;
+ (NSCursor*) _waitCursor;

こんだけあったよ。まず resize 系のカーソルがあることが分かる。Windows や X Window みたいに、ウインドゥのどこをつかんでもサイズを変えれるようにするときのカーソルだね。これって、たぶん、Mac OS X Server のときの名残りだと思う。X Server はどこつかんでも変えれたからね。あと、waitCursor っていうのは、あの 7 色ビーチボールのアイコンだ。ただし、どうやって回転させるかは不明。ちなみに、これらのメソッドは、ドキュメントに記載されていないものなので、実際の使用に関しましては、当サイトは一切の責任を持ちません。resize 系は、正規版では削除されてくるかもね。

Feburary 12, 2001

■Project Sketch-BP (7):誰がツールパレットをつくるの?NSApplication の delegate

誰がツールパレットをつくるの?どうやって?、、、というわけで、今回の話題は ToolPaletteController の初期化と表示される時のシーケンスだ。

まず、ツールパレットには、クラスメソッド sharedToolPaletteController がある。このメソッドを使うと、だれでも ToolPaletteController のインスタンスを得られるんだ:

Sketch/ToolPaletteController.m
+ (id)sharedToolPaletteController {
    static ToolPaletteController *sharedToolPaletteController = nil;

    if (!sharedToolPaletteController) {
        sharedToolPaletteController = [[ToolPaletteController allocWithZone:NULL] init];
    }

    return sharedToolPaletteController;
}

このメソッドでは、static 変数 sharedToolPaletteController を使ってる。一番最初にこのメソッドが呼ばれたとき、すなわち sharedToolPaletteController が nil のときは、ToolPaletteController のインスタンスが、allocWithZone を使って作られて、init も呼ばれる。init はこんな感じ:

Sketch/ToolPaletteController.m
- (id)init {
    self = [self initWithWindowNibName:@"ToolPalette"];
    if (self) {
        [self setWindowFrameAutosaveName:@"ToolPalette"];
    }
    return self;
}

NSWindowController のメソッド initWithWindowNibName: を使って、ウインドゥが初期化されるわけだ。この後で、先日見た windowDidLoad が呼ばれるわけね。

じゃ、最初に sharedToolPaletteController を呼のは誰?それはこのアプリケーションの delegate、DrawAppDelegate クラスだ。nib ファイル、Draw2.nib を見ると、DrawAppDelegate がインスタンス化されていて、NSApplication の delegate として設定されている:

ほんじゃ、っていうんで DrawAppDelegate のソースを見てみると、applicationDidFinishLaunching: がオーバーライドされているんだ:

Sketch/DrawAppDelegate.m
- (void)applicationDidFinishLaunching:(NSNotification *)notification {
    [self showToolPaletteAction:self];
}

このメソッドは、名前から分かるよね、アプリケーションが起動したあとに呼ばれるんだ。で、showToolPaletteAction: っていうメソッドを呼んでいる:

Sketch/DrawAppDelegate.m
- (IBAction)showToolPaletteAction:(id)sender {
    [[ToolPaletteController sharedToolPaletteController] showWindow:sender];
}

showToolPaletteAction: で、たどりついたよ!、ToolPaletteController の sharedToolPaletteController を呼んでいるんだ。そして得られたインスタンスの showWindow: を呼んでウインドゥを表示させている。

じゃ、まとめると、流れはこうなっているんだ。

   NSApplicationMain
-> DrawAppDelegate's - applicationDidFinishLaunching:
-> DrawAppDelegate's - showToolPaletteAction:
-> ToolPaletteController's + sharedToolPaletteController
-> ToolPaletteController's - init
-> NSWindowController's - initWindowNibName:
-> ToolPaletteController's - windowDidLoad
-> NSPanel's - showWindow

applicationDidFinishLaunching から initWindowNibName までは同一スレッドのはずだ。おーっし、これでアプリケーションの起動からウインドゥの表示までが、だいぶ理解できたかな?

Feburary 12, 2001

■Project Sketch-BP (6):ToolPaletteController を変更する

このプロジェクトの目的は、Sketch の機能を 1 つ増やすこと。そのためにツールパレットにアイコンを追加したよね。今回は、それに伴う ToolPaletteController の変更の話だ。

まず、この前、Scribble っていうクラスを作ったよね。そいつを import してやる:

Sketch/ToolPaletteController.m
#import "ToolPaletteController.h"
#import "Graphic.h"
#import "Rectangle.h"
#import "Circle.h"
#import "Line.h"
#import "TextArea.h"
// mkino [start]
#import "Scribble.h"
// mkino [end]

あと、ToolPalletController には enum による、ツールパレットの各ボタンに相当する定数宣言があるんだ。これにも追加する:

Sketch/ToolPaletteController.m
enum {
    ArrowToolRow = 0,
    RectToolRow,
    CircleToolRow,
    LineToolRow,
    TextToolRow,
// mkino [start]
    ScribbleToolRow,
// mkino [end]
};

もう一ケ所。ToolPaletteController には、現在選択されているツールを得るためのメソッド currentGraphicClass がある。これを変更してやる:

Sketch/ToolPaletteController.m
Sketch/ToolPaletteController.m
- (Class)currentGraphicClass {
    int row = [toolButtons selectedRow];
    Class theClass = nil;
    if (row == RectToolRow) {
        theClass = [Rectangle class];
    } else if (row == CircleToolRow) {
        theClass = [Circle class];
    } else if (row == LineToolRow) {
        theClass = [Line class];
    } else if (row == TextToolRow) {
        theClass = [TextArea class];
    }
// mkino [start]
    else if (row == ScribbleToolRow) {
        theClass = [Scribble class];
    }
// mkino [end]
    return theClass;
}

このメソッドは、まずボタンの Matrix から現在選択されている row を取り出す。で、その値から類推して、関連するクラスを返すわけだ。row == ScribbleToolRow のとき、この定数はさっき追加したよな?、Scribble クラスを返すってわけだ。

これで、ToolPaletteController クラスの変更はおしまい。個人的な感想だけど、ほんとは、currentGraphicClass メソッドのやり方はベストではない。この方法だと、dynamic にツールが増える場合、対応できないよね?なんでかっていうと、ツールが増える度にソースを書き換えなきゃいけないから。たとえば、ツールのボタン(NSButtonCell)に、各ツールのクラスに対応する情報(たとえば名前)をセットし、その値から対応するクラスを動的に推定する、っていう機構がほんとは必要だ。その方が Objective-C っぽいよね。

Feburary 11, 2001

■Project Sketch-BP (5):NSWindowController を継承した ToolPaletteController、ソースコード編

じゃ、前回の予告通り、ToolPaletteController のソースを見よう。まず、ToolPaletteController は、前回も書いた通り、NSWindowController を継承したクラスだ:
Sketch/ToolPaletteController.h
@interface ToolPaletteController : NSWindowController

この ToolPaletteController クラスがオーバーライドしている NSWindowController のメソッドは、windowDidLoad なんだ:

Sketch/ToolPaletteController.m
- (void)windowDidLoad {
    NSArray *cells = [toolButtons cells];
    unsigned i, c = [cells count];
    
    [super windowDidLoad];

    for (i=0; i<c; i++) {
        [[cells objectAtIndex:i] setRefusesFirstResponder:YES];
    }
    [(NSPanel *)[self window] setFloatingPanel:YES];
    [(NSPanel *)[self window] setBecomesKeyOnlyIfNeeded:YES];

    [toolButtons setIntercellSpacing:NSMakeSize(0.0, 0.0)];
    [toolButtons sizeToFit];
    [[self window] setContentSize:[toolButtons frame].size];
    [toolButtons setFrameOrigin:NSMakePoint(0.0, 0.0)];
}

このメソッドは、システム(っつーか NSApplication か?)によるウインドゥのロードが終わった後呼び出されるんだ。ウインドゥ関連のリソースが揃った後、必要な初期化を書くことができる。ToolPaletteController では、ふむふむ、ウインドゥをフローティングにして、サイズを調節して、ボタンの位置も調節するって感じですか。これらは、.nib がロードされた後にやる必要があるから、windowDidLoad でやるんだね。ちなみに、ロードされる前に行いたかったら windowWillLoad をオーバーライドすればよし:

Sketch/ToolPaletteController.h
- (void)windowWillLoad;

Feburary 7, 2001

■Project Sketch-PB (3):Scribble クラスをつくる

ツールパレットをいぢった次は、ベジェ曲線のハンドリングを担当するクラスをつくるぜ。そのために、Sketch の構造を少し理解しておく。

Sketch の Model Classes の下に、各種ツールパレットに対応した図形をハンドリングするクラスが入ってるんだ。まず、いちばんの親玉となるクラスが Graphic クラス。それを継承する形で、Rectangle、Circle、Line、TextArea、Image があるんだ。ということは、同じように、Graphic を継承したクラス Scribble を作ればいいというわけだ:

Sketch/Scribble.h (this file is created by mkino)
#import	<AppKit/AppKit.h>
#import "Graphic.h"

@interface Scribble : Graphic
{
}
@end
Sketch/Scribble.m (this file is created by mkino)
#import "Scribble.h"

@implementation Scribble
- (id)init {
    self = [super init];
    if(self) {
        // Specified initialize
    }
    return self;
}
@end

これで OK!とりあえず、いまのところはからっぽにしておく。基本的な動作は、親クラスである Graphic クラスがやってくれるので、必要に応じてオーバーライドすればいい。ついでなので、Graphic クラスの書き換えが必要そうなところを見ておこう。Graphic クラスには、bezierPath というメソッドがある:

Sketch/Graphic.h
- (NSBezierPath *)bezierPath;

基本的に Graphic クラスは、扱う図形を NSBezierPath の形で持ってる。それを取り出すのがこのメソッドだ。ということは、Scribble クラスでは、内部でどうにかして適当な NSBezierPath を作って、このメソッドで取りだせるようにすればいい。ちなみに、実際に描画するのは、Graphic の drawnView:: メソッド:

Sketch/Graphic.m
- (void)drawInView:(GraphicView *)view isSelected:(BOOL)flag {
    NSBezierPath *path = [self bezierPath];
    if (path) {
        if ([self drawsFill]) {
            [[self fillColor] set];
            [path fill];
        }
        if ([self drawsStroke]) {
            [[self strokeColor] set];
            [path stroke];
        }
    }
    if (flag) {
        [self drawHandlesInView:view];
    }
}

あと、もうひとつ肝となるメソッドは、createWithEvent:: メソッド。これはだねぇ、あるツールを選択して、ウインドゥの上でクリックかドラッグするとするでしょ。そうすると、このメソッドが呼び出されるんだ。ユーザのマウス操作によって、図形が実際に作られるのはこのメソッドの中でだ:

Sketch/Graphic.h
- (BOOL)createWithEvent:(NSEvent *)theEvent inView:(GraphicView *)view;

このメソッドをオーバーライドするのが、中心となるわけだな。

Feburary 6, 2001

■Project Sketch-BP (2):ツールパレットの追加

まずは、見た目からだ!というわけで、ツールパレットを追加する。現在のところ Sketch のツールパレットには 5 つのツール(Arrow、Rectangle、Circle、Line、TextArea)がならんでいるけど、これにベジェ曲線のための 6 つ目を追加するのが目標だ。

そのためには、ToolPalette.nib をいぢる。Project Builder を開いて、Interfaces の下にある ToolPalette.nib をダブルクリックして、Interface Builder で開くんだ。ToolPalette.nib には、パレットが入ってる。このウインドゥには、NSMatrix が張り付けてあって、その中に cell としてボタンが入ってるんだ。

NSMatrix は、名前のとおり、行列型に NSCell を内部に配置できるクラスだ。今回は、1 行 5 列に NSButtonCell を配置している。さらに、NSMatrix は内部の cell をいろんな形で操作できる。今回は NSRadioModeMatrix というタイプらしい。これを使うと、ボタンの排他的選択が実現できる。つまり、あるツールを選択すると、他のツールの選択が外れる、ってやつね。まさにツールパレットにうってつけのモードというわけだ。

さて、こいつにもう 1 個ボタンを追加してやろう。まず、インスペクタウインドゥを表示させておいてと。ボタンのあたりを 1 回クリックする。インスペクタが NSMatrix Inspector になっていることを確認しよう。その上で Row/Col の値を変える。"R: 5" から "R: 6" へ変えよう。これでボタンが 1 個増えるはずだ。

ボタンが増えたら、次はそのボタンの属性を変える。NSMatrix をダブルクリックすることによって、内部の NSButtonCell を選択できるはずだ。そしたら、一番下の今追加されたやつを選択して、属性を変える。ボタンのアイコンは、めんどうなんで、Sketch に残っているやつを使ってしまおう。"Scribble" アイコンを割り当てる。あと、Type を PushOn/PushOff にして、Enabled はチェックして、でも Selected はチェックしない。最後に Tag は 5 に設定する。こんな感じになるはずだ:

これで、Test Interface を選ぶと、こうなるはずだ。ちゃんとボタンが排他的になるかどうか確かめよう:

とりあえずここまで。IB の操作を文章で説明すると、疲れるねー。

Feburary 5, 2001

■NSBezierPath (6):パスを閉じる

いままでは、直線だったり、曲線だったりした、線だったけど、今度は線を閉じることを考える。四角とか、円とかは閉じられた BezierPath として扱うことができるんだ。パスを閉じるには、closePath を使う:
AppKit/NSBezierPath.h
- (void)closePath;

これを呼ぶと、カレント・ポイントから、最初の点までの線が加えられて、線が閉じるんだ。ということは、多角形を書く手順は、

  • moveToPoint: を呼ぶ
  • lineToPoint: を必要なだけ呼ぶ
  • closePath を呼ぶ

という風に考えられるんだな。これを使って多角形をつくる関数を作ってみた:

BezierSample.m
- (NSBezierPath*)makePolygonPath:(NSBezierPath*)bezierPath 
                polygonNumber:(int)polyNum
                origin:(NSPoint)origin 
                radius:(float)radius
{
    int	i;
    for(i = 0; i < polyNum; i++) {
        NSPoint	tmpPoint = origin;
        tmpPoint.x += 
            radius * cos(M_PI_2 + 2 * M_PI * (i / (float)polyNum));
        tmpPoint.y += 
            radius * sin(M_PI_2 + 2 * M_PI * (i / (float)polyNum));
        
        if(i == 0) {
            [bezierPath moveToPoint:tmpPoint];
        }
        else {
            [bezierPath lineToPoint:tmpPoint];
        }
    }
    [bezierPath closePath];
    
    return bezierPath;
}

この関数、makePolygonPath:::: は bezierPath を渡して、多角形をつくる関数だ。他の引き数として、辺の数(polygonNumber)、中心の座標(origin)、中心からの半系(radius)を設定する。関数の中では、sin、cos を使って、各頂点の座標を求めて、moveToPoint:、lineToPoint: を呼び出す。最後に closePath を呼び出して終わりだ。

この関数を呼び出す側は、こんな感じで呼び出すんだ:

BezierSample.m
    NSBezierPath*	bezier = [NSBezierPath bezierPath];
    NSPoint			origin;
    float			radius = 25.0f;
    int				i;
    
    for(i = 3; i < 12; i++) {
        origin.x = 50 * (i-2);
        origin.y = 50;
        [self makePolygonPath:bezier 
                        polygonNumber:i 
                        origin:origin 
                        radius:radius];
         
        [[NSColor blackColor] set];
        [bezier stroke];
        [[NSColor whiteColor] set];
        [bezier fill];
    }

これで、3 角形から 12 角形までが描かれる。注意をひくのは bezier に対して stroke と、もうひとつ fill も使えることかな。線が閉じているので、fill が心置きなく使えるわけだ。実行画面はこんな感じ:

Feburary 4, 2001

■NSBezierPath (5):曲線を描く

そろそろパターンが見えてきたろ?きょうは曲線の描き方だ。とはいっても、直線も曲線の一つであるわけだよね。ただ単に曲がっていない曲線というだけ。

曲線を描くには curveToPoint:controlPoint1:controlPoint2 を使う:

AppKit/NSBezierPath.h
- (void)curveToPoint:(NSPoint)aPoint 
                controlPoint1:(NSPoint)controlPoint1 
                controlPoint2:(NSPoint)controlPoint2

1 つの曲線(または長い曲線のある部分)は、4 つの点で決定されるんだ。まずカレント・ポイント。これが始点になる。次に上のメソッドに出てくる aPoint。これは終点だ。そして曲線の曲がり具合を指示する controlPoint1 と controlPoint2。この 4 つの点と曲線の曲がり具合の関係は、下の図のようになってるんだ:

ドロー系のグラフィックソフトで絵を描いたことがある人なら、分かるよね?本当なら、ここで Bezier 曲線の数式的定義を持ち出して議論するんだけど、めんどくさいからやんない。その筋の本をあたってくれ。

サンプルプログラムとして、簡単に書いてみた:

Sample.m
    NSBezierPath*	bezier = [NSBezierPath bezierPath];
    NSPoint	start = { 50, 100 };
    NSPoint	end = { 200, 100 };
    NSPoint	ctrl1 = start;
    NSPoint ctrl2 = end;
    ctrl1.x += 50;	ctrl1.y += 100;
    ctrl2.x -= 50;	ctrl2.y += 100;
    
    [bezier moveToPoint:start];
    [bezier curveToPoint:end controlPoint1:ctrl1 controlPoint2:ctrl2];  
    [bezier stroke];

実行すると、こんな感じに曲線が引かれる:

以上。Bye!

Feburary 3, 2001

■NSBezierPath (3):Zig-Zag 線を描く

さて、だんだん調子が上がってきたかな?今回はジグザグな線を描いてみよう。ジグザグな線とはどういうことか?Illustrator 的にいうと、途中にアンカーポイントを父君打線、凄い誤変換だ、含んだ線ということだな。

まず、空の BezierPath をつくる。そのためには、クラスメソッド bezier を使うんだ:

+ (NSBezierPath *)bezierPath

これで作られるのが、何も含んでいない線ね。さて、NSBezierPath にはカレント・ポイントというものがあるんだ。現在の点の位置ということ。これはメソッド currentPoint で得ることができる:

- (NSPoint)currentPoint

ちなみに、空のパスに currentPoint を適用すると例外が発生する。で、まず最初にカレント・ポイントを設定しないといけない。それには moveToPoint: を使うんだ:

- (void)moveToPoint:(NSPoint)aPoint

カレント・ポイントが設定されたら、そこから指定した点まで線を引くことができる。それには lintToPoint: を使う:

- (void)lineToPoint:(NSPoint)aPoint

あとは、lineToPoint: をくり返し呼ぶことによって線が引けるんだ。そして最後に storoke を呼ぶ:

- (void)stroke

これで線が実際に描かれる。じゃ、サンプルコード:

        NSBezierPath*	bezier = [NSBezierPath bezierPath];
        [bezier moveToPoint:NSMakePoint(00, 00)];
        [bezier lineToPoint:NSMakePoint(50, 40)];
        [bezier lineToPoint:NSMakePoint(50, 20)];
        [bezier lineToPoint:NSMakePoint(100, 60)];
        [bezier lineToPoint:NSMakePoint(100, 40)];
        [bezier lineToPoint:NSMakePoint(150, 80)];
        [bezier lineToPoint:NSMakePoint(150, 60)];
        [bezier stroke];

実行結果がこれ:

すこし、おもしろくなってきたかな?

Feburary 2, 2001

■NSBezierPath ちょっと番外:NSColor の話

NSBezierPath の話を続ける前に、ちょっと寄り道して NSColor の話だ。なに?このサイトは寄り道ばっかりだって?ごもっともです。

前回、四角を描いたけど、色を塗ってみたいよね。とりあえず、細かいことは抜きにして、てっとり早く色を付けてみよう。NSColor を使う。まず、インスタンスを取得しないといけないけど、それには便利なクラスメソッドがあるんだ。黒とか青とか決まった色なら一発でとれる:

+ (NSColor *)blackColor

このクラスメソッドを呼び出すと、黒色をあらわす NSColor のインスタンスがとれるんだ。他にも bluColor とか brownColor とかが同様にある。

インスタンスが取れたら色の設定だ。それには set メソッドを使う:

- (void)set

これで、色がセットできたぜ!まとめると、色付きの四角を描くのはこんな感じになる:

    [[NSColor blackColor] set];
    [NSBezierPath fillRect:NSMakeRect(0, 0, 20, 20)];

NSColor を set したあとは、NSBezierPath はその色で描かれることになる。これには NSGraphicsContext がかかわってくるんだけど、これもまた後日。サンプルとして、クラスメソッドで用意されている色を全部描いてみた:

では、また。

Feburary 2, 2001

■NSBezierPath (2):四角を描く

NSBezierPath クラスの次のターゲットは、四角だ。四角を描いてみよー。

なんとなく、想像ついているかも知れないけど、前回と同じように、四角を描くクラスメソッドが用意されている。その名もそのまま storkeRect: だ:

+ (void)strokeRect:(NSRect)aRect

ある NSRect を指定してやればいいわけだ。サンプルコードはこんな感じ:

    NSRect	rect = { 0, 0, 100, 100 };
    [NSBezierPath strokeRect:rect];

簡単だね。なに?もっと簡単にしたい?よーし、一行で書いてみよう:

    [NSBezierPath strokeRect:NSMakeRect(0, 0, 100, 100)];

関数 NSMakeRect を使ってみた。ちなみに、構造体 NSRect や、この関数 NSMakeRect はどこに宣言があるのかなー、と思って探してみたら、Foundation/NSGeometry.h にあった。これによると、NSMakeRect は、

FOUNDATION_STATIC_INLINE NSRect NSMakeRect(
                float x, float y, float w, float h) {
    NSRect r;
    r.origin.x = x;
    r.origin.y = y;
    r.size.width = w;
    r.size.height = h;
    return r;
}

という関数らしい。ま、予想通りだよね。inline 関数なので、関数呼び出しのオーバーヘッドもないわけだ。

次は、四角を塗りつぶしてみる。この場合は fillRect: を使う:

+ (void)fillRect:(NSRect)aRect

これで、四角の内部を塗りつぶすことができる。何色で?色の話はまた後日だ。じゃっ!

Feburary 1, 2001

■復活〜!および Cocoa はやっぱり!

や!ひさしぶりに更新だ。おいおい、間があいたじゃねぇかっ、て?うんうん、記事自体はできていたんだけど、PowerBook が不調におちいって、モデムがつながらなくなっちゃったんだ。システムが悪いのか、ハードが悪いのか不明。ただ、どうもモデムからの音声を入力できなくなってしまったような気がする。サウンド・コントロールパネルで、入力源を内蔵モデムにしても、どういうわけか、なしに戻っちゃうんだ。しょーがないので、会社で使っている VAIO を使って更新してみた。<裏切り者だ。考えられる原因としては、PowerBook G4 の購入を決めたということが考えられる。旧マシンが嫉妬するという現象は本当だったんだ!

で、下の記事で NSBezierPath について書いて見たんだけど、サイトあろさんに、先を越されちゃったよー。ちょっとくやしい。ちなみに、サイトあろさんは、きれいで分かりやすい図を大量に備えており、きちんと順序だてて説明しておられるので、うちみたいに、てきとーといい加減をモットーにしているサイトとはわけが違うんだ。Cocoa をきちんと勉強するつもりの方は、ぜひあろさんの Cocoa はやっぱり!を参考にしましょう。

Feburary 1, 2001

■NSBezierPath (1):線を書く

ちぁっす!あいかわらず、脈絡も前振りもないこのページ。きょうから NSBezierPath 特集だ!

いちおうイントロダクションから。みんな Bezier って知ってるよね?なに知らない?でも、ベジェ曲線なら知ってるだろう。Illustrator とかで、ぐにょぐにょした線を書く時に使われる、あの用語ね。ちなみに、mkino のおぼろげな記憶によると、Bezier っていうのは、フランスの自動車メーカーで働いていた Bezier さんのことで、彼が自動車をデザインする時に、考え出したのが、あの曲線らしい。

で、Cocoa には、Bezier 曲線をハンドリングするための、NSBezierPath っていうクラスがある。Cocoa 上での 2 次元グラフィックス描画は、このクラスが中心になるようだな。というわけで、NSBezierPath の探索だ!まずは、線を書いてみよう。

線を書くにもいくつか方法があるみたいだ。だけど、とーぜん、簡単なものからいく。クラスメソッド strokeLineFromPoint:toPoint を使おう。このメソッドは名前のとおり、あるポイントから、あるポイントまで線を引くものだ:

+ (void)strokeLineFromPoint:(NSPoint)point1 toPoint:(NSPoint)point2

クラスメソッドなんで、前準備もいらず、いきなり書けてしまう。このメソッドを、View が focus のあたっている状態で呼び出せばいいんだ。一番簡単なのは、drawRect の中で呼んじゃえばいいんだな。じゃ、ソースだ:

#import "BezierView.h"

@implementation BezierView
- (void)drawRect:(NSRect)frameRect
{
    // storkeLineFromPoint:toPoint
    NSPoint	point1 = { 0, 0 };
    NSPoint	point2 = { 100, 100 };
    [NSBezierPath strokeLineFromPoint:point1 toPoint:point2];
}
@end

おー、簡単だ。ま、線を引くだけだからな。で、実行画面がこれ:

じゃ、また次回。チャオ!


Home | Link | Download | Back Number | Speciall Issue

mailto: mkino@xd5.so-net.ne.jp

HMDT