home link download back number special issue

HMDT - Back Number / January, 2001


January, 2001

January 29, 2001

■先日の修正

この前、Developer Tool に付属するドキュメントには、NSString の Attribute に関する記述がない、って書いちゃったけど、実はありました。Application Kit API Reference の一番下の方に、Other Reference っていう項目があるんだ。そこに、NSAttributedString Class Cluster っていうページがあって、それが求めるドキュメントでした。

だってさぁ、こんな下の方にあってさぁ、他の NSString 関係のドキュメントと全然関係ない位置にあるんだもん。言い訳でした。ごめんなさい。


January 26, 2001

■文字に属性を設定する

こないだのちょいネタの続きなんだけどさ、String を書くときに、属性を追加してみたよ。

属性を設定するには NSMutableAttributedString を使う。NSString のサブクラスである NSAttributedString のサブクラスだ。サブサブだ。こいつの addAtribute:value:range ってメソッドを使う:

- (void)addAttribute:(NSString *)name value:(id)value range:(NSRange)aRange

なるほど、なるほど。じゃ、設定してみるか。えーっと value に設定するのは、、、うん?ドキュメントのどこにも書いてないぞ!?どーすんだ?

しょーがないので、Web を探索。すぐ見つかる。NSAttributedString のドキュメントが置いてあった。これは、Developer Tool についてくるのとは違うらしい。どっちかってーと、こっちの方が古いような。Developer Tool のドキュメントは、不備が多いのぅ。これじゃ、デベロッパが Cocoa に移行したくても、二の足を踏むわけだ。ま、とりあえずこれによると、次のような属性を設定できるらしい。

NSFontAttributeName
フォント属性
NSForegroundColorAttributeName
文字の色
NSBackgroundColorAttributeName
文字の背景色
NSUnderlineStyleAttributeName
下線
NSSuperscriptAttributeName
上付き
NSBaselineOffsetAttributeName
ベースラインからのオフセット(正負 OK)
NSKernAttributeName
カーニング(正負 OK)
NSLigatureAttributeName
リガチャをするか否か
NSParagraphStyleAttributeName
パラグラフスタイル
NSAttachmentAttributeName
テキストアタッチメント(よくわかんない)

じゃあ、使ってみるか!というわけで、一気に使ってみたのが、下の画像だ:

ひとつ注目すべきは、最後の 'fine!' かな。ここにはリガチャが設定してあるので、f と i がくっついているのが分かるだろう。ソースコードはこうなっている。解説するまでもないと思う。てきとーに読んでも分かると思うぜ!

#import "StringDrawView.h"

@implementation StringDrawView
- (void)drawRect:(NSRect)frameRect
{
    NSMutableAttributedString*	str = [[NSMutableAttributedString alloc]
         initWithString:@"How's about your Mac? It's fine!"];
    NSPoint		point = { 10, 10 };
    
    // Set font attribute
    
    [str addAttribute:NSFontAttributeName
        value:[NSFont fontWithName:@"Chicago" size:36.0] 
        range:NSMakeRange(0,5)];
    [str addAttribute:NSFontAttributeName 
        value:[NSFont fontWithName:@"Helvetica" size:24.0] 
        range:NSMakeRange(6,5)];
    [str addAttribute:NSFontAttributeName 
        value:[NSFont fontWithName:@"Sand" size:12.0] 
        range:NSMakeRange(12,4)];
    [str addAttribute:NSFontAttributeName 
        value:[NSFont fontWithName:@"Monaco" size:48.0] 
        range:NSMakeRange(17,4)];
    [str addAttribute:NSFontAttributeName 
        value:[NSFont fontWithName:@"Apple Chancery" size:36.0] 
        range:NSMakeRange(22,4)];
    [str addAttribute:NSFontAttributeName 
        value:[NSFont fontWithName:@"Times" size:48.0] 
        range:NSMakeRange(27,4)];
    
    // Set underline style attribute
    
    [str addAttribute:NSUnderlineStyleAttributeName 
        value:[NSNumber numberWithFloat:1.0] 
        range:NSMakeRange(0,2)];
    [str addAttribute:NSUnderlineStyleAttributeName 
        value:[NSNumber numberWithFloat:2.0] 
        range:NSMakeRange(2,3)];
    
    // Set foreground color attribute
    
    [str addAttribute:NSForegroundColorAttributeName 
        value:[NSColor redColor] 
        range:NSMakeRange(6,5)];
    [str addAttribute:NSForegroundColorAttributeName 
        value:[NSColor colorWithCalibratedWhite:0.0 alpha:0.3] 
        range:NSMakeRange(17,4)];
    
    // Set background color attribute
    
    [str addAttribute:NSBackgroundColorAttributeName 
        value:[NSColor blueColor] 
        range:NSMakeRange(22,4)];
    
    // Set ligature attribute
    
    [str addAttribute:NSLigatureAttributeName 
        value:[NSNumber numberWithInt:1] 
        range:NSMakeRange(27,4)];
    
    // Set kern attribute
    
    [str addAttribute:NSKernAttributeName 
        value:[NSNumber numberWithFloat:10.0] 
        range:NSMakeRange(22,4)];
    
    // Set superscript attribute
    
    [str addAttribute:NSSuperscriptAttributeName 
        value:[NSNumber numberWithInt:1] 
        range:NSMakeRange(17,4)];
    
    // Set baseline attribute
    
    [str addAttribute:NSBaselineOffsetAttributeName 
        value:[NSNumber numberWithFloat:-10.0] 
        range:NSMakeRange(12,4)];
    
    [str drawAtPoint:point];
}
@end

Cocoa でのアンチエイリアシングは、なんかすげーきれいだ。アンチエイリアシングにも、いろいろな技術差があるわけね。しかし、きれいな文字で書かれるだけで、どうしてこうもやる気が起きてくるんだろう。


January 25, 2001

■NSCell の中身をかく

話が錯綜してごめんな、NSCell の話だ。いっけん脈絡なく見えるけど、実は壮大なこ構想に乗っとって、話題が選ばれている、ということは別にないんだ(笑)。まぁ、適当なアプリを作ろうと思った時に、ぶちあたった問題とその解決策を、かたっぱしから書き留めておいてるだけだね。

で、NSCell の話だけど、NSCell っていうのは、かなりあちこちで使われているクラスだ。たとえば、ボタンを考えてみる。ボタンは NSButton と、NSButtonCell っていうクラスからできているんだ。このとき、NSButtonCell の役割っていうのは、実際にボタンを描画することなんだ。ということは、ボタンがどう描かれるかを変えたい時は、NSButtonCell の Subclass を作ればいい、ってことらしい。

実際にはどうすればいいのか?NSCell のうちの drawInteriorWithFrame:inView: か drawWithFrame:inView: (または両方)をオーバーライドすればいい:

- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView

これらのメソッドは、引き数として cellFrame が渡されるんだ。だから、cellFrame の範囲内で、好きなものを描けばいい。例として、String を、少し左にずらして、セルの中に書くメソッドを実装してみよう:

- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    NSAttributedString*	attString = nil;
    NSRect	stringRect = cellFrame;

    attString = [self attributedStringValue];
    stringRect.origin.x += 20;
    [attString drawInRect:stringRect];
}

書くべき文字列は、すでに Cell にセットしてあるものとしよう。そうすると、attributedStringValue で取り出すことができる。それを drawInRect で、書くわけだ。

これで、また一歩野望に近付いた(なんの?)。


January 25, 2001

■ちょいネタ:NSString を画面にかく

ある文字列があったとしよう。それを画面に書くにはどうしたらいいか?

AppKit の Reference の NSString Additions を見てくれ。そこにある、drawAtPoint:withAttributes:drawInRect:withAttributes: で書くことができるんだ:

- (void)drawAtPoint:(NSPoint)aPoint withAttributes:(NSDictionary *)attributes
- (void)drawInRect:(NSRect)aRect withAttributes:(NSDictionary *)attributes

NSView にフォーカスがあたっている状態で、上のメソッドを呼び出すと、文字を書くことができるんだ。でも、これって、NSString クラスに入れるべきものなのか?なんか、変な感じ。


January 24, 2001

■半透明ウィンドゥ

Mac OS X についてくる、Clock っていうアプリケーションがあるよね。あの、半透明で表示されるアナログ時計。あれみたいな、ウィンドゥの半透明表示をやってみたいなー、って思ったんだ。

Developer Kit についてくるドキュメントを呼んだけど、記述はなし。じゃあ、っていうんで、Google で検索かけたら、あったあったありました。うーん、Google すばらしい(Google の頼もしさについてはこちらの記事も参照)。

Help With Transparent Windows
http://www.omnigroup.com/mailman/archive/macosx-dev/2000-October/004625.html

これによると、NSWindow を継承させて、

@interface TransparentWindow : NSWindow
- (float)_transparency;
@end

@implementation TransparentWindow
- (float)_transparency { return 0.0; }
@end

という _transparency というメソッドをオーバーライドすればいいらしい。return する値は 0.0 から 1.0 の間だ。よし、さっそくやってみよう:

おぉっ、ほんとにできた!0.0 だと何も表示されなくなってしまうので、上にあげた画像は 0.5 の状態だ。

ただし、上記の文章中にもあるように、いささかトリッキーな手法だ。正規のドキュメントには記載されてないしね。将来的に使えるかどうかは分からないけど、いまのところ、これしか方法が見つからないようだ。


January 24, 2001

■フェード・イン、フェード・アウトアニメーション (2):setNeedsDisplay

フェード・イン、フェード・アウトアニメーションの続きだ。先日は、NSTimer から周期的にメソッドを呼び出すとこまでいったので、今日は呼び出された後からだ。

呼び出された後は、View の再描画をしたい。くり返しくり返し、少しづつ変えて書くのが、アニメーションの基本だからな。で、どうするかというと、setNeedsDisplay というメソッドを使う:

    [self setNeedsDisplay:YES];

これを YES とともに呼んでやると、その View は再描画をするんだ。

これで、必要な機能はそろったね。じゃ、コードを:

#import "DissolveIconView.h"

@implementation DissolveIconView
- (void)awakeFromNib
{
    NSSize	iconSize = { 128, 128 };
    
    iconImage = [[NSWorkspace sharedWorkspace]
        iconForFile:@"/Applications/Sherlock.app"];
    [iconImage retain];
    [iconImage setSize:iconSize];
    
    fract = 0.0f;
    fadeIn = YES;
    
    [NSTimer scheduledTimerWithTimeInterval:0.05
                  target:self
                  selector:@selector(firedByTimer)
                  userInfo:nil
                  repeats:YES];
}

- (void)firedByTimer
{
    [self setNeedsDisplay:YES];
}

- (void)drawRect:(NSRect)aRect
{
    NSPoint	point = { 16, 16 };
    float	diff = 0.05f;
    
    if(fadeIn) {
        fract += diff;
        if(fract >= 1.0f) {
            fadeIn = NO;
        }
    }
    else {
        fract -= diff;
        if(fract <= 0.0f) {
            fadeIn = YES;
        }
    }
    
    [iconImage dissolveToPoint:point fraction:fract];
}
@end

メソッドは 3 つ。awakeFromNibfiredByTimerdrawRect だ。

awakeFromNib でやっているのは、まず初期化。そのあと、NSTimer をスタートさせている。NSTimer で叩き起こされるのは、firedByTimer メソッドだ。

firedByTimer では、やっていることは、極めて単純。self に対して、setNeedDisplay を投げているだけだ。これによって、メッセージングシステムを経由して、drawRect が呼び出される。

drawRect では、実際に View の内容を描画している。fract の値を適当に変えて、dissolveToPoint で表示するんだ。

これで、OK!ちなみに、NSTimer を使っても、Process Viewer で見ている限りでは、プロセスは増えないようだ。気楽に使えるね。いやー、これだけでアニメーションできるとは、楽ちん極まりない。スレッドを作る必要がないぶん、Java より楽か?

実行中の画面。このシャーロックアイコンが、ゆっくり消えたり、現れたりする

January 23, 2001

■サンプルコード解析(偉そー):Cocoa With Carbon or C++

お宝経由で知ったんですが、ADC (Apple Developer Connection) が Mac OS X の新しいサンプルコードを公開したらしい。どうやら、Carbon コードや、C++ コードを Cocoa から呼び出すプログラムらしい。さっそくダウンロードしてみた。

ダウンロード、解凍、ビルドは問題なく行えた。アプリを走らせると、次のようなウィンドウが出る:

このアプリケーション自体は、Cocoa アプリ(つまり、Objective-C)として動いているんだ。下の 'Say Hello To The World' ボタンを押すと、アラートパネルが表示されて、Say Hello と書かれているんだ。で、まん中にあるラヂオボタンがみそだ。一番上を選ぶと、普通に、Objective-C から Objective-C を呼び出す。二番目は、Objective-C から C++ のコードを呼び出して使っている。最後は、Carbon のコードを呼び出す、ということらしい。

さて、ソースコードの中身をのぞいてみよう。肝となるのは SayHello.m っていうファイルだ。ここに、message1message2message3 っていうメソッドがあって、それぞれラヂオボタンに対応している。

message1 では、単に、NSRunAlertPanel を呼び出しているだけだ。これが一番簡単なやつね:

- (void)message1:(id)sender
{
    //Display an alert message on the screen
    NSRunAlertPanel(@"Regular Obj-C from Obj-C"
                   ,@"Hello, World! This is a regular old\
                      NSRunAlertPanel call in Cocoa!"
                   ,@"OK",NULL,NULL);
}

一つ飛ばして、message3 では、Carbon のコードを呼び出している。これも、直接呼んでいるだけだ。ここでは Carbon のコードは C で書かれているので、Objective-C から問題なく呼び出せる。Objective-C は、C のスーパーセットなので(この位置付けは C++ と同じだ)、C のコードは透過的に呼び出せる。さらに、この場合は、Carbon のコードも問題なくいきなり呼び出せるらしい(他のものはどうなのか?):

- (void)message3:(id)sender
{
    Alert(128,NULL); //This calls into Carbon
}

で、message2 の話。message2 では、最終的には NSRunAllertPanel を呼び出すんだけど、その前に、必要な情報を C++ のコードから取得するような作りになっている。Foo というクラスで、FooClass.h に定義がある。だけど、Objective-C から直接 C++ のコードは呼べない。どうするかというと、WrapperForCpluPlus.h っていうファイルの中にあるように、C で C++ をラップする関数を作って、それを経由して C++ のコードを呼び出すんだ。

Objective-C -> C wrapper -> C++

- (void)message2:(id)sender
{
    int howMany;
    NSString *theAnswer;
    setUpFoo();
    howMany=fooGetVariable();
    //This next line stores the Objective-C object
   // running this method in a C++ instance variable,
   // just to show that it can be done.
    fooStoreObjCObject(self);
    deleteFoo();
    //Here we put together an NSString that contains
   // the value returned by fooGetVariable()
    theAnswer=[NSString stringWithFormat:@"Hello, World!\
        When our C++ object is queried from within our\
        Objective-C Cocoa code, it tells us that the\
        number is %i!",howMany];
    //Display an alert message on the screen with the strin
  g// generated above
    NSRunAlertPanel(@"C++ from Obj-C",theAnswer,@"OK",NULL,NULL);
}

fooGetVariable()fooStoreObjCObject()deleteFoo() っていうのが、ラップしてる C 関数だ。

それだけ?うん、それだけのことのようだ、、、


January 22, 2001

■フェード・イン、フェード・アウトアニメーション (1):NSTimer を使う

きのう、半透明表示をしたので、それを使って、フェード・イン、フェード・アニメーションをしてみよう。アイコンが、消えた状態からだんだん現れてきて、まただんだん消えていく、ってやつね。

アニメーションのためには、周期的に View の drawRect を呼び出してやる必要がある。いくつか方法が考えられるんだけど、今回は NSTimer を使ってみよう!

NSTimer を使うと、一定時間後に、指定したメソッドを呼び出すことができる。さらに、周期的に呼び出すこともできる。クラスメソッド scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: を使うんだ。名前なげーな、おい。

[NSTimer scheduledTimerWithTimeInterval:0.05 
             target:self 
             selector:@selector(firedByTimer) 
             userInfo:nil 
             repeats:YES];

と、パラメータを指定すると、時間間隔 0.05 秒で、self の firedByTimer っていうメソッドを、引き数 nil で、繰り返し呼び出す、ってことになる。もぅ、プログラムをそのまま自然言語に直せるね。

ここから分かるように、Objective-C では、コールバックを target(クラスのインスタンス)と、selector(メソッド)の組みでいきなり呼び出せるわけだ。メソッド呼び出しを、メッセージパッシングで処理する Objective-C ならではだね。static 関数にしないと、クラスのメンバ関数をコールバックにできない C++ や、interface を作って、implements してやる手法の Java とかと比べて、はるかにお手軽に使えるよ。これって、結構有利だと思う。

アニメーションの続きは後日。


January 22, 2001

■NSOutlineViewDataSource の implementation

先日の続きの NSOutlineView の DataSource ね。

NSOutlineViewDataSource のドキュメントによると、こいつには 7 つのメソッドがある。そのうちの 4 つが必須だ。残りの 3 つはオプションね。ちなみに、必須メソッドを外してもコンパイルは通る。だけど、実行時にエラーになる。Java の abstract みたいに、コンパイラ時にエラーになってくれればいいのにー。と、思ったけど、Objective-C の場合は、実行時にリンクが解決されるから、コンパイル時にエラーを吐くわけにいかないんだ。うーん、なるほど。どっちがいいのかな?

例として、次のような木構造を考えてみよう。

  • A (root)
    • B
    • C
    • C

root として、A がいて、その子供に B、C、D がいる、っていう構造だ。この木をどうやって DataSource にしてやるのか?必須のメソッドだけ見てみよう。

- numberOfChildrenOfItem

- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item

これは、あるノードに子供が何人いるか?っていうメソッドだね。item として、A が来た場合には 3、B - D が来た場合には 0、って返せばいいわけだ。気をつけるのは item が nil のときは、root の数を聞いてきているんだ。この場合は 1 を返せばいい。1 以外を返すことも可能だから、この View は複数 root をサポートしているんだね。

- isItemExpandable

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item

指定した item は開くこと、つまり三角ボタンをクリックして子供を表示することが可能か?って聞いてきたわけだ。item が A なら YES、それ以外なら NO だね。

- childOfItem

- (id)outlineView:(NSOutlineView *)outlineView 
       child:(int)index 
       ofItem:(id)item

itemindex 番目の子供を返してくれ、っていうメソッド。itemnil の場合は root item を聞いてきてる。したがって、item == nilindex == 0 のときは(この場合 index は 0 しか聞いてこないはず)、A を返す。item == A のときは、index に合わせて、B、C、D を返す。

- objectValueForTableColumnByItem

- (id)outlineView:(NSOutlineView *)outlineView 
      objectValueForTableColumn:(NSTableColumn *)tableColumn 
      byItem:(id)item

さて、いちばんの肝となるメソッドだ。指定された tableColumn に、item を表示するためのオブジェクトを返せ、っていうことだ。上の例で、仮に、View に文字列で 'A' とか 'B' とか表示するとしよう。そのときは、NSOutlineView がデフォルトの状態だとすると、NSString へのポインタを返せばいい。@"A" とかやればいいんだ。

最後のメソッドはちょっと面倒なので、もう少し詳しく見るとしよう。

January 21, 2001

■画像の半透明表示

Mac OS X を、他のたくさんの GUI システムに対して、比類なきものにしているものは?それはもちろん、半透明でしょ、おにーさん。Aqua がなかったら、Windows と区別つかないもんなぁ、普通の人にしてみれば。

というわけで、Mac OS X の命綱、画像の半透明表示をやってみよう。今回は NSImage を使う。NSImage には、半透明で表示するための dissolveToPoint というメソッドがあるんだ。これを使えば一発!さっそくやってみよう。まず、Interface Builder で、NSImage の Subclass を作る。仮に名前を、DissolveIconView としとこう。そして、ウインドゥに適当に Custom View を張り付けて、class を DissolveIconView に指定する。DissolveIconView を Create Files したら、Project Builder に戻って来よう。

さて、ソースだ:

#import <Cocoa/Cocoa.h>

@interface DissolveIconView : NSView
{
    NSImage*	iconImage;
}
@end

まず interface の方。NSImage へのポインタを一個用意した。次は implementation の方:

#import "DissolveIconView.h"

@implementation DissolveIconView
- (void)awakeFromNib
{
    NSSize	iconSize = { 64, 64 };
    
    iconImage = [[NSWorkspace sharedWorkspace] iconForFile:@"/Applications/Sherlock.app"];
    [iconImage retain];
    [iconImage setSize:iconSize];
}

- (void)drawRect:(NSRect)aRect
{
    NSPoint	point = { 0, 0 };

    int		i;
    float	fract = 0.0f;
    
    for(i = 0; i < 5; i++) {
        [iconImage dissolveToPoint:point fraction:fract];
        point.x += 64;
        fract += 0.2f;
    }
}
@end

メソッドを二つ用意した。awakeFromNibdrawRect だ。

awakeFromNib は、アプリケーションが立ち上がって、リソースが用意されたところで呼び出される。ここで、画像を用意する。とりあえず、また、シャーロックのアイコンを使うよ。

で、drawRect。このメソッドは、View が画面に描かれるとき呼び出される。ここで、dissolveToPoint を呼んでいるんだ:

    for(i = 0; i < 5; i++) {
        [iconImage dissolveToPoint:point fraction:fract];
        point.x += 64;
        fract += 0.2f;
    }

このメソッドを呼ぶときは、画像を表示する場所を point として指定し、半透明の度合いを fract で指定するんだ。fract の値は 0.0 から 1.0 の間ね。こうして表示させたのがこんな感じ:

左から順に、0.0、0.2、0.4、0.6、0.8 ね。半透明表示ができたよ。これでより Mac OS X ライクになるね(何が?)。


January 21, 2001

■NSOutlineViewDataSource について

NSOutlineView っていうクラスがあるよね。階層構造を表示するための View だ。その View がなにを表示するか決めるのが NSOutlineViewDataSource プロトコルなんだ。詳しくいうと、NSObject に適用される informal protocol だ。この informal protocol と、カテゴリっていうのは同じものと考えていいいの?誰か教えて。

NSOutlineView は、NSTableView から継承されたクラスで、NSTableView のヘッダファイルの方に、NSOutlineViewDataSource へのポインタが定義されている:

@interface NSTableView : NSControl
{
...
    id			_dataSource;
...
}

id 型だね。で、この dataSource は、Interface Builder から設定できるんだ:

NSOutlineView の Inspector。dataSource が connect されているのが分かる

したがって、dataSource を伴った NSOutlineView の使い方は次のようなステップをふむ:

  1. IB で、適当なクラス(たいていは NSObject)の Subclass として、DataSource (という名前の)クラスを作る。
  2. その DataSource を Instantiate する。
  3. NSOutlineView を選択して(NSScrollView をダブルクリックすると選択できる)、control を押しながらのドラッグで、ぐぐいっ、と DataSource とつなげて、dataSource として接続する。
  4. DataSource を Create Files して、ファイルを作って、中身を実装する。

これで、NSOutlineView に、データを表示させる準備ができた。その実装の中身は、、、また後で。


January 20, 2001

■Class の名前を表示する

ある id 型の変数があったとしよう。さて、こいつのクラスはいったい何だ?早速調べてみよう!

まず、Class 型を表示する。id に代入されているものが、NSObject から派生されているものならば(たいていはそうだ)、class メソッドを使うことができる。class メソッドは、NSObject の protocol として、定義されている:

id anUnknownItem;
Class unknownClass = [id class];

Class 型が得られたら、次はどうするか?こいつの名前を知りたいよね。そのためには、NSStringFromClass() という、そのものずばりの関数がある。解説は、FoundationKit の Functions のページに書いてある:

NSString* classNameStr = NSStringFromClass(unknownClass);
printf("class name is %s.\n", [classNameStr cString]);

最後の行の、cString というメソッドは、NSString のメソッドで、C 言語型の文字列を得るメソッドだ。これで、クラスの名前を知ることができるぜ!


January 20, 2001

■NSApp って何?

ProjectBuilder を使って、Cocoa アプリケーションを作ると、main.m っていうファイルができて、次のようなコードが書かれるよね:

int main(int argc, const char *argv[]) {
    return NSApplicationMain(argc, argv);
}

この NSApplicationMain() って何?NSApplication のドキュメントによれば、この関数は次のような関数と同値らしい(これそのものでは、ないらしい):

void NSApplicationMain(int argc, char *argv[]) {
    [NSApplication sharedApplication];
    [NSBundle loadNibNamed:@"myMain" owner:app];
    [NSApp run];
}

まず、Cocoa のアプリケーションは、かならず NSApplication のインスタンスを一個作らなくてはいけない。それを作るのが、sharedApplication メソッドだ。で、このメソッドを呼ぶと、インスタンスが作られるんだけど、そのポインタは次のようなグローバル変数に入れられる:

APPKIT_EXTERN id NSApp;

この変数、NSApp は、AppKit/NSApplication.h に宣言があるよ。でも extern だから、本体はどっか別にあるんだな。この値は、NSApplication.h を import しておけばどこからでも参照できるから、自分のアプリケーションにアクセスしたいときは、いきなり NSApp を使えばいいんだな。


January 19, 2001

■僕は iTunes に嫉妬する

iTunes をダウンロードした。この文章はインターネット・ラジオを聞きながら書いている。

ユーザインタフェースがガイドラインから外れていることは、さて置こう。今は、Mac OS X への移行のための、ガイドラインの過渡期だと思える。Apple も試行錯誤しているのだと思う。

しかし、実にシンプルで使い易いインタフェースではないか。使い易さを優先したため、機能が制限されているきらいはある。しかし、それは機能過多による、複雑で醜悪なインタフェースをさけるためのトレードオフではないか?ほとんどの機能をメニューを介さずに、ドラッグ・アンド・ドロップで操作できる。この直接さこそが、Mac の持ち味ではないか。

しかし、僕は iTunes の一番の特徴は、Visual にあるのではないかと思う。Visual を選択すると、プレイリストは消え、漆黒のスクリーンが表示される。始めに、白の Apple マークが浮かぶが、それは周りから現れる、映像の波の中に飲み込まれ、静かに消えていく。映像は強烈であるが主張はなく、パターンはあるが同じものとは認識されにくい。美しく、かつ暴力的なまでに変遷を繰り返す映像を眺めていると、自己の意識が溶け出していくのを感じる。

また、映像の動きには、断絶がない。二つの映像のつなぎは、フェードイン、フェードアウトで処理される。それにより、主である音楽の流れを断ち切ることなく存在することに成功している。その手法は、フルスクリーン化する際にも用いられ、細やかな心遣いが感じられる。

僕は iTunes に、プログラマとして、嫉妬する。その確かな技術力に、アプリケーションをまとめる調和性に嫉妬する。そこには、ソフトウェアをひとつの「モノ」として昇華させようという、意志が感じられる。この美意識がある限り、Apple は大丈夫だ。


January 17, 2001

■NSOutlieView の構造

ちょっと話を前に戻して、NSOutlieView の話です。

NSOutlineView は、Finder のリスト表示みたいな、木構造を実現する View のことだ。その実現のために、複数の View が組み合わされている。目に着くものを軽く書いてみると、以下のようになってるんだな:

リストのヘッダを表示する Header View、スクロールバーの上にある Corner View、リストの内容を表す Table Columns、スクロールさせる Scroll View。ざっと見ても、これだけの View があるんだ。

じゃ、実際のコードではどうなっているんだろう?まず、第一に、NSOutlineView は NSTableView を継承してるクラスだ。上に挙げた構成要素は、NSTableView のメンバなんだな。そのことを頭において、NSOutlineView の構造を図にしてみた:

まず、NSOutlineView は NSTableView の子クラス。そして、NSTableView はいくつかのメンバを持っている。ヘッダを表す _headerView は NSTableHeaderView 型。コーナーを描画する _cornerView は NSView 型。そして、テーブルの中のアイテムは NSMutableArray に入っていて、その中身は NSTableColumn 型だ。

NSTableColumn は、ヘッダを描画するための _headerCell と、アイテムを描画するための _dataCell を持っている。それぞれは、保持されている型は NSCell 型だけど、デフォルトでは、それぞれ、NSTableHeaderCell、NSTextFieldCell が入ってるんだ。

てなわけで、これでざっと NSOutlineView のことが分かったかな、、、


January 15, 2001

■アプリケーションを起動する:NSWorkspace を使って

アイコンが表示されたら、とうぜん(か?)アプリケーションを起動させたくなるよな。NSWroksapce を使うと、極めて簡単。launchApplication を使えば、一発で起動できる:

[[NSWorkspace sharedWorkspace] launchApplication:@"Sherlock"];

このメソッドは BOOL を返すんだ。起動が成功するか、すでに起動していれば、YES、失敗すれば NO が返る。引き数にはアプリを指定する。フルパスでなくてもよろし。".app" をつけてもつけなくてもよろし。

ま、これだけだとつまらないので、きのうのアプリをいぢって、アイコンをダブルクリックすると起動するようにしてみた。きのうのコードに以下のものを付け加えるんだ:

- (void)mouseUp:(NSEvent*)event
{
    if([event clickCount] == 2) {
        [[NSWorkspace sharedWorkspace] launchApplication:@"Sherlock"];
    }
}

mouseUp は NSResponder からオーバーライドしたメソッド。引き数として渡される NSEvent の clickCount からクリック数を知ることができるんだ。ここでは 2 回クリックされたときに、アプリケーションを立ち上げてる。決め打ちだけどね。

ここまでくれば、ラウンチャが作れるなぁー。

January 14, 2001

■NSWorkspace を使ってアイコンを表示する

やはり Mac のリソースといったら、アイコンだろう!というわけで、アイコンを表示させてみよう。アイコンは .app の中の .icns というファイルにしまわれている。このファイルのパスは Info.plist からたどれるわけだ。しかぁーし、この .icns ファイルのフォーマットが分からない。ドキュメントは公開されていないのか?

しかぁーし(しつこい)、簡単に .icns ファイルから画像を読み出す方法がある。それが NSWorkspace を使う方法だ。このクラスの iconForFile メソッドを使うと、NSImage 型でアイコンの画像を読みだせるんだ。

今回は InterfaceBuilder を使った。IB で ImageView のサブクラス、IconImageView を作る。そして、ウインドゥに適当に CustomView を張り付けて、クラスに IconImageView を指定するんだ。そして、Create Files を呼び出す。

IconImageView では、とりあえず initWithFrame をオーバーライドした。以下ソースコードだ:

#import "IconImageView.h"

@implementation IconImageView
- (id)initWithFrame:(NSRect)frameRect
{
    self = [super initWithFrame:frameRect];
    if(self) {
        NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
        NSImage*	image = [workspace iconForFile:@"/Applications/Sherlock.app"];
        NSSize		imageSize = { 128, 128 };
        
        [image setSize:imageSize];
        [self setImage:image];
    }
    
    return self;
}

@end

肝は NSWorkspace に関するところだ。NSWorkspace はアプリケーションごとに一個インスタンスが作れらるらしい。それをクラスメソッド sharedWorkspace で取り出すことができる:

        NSWorkspace* workspace = [NSWorkspace sharedWorkspace];

そのインスタンスから iconForFile を呼び出す。このメソッドは、パスを指定すると、そのファイルのアイコンの画像を取ってきてくれる、すげー便利なものなんだ:

        NSImage*	image = [workspace iconForFile:@"/Applications/Sherlock.app"];

画像が得られたら self に setImage してやると。これでアイコンが表示できたぜ!


January 14, 2001

■Info.plist にアクセスする from Core Foundation

きのうは NSBundle から Info.plist を読みにいったけど、きょうは Core Foundation からいってみよう!メインは Core Foundation の Bundle Service を使うことだ。NSBundle とほとんど同じなんだけどね。まずはソースコードだ:
#include <CoreFoundation/CoreFoundation.h>

int main (int argc, const char *argv[]) {
     CFURLRef		url;
     CFBundleRef		bundle;
     CFDictionaryRef	dict;
     CFIndex			ind;
     CFStringRef		key, value;
    
     url = CFURLCreateWithFileSystemPath(
                    kCFAllocatorDefault, 
                    CFSTR("/Applications/TextEdit.app"), 
                    kCFURLPOSIXPathStyle, 
                    true);
     bundle = CFBundleCreate(kCFAllocatorDefault, url);
     dict = CFBundleGetInfoDictionary(bundle);
    
     CFRelease(url);
     CFRelease(bundle);
    
     exit(0);
}

NSBundle 用のコードを読んだ人ならばすぐ分かるでしょう。まず、システムパスを CFURLRef の形でつくる:

     url = CFURLCreateWithFileSystemPath(
                    kCFAllocatorDefault, 
                    CFSTR("/Applications/TextEdit.app"), 
                    kCFURLPOSIXPathStyle, 
                    true);

そして、それを使って CFBundleRef をつくる:

     bundle = CFBundleCreate(kCFAllocatorDefault, url);

その bundle から、Info.plist の内容が含まれる CFDictionary を GetInfoDictionary を使って取ってくる:

     dict = CFBundleGetInfoDictionary(bundle);

これで OK だ!ためしにアクセスしてみよう。CFBundleIconFile の値を取ってみよう。次みたいなコードを挿入してみて:

     key = CFSTR("CFBundleIconFile");
     value = CFDictionaryGetValue(dict, key);
     printf("value %s\n", 
         CFStringGetCStringPtr(value, kCFStringEncodingMacRoman));

"Edit.icns" と表示されるはずだ。先日、NSBundle からアクセスすると、すべての key が NSString になってしまうようだ、と書いたけど、Core Foundation を使うと大丈夫みたいだ。あんまり厳密にテストしてないけど。

ところで、CFDictionary って、含まれているすべてのオブジェクトに簡単にアクセスする方法がないような気がする。つまり、Enumerator 系の関数が用意されていないような気がするんだ。ということは、key の値が分かっていないとアクセスできないのか?それとも何か方法があるのかな?


January 13, 2001

■glclock を動かしてみた

glclock を動かしてみたよ。glclock は Masa(川瀬 正樹)さんの作った OpenGL による 3 次元懐中時計プログラム。時計として使うよりは、OpenGL の機能のデモのようなものだそうだ。

プログラムは GLUT をベースにしているので、ほぼ問題なくコンパイルが通ったよ。動作速度も良好。ただし、ウインドゥをいぢくっていると、時々挙動がおかしくなった。MacOS X Public Beta の GLUT にはまだ少し問題があるようだ。でも、ざっと動かしたところは結構いい感じ。

参考:
glclock
Masa(川瀬 正樹)さんによる、オリジナルのサイト
glclock for Mac
Shinichiro Hirama さんによる、MacOS (Classic) 上での glclock

January 13, 2001

■Info.plist にアクセスする from NSBundle

さて、Info.plist の中身をプログラムから読んでみようか。まずは、NSBundle を使ってみる。

さっそくソースコード。以下の通りだ:

#import <Foundation/Foundation.h>

int main(int argc, const char *argv[]) {
     NSAutoreleasePool*	pool = [[NSAutoreleasePool alloc] init];
     
     NSString*	str = @"/Applications/TextEdit.app";
     NSBundle*	bundle = [NSBundle bundleWithPath:str];
     NSDictionary*	dict = [bundle infoDictionary];
     NSEnumerator*	keyEnum = [dict keyEnumerator];
     id	key;
     
     while((key = [keyEnum nextObject])) {
         printf("%s\n", [key cString]);
     }
     
     [pool release];
     
     return 0;
}

このプログラムでは、アクセスするアプリケーションをフルパスで指定している。ま、手抜きだね。とりあえず、TextEdit を指定している:

     NSString*	str = @"/Applications/TextEdit.app";

そして、NSBundle のファクトリメソッド bundleWithPath を使って、NSBundle のインスタンスを作る:

     NSBundle*	bundle = [NSBundle bundleWithPath:str];

インスタンスから、infoDictionary を使うと、NSBundle から Info.plist を NSDictionary 型で取得することができるんだ:

     NSDictionary*	dict = [bundle infoDictionary];

で、その後、Info.plist の中身を NSEnumerator を使って触ってみたんだけど、どうも、すべての key が NSString になっているらしいんだ。Info.plist の中には NSDictionary 型や NSArray 型の key もあるはずなんだけど、NSString になっているようだ。これが仕様なのか、未実装なのか、使い方が間違っているのか、よく分かんなーい。


January 12, 2001

■Info.plist の中身 for NSBundle

Info.plist の中には、接頭子 CF で始まるものの他に、NS で始まるものもある。こいつらはいったいどこで定義されているんだ?

Cocoa のドキュメントを探してみると、Resource Management の章があやしいんだけど、Description forthcoming になっている。つまり、まだ書かれていないわけだ。うーん、残念。

仕方ないので、API Document の NSBundle の項を読んでいたら、infoDictionary のところに、少しだけ記述があった。それによると、Info.plist の共通の key は、NSExecutableNSExtensionsNSIconNSMainNibFileNSPrincipalClass だそうだ。

NSMainNibFile や NSPrincipalClass は、いかにも Cocoa 環境でしか使わなさそうだね。それに対して、NSIcon とかは CoreFoundation で定義しているものとかぶりそうだ。この辺の使い分けはどうなるんだろう?と、いぶかんでもドキュメントが出てくるまでは分からないよなあ。

January 7, 2001

■Info.plist の中身

きょうは時間がないから、手短にいくよぅっ!

きのうは ProertyListEditor で Info.plist をのぞいたけど、この XML ファイルの定義はどうなっているんだ?それは、CoreFoundation のドキュメントの中に書いてあるんだ。/Developer/Documentation/CoreFoundation からたどれるよ。

まず、使われている XML タグの種類は?Property List XML Tags に記述がある。こんな感じだ:

CF type
XML tag
CFString <string>
CFNumber <number>
CFDate <date>
CFBoolean <boolean>
CFData <data>
CFArray <array>
CFDictionary <dict>

そして、key として使われている要素の定義は?これは Bundle ServicesMore About the Info.plist File の中に定義がある。Standard KeysFinder の Keys が紹介されているんだ。詳しい中身はまた後日。じゃ!


January 6, 2001

■PropertyListEditor を使う

NSBundle の話の前に PropertyListEditor を使ってみよう。PropertyListEditor は、Developer Tool をインストールするときいっしょに入れられる。/Developer/Applications にあるよ。

PropertyListEditor アイコン

まず、アプリケーションファイルの中身を見てみよう。アプリケーションアイコンの中は実はディレクトリだってのは知ってるよね!?一番簡単に中身を見るには、Finder でアプリケーション・アイコンを選択した状態で、control + クリックするんだ。そしてコンテクスト・メニューから "Show Package Contents" を選択するんだ(日本語版では何ていうんだ?)。そうすると、別のウインドゥで中身が見れるよ。

さて、中を見ると、ルートが Contents って名前になっていて、その中にいくつかのファイルがあるだろう。今回注目するのは、"Info.plist" ってファイルだ。これをダブルクリックすると ProertyListEditor が立ち上がって、中身が見れるぞ。こんな感じだ:

OutlineView を除いてみたところ

色んなプロパティとその値が登録されていることが分かるよね。これらの取得の仕方と内容は、また次回だ。CoreFoundation の方にも手を伸ばさないとなぁ。ちなみに、アイコンに XML と書いてあるところからわかるように、"Info.plist" の中身は XML だ。普通のテキストエディタや、他の XML エディタでものぞけるよ。


January 3, 2001

■Cocoa プログラミング Tips: OutlineView Example での NSFileManager の使い方

ちぁっす!

前振りなしで、いきなりきのうの続きだ。Developer Tool についてくる Example、OutlineView を見ていたけど、きょうはファイルの探りかただ。

ファイルを取り扱っているのは、Example の中の、クラス FileSystemItem だ。このクラスは OutlineView で表示するために、帰納的にアクセスするように作られている。parent があって、children がいて、parent が nil だったらルートだー、っていうアレね。<こんな説明でいいのかよ。

さて、実際にファイルシステムにアクセスしているのは、メソッド children だ。ここでは NSFileManager を使っているんだ!NSFileManager の使い方はけっこう簡単。まず、ファクトリメソッド defaultManager で、インスタンスを取得する:

NSFileManager *fileManager = [NSFileManager defaultManager];

そのインスタンに対して必要なメソッドを呼び出してやる。Example で使っているのは、fileExistAtPath:isDirectory と directoryContensAtPath の二つだ:

- (BOOL)fileExistsAtPath:(NSString *)path isDirectory:(BOOL *)isDirectory;
- (NSArray *)directoryContentsAtPath:(NSString *)path

この関数の機能は、読めば分かるよね。前者は path で指定したファイルが存在するかどうか、かつそれがディレクトリかどうかを調べる。後者はディレクトリの中身を取得する。

一つ注意するのは、前からよく言われていることだけど、Classic な MacOS のようにファイル ID でファイルにアクセスするのではなく、完全にパスベースになっていることだ。これに関しては賛否両論、喧々諤々あるよな。ここでは、とりあえず、パスベースの方が新規に参入する人には楽なんじゃない、という立場を取っておこう。

NSFileManager を使うと、ファイルの生成、削除、ディレクトリの調査、シンボリックリンクの作成なんかができるみたいだ。じゃあ、ファイルに格納されているリソースを取り出したいときは?ふっふっふ、それは NSBundle を使うんだよ。というわけで、後日に続く。


January 2, 2001

■Cocoa プログラミング Tips: Example に見る NSOutlineView の使い方

正月休みなのをいいことに、久しぶりに MacOS X の開発環境をいじりまくってるよ。

そこで、脈絡がないけど、Developer Tool の Example である OutlineView をいぢくってみたので、解説してみよう。OutlineView は、ひとことでいえば、Finder のリスト表示を実現するアプリケーションだ:

アプリケーション OutlineView。ルートから表示させているところ

じゃあ、ざざっと解説するぞ!きょうは、どうやってリスト階層を表示しているか?という点に着目してみよう。まず、このプロジェクトには .nib ファイルが一つある。MainMenu.nib だ。これを Interface Builder で開けてみよう。重要なコンポーネントは二つある。まず一つ目は MyWindow ね。この Window は中に NSScrollView を一つ持っている。ScrollView っていうのは、リスト表示をするやつのことだ:

MyWindow を表示させたところ。中に張り付けてあるのが NSScrollView

もうひとつ、大事なものは、DataSource というクラスがある。これは NSObject のサブクラスだ。ただし、Outlet も Action もなんもなし。インスタンス化はされているんだ。

この二つが、この .nib ファイルの重要なアイテムなんだ。NSScrollView と、DataSource ね。さて、ここからが肝なんだけど、Inspector を表示させて、Window の中の NSScrollView のデータを表示するところをダブルクリックしてみよう。NSScrollView は、内部に NSOutlinewView を持っているんだ。ダブルクリックすることによって、NSOutlineView のインスペクタを表示させることが出来る:

NSOutlineView のインスペクタ。dataSource と delegate に注目

これを見ると、dataSource と delegate っていう二つの Outlet が DataSource に接続されていることが分かるよね。こうすることによって、NSOutlineView に表示させる内容と、DataSource クラスを結び付けているんだな。うーん、IB だねぇ。

じゃあ、次は DataSource クラスのソースコード見てみよう。ヘッダファイル DataSource.h では、NSObject から継承しているという情報だけで、なんのメソッドも宣言されていない。実装部である、DataSource.m の方では、五つのメソッドが定義されている。その内の最初の四つは dataSource に関するもので、残りの一つは delegate に関するものだ:

For dataSource
- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemEpandable:(id)item
- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableClumn:(NSTableColumn *)tableColumn byItem:(id)item
For delegate
- (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item

さて、これらのメソッドは何から継承されているのか?AppKit のリファレンスで、NSOutlineViw を見てみよう。すると、NSOutlineViewDataSource という informal protocol があることが分かるんだ。Data Source クラスでは、この protocol を実装しているんだな。さらに、NSOutlineViewDelegate という protcol の存在も分かる。でも、これに関してはドキュメントが見つからなかったよ。そこで、ヘッダファイル AppKit/NSOutlineView.h を見ることによって、定義を知ることができるんだ。これらの protocol のうち、必要なものを実装するだけで、outline view をコントロールすることができるんだな。

ちょっと DataSource クラスの実装を見てみよう。このクラスの実装では、FileSystemItem というクラスをサポートとして使っている。実際にファイル名を調べるのはこっちのクラスなんだな。NSFileManager を用いることによって調べているようだ。

ちなみに、よくわかんないけど、このアプリケーションのアイコンはねこになっている。謎だけど、mkino はねこ好きだからよしとしよう(笑):

ねこアイコン。かわいい!

January 1, 2001

■Cocoa プログラミング Tips: コマンドラインから Cocoa を呼び出すときの注意点

あけましておめでとうございます。しかーし、新年のあいさつも、21 世紀のはじまりも特に気にせずいつもどおりに行くぞっ。

きょうは Cocoa プログラミングの初歩的な Tips だ。Classic な MacOS プログラミングと比べて、MacOS X のプログラミングのさいに、やっぱり便利なものはなにか?それは、標準のコマンドラインの存在だと思うんだ。もちろん、アプリケーションとして仕上げるときは使わないけど、デバッグの段階では便利だよね。

でも、いざコマンドラインを使ってみると、問題があることに気付くと思うんだ。C や C++ の標準ライブラリ(stdio とか)だけのときは特に問題ない。でも、AppKit や Foundation Kit のクラスを使おうとすると、"_NSAutoreleaseNoPool()" っていうエラーコメントが表示されないかい?

AppKit では(というか Objective-C 自体にそういう傾向があるのか?)、メモリ管理に、参照カウンタを用いた、自動的なオブジェクトの削除を行っているらしい。もうちょっと厳密に言うと、AppKit のルート・オブジェクトである NSObject が、参照カウンタ機構の存在を前提に設計されているんだ。その管理を行うのが NSAutoreleasePool だ。

Project Builder でテンプレートを使ってアプリを作る場合は、自動的に NSAutoreleasePool が作られるから、気にしなくてよろしい。でも、コマンドラインのアプリを作るときは、自分で作らなくちゃいけないんだ。そのようなときは、次のおまじないを呼ぶ:

int main(int argc, const char *argv[]) {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    
    ...
    // AppKit の関数
    ...

   [pool release];
    
    return 0;
}

まず、NSAutoreleasePool のインスタンスを作る。そのあとで、AppKit や FoundationKit の関数を呼び出す。最後に NSAutoreleasePool の release を呼び出して、オブジェクトを削除する。これで、とりあえず OK だ。

細かいことは Foundation-Kit douments の NSAutoreleasePool の項を読んでくれ。もっと突っ込んだ解析は後日やるかもしれない。じゃ!


Home | Link | Download | Back Number | Speciall Issue

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

HMDT