ページ

2008年2月12日火曜日

NSGradiationサンプルのフォロー

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

前回のサンプルのフォローを少し。

bindingを使い Angleの変更などを Viewへ反映している。



角度やグラデーション、色の数などの状態を MyControllerで保持し、これを Object Controllerを介して画面上のコントローラに紐づけている。グラデーションを描画する MyViewはこれらの状態をキー値監視(Key-Observing)の仕組みを利用して取得し、表示に反映させている。

Observerの登録は addObserver:forKeyPath:options:context:を使う。

MyView.m

-(void)awakeFromNib
{
[controller addObserver:self
forKeyPath:@"angle"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:nil];
[controller addObserver:self
forKeyPath:@"gtype"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:nil];
[controller addObserver:self
forKeyPath:@"cflag"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:nil];
}


監視対象の値が変化すると observeValueForKeyPath:ofObject:change:context: がコールバックされる。ここで再描画を指示する。
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
[self setNeedsDisplay:YES];
}


上記メソッドが呼ばれた時のデバッグ出力例 (1) Angleを変更:
2008-02-11 12:08:51.184 Gradient[4320:10b] change={
kind = 1;
new = 60;
old = 30;
}


デバッグ出力例 (2) タイプ(ラジオボタン)を変更:
2008-02-11 12:08:53.137 Gradient[4320:10b] change={
kind = 1;
new = 1;
old = 0;
}


デバッグ出力例 (3) 色数(チェックボックス)変更:
2008-02-11 12:08:47.092 Gradient[4320:10b] change={
kind = 1;
new = 1;
old = 0;
}



- - - -
bindingは本当に便利。

2008年2月11日月曜日

NSGradiation

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

MacOSX 10.5 からグラディエーションを表現するクラス NSGradiation が追加されている。

NSGradient Class Reference

始点の色と、終点の色を指定するだけで簡単にグラディエーションを作ることができる。



グラディエーションは直線だけでなく放射状でも表現できる。


色も3色以上が使える。



簡単なサンプルを作ってみた。

サンプル:sGradient.zip

2008年2月10日日曜日

スクラップブックその5 - スクロールバー追加

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

今度はスクロールバーを追加する。



Interface Builder で WorkViewを選択後、メニューから Layout->Embed Objects In->Scroll View を選ぶ。
これだけだと画像が WorkViewの範囲外にはみ出てもスクロールできない。はみ出た分だけ WorkViewの frameサイズを大きくする必要がある。

そこでドラッグ処理内で範囲外かどうかを判断して、その場合は frameサイズを拡大する処理を追加する。

WorkView.m


- (void)mouseDown:(NSEvent *)theEvent {
  :
 [self resizeWithRect:[item rect]]; // item ...ドラッグしている画像
  :
}

- (void)resizeWithRect:(NSRect)rect
{
float x = rect.origin.x + rect.size.width;
float y = rect.origin.y + rect.size.height;
NSSize view_size = [self bounds].size;
BOOL changed = NO;

if (view_size.width < x) {
view_size.width = x;
changed = YES;
}
if (view_size.height < y) {
view_size.height = y;
changed = YES;
}
if (changed) {
[self setFrameSize:view_size];
}
}


これでドラッグ中に WorkViewをはみ出すとスクロールバーが出るようになる。

なお、スクロールバー追加にあわせて WorkViewの y座標を反転(flip)させている。
WorkView.m

- (BOOL)isFlipped
{
return YES;
}


NSViewの座標系はデフォルトでは左下が (0,0)となるが、isFlippedをオーバーライドして YESを返すとそのViewは左上が (0,0)の座標系となる。
ただこのままでは画像が正しい位置に表示されないので、描画時に compositeToPoint:operation の位置を補正している。

[item.image compositeToPoint:NSMakePoint([item.x floatValue],
[item.y floatValue]+[item.height floatValue])
operation:NSCompositeSourceOver];


Flipped Coordinate Systems の Drawing Content in a Flipped Coordinate System > Drawing Images によると、compositeToPoint:fromRect:operation: を使った描画では、描画開始位置のみが isFlippedの影響を受けて、NSImage自体の座標系は変わらない(左下が (0,0)として右上方向に描画される)。このあたりの動きは「Figure 3-9 Compositing an image to a flipped view」の図を見ると一目で分かる。

NSImageの座標系も setFlipped: を使い Y座標を反転させることができるが、実際これは(感覚的に意図したようには)働かない。使って見ると下のように上下反転した表示になる。



この辺りは下記に記述がある。
Image Coordinate Systems

2008年2月9日土曜日

ヒレガス本

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

ヒレガス本は第三判が5月に出るらしい。



情報源:
iBook G4 Blog

上記は英語で翻訳版ではない。



アメリカの amazonを眺めていたらこんな本もあった。



同名で同じ作者がオライリーから過去に本を出していたので、出版社を替えての改訂版なのかもしれない。
どんな内容か気になる。

2008年2月8日金曜日

スクラップブックその4 - メニューとの連携

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

前回、deleteキーで削除を行ったが、メニューから削除を行えるようにする。
メニューにある削除(Delete)はデフォルトで First Responderに接続されている。
前回 acceptsFirstResponder を実装したので delete: メソッドを実装するだけでメニューが有効になる。

WorkView.m


-(IBAction)delete:(id)sender
{
if (_selected_item) {
[_controller deleteItem:_selected_item];
_selected_item = nil;
[self setNeedsDisplay:YES];
}
}


ここが Cocoaの面白いところで、実行時に FirstResponderが deleteメソッドを実装しているか判断してメニューの有効・無効の制御している。

メニューにはキーを割り当てることができるので deleteキーを割り当てる。Interface Builderでメニューから Deleteを選び属性の Key Equiv. に設定する。



すると前回実装した keyUp:(NSEvent *)theEventは不要となる。これは削除しておこう。


ただ、このままでは画像が選択されていない時もメニューが有効になってしまう。選択されていない時にはメニューを無効にしたい。cocoaはメニューの有効・無効を判断するのにメソッドの実装の他、そのメニューのターゲットとなるオブジェクト(今回は WorkView)に対してメッセージ validateMenuItem: を送り、その戻り値(BOOL)で判断を行っている。そこで WorkViewにこのメソッドを実装する。


-(BOOL)validateMenuItem:(NSMenuItem*)menuItem
{
if ([menuItem action] == @selector(delete:)) {
return (_selected_item != nil);
} else {
return YES;
}
}


これで選択されていない時はメニューが無効になる。

- - - - -
今回もヒレガス本に世話になりました。
米国では新版が出るとの噂を聞いた。日本訳も出てくれると良いのだが。

2008年2月7日木曜日

スクラップブックその3 - 削除

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

選択した画像を削除する。削除はDeleteキーで行う。

まずビューでキー入力をハンドリングする。

WorkView.m


- (BOOL)acceptsFirstResponder
{
return YES;
}
- (void)keyDown:(NSEvent *)theEvent
{
}
- (void)keyUp:(NSEvent *)theEvent
{
if ([theEvent keyCode] == 117 && _selected_item) {
[_controller deleteItem:_selected_item];
_selected_item = nil;
[self setNeedsDisplay:YES];
}
}

キーボード・イベントを取り扱う場合には acceptsFirstResponder をオーバライドし YESを返す(ヒレガス本参考)。
次にdeleteキーが押されたかを keyUp: で判定する。keyDown: で処理する場合は、押しっぱなしだと何度も呼び出されるので1回だけ呼出される keyUp:を使う。MyController#deleteItemを呼び出してモデルを削除した後、選択を解除し再描画する。


以下は MyController内でのモデル削除の処理コード。

MyController.m

-(void)deleteItem:(Item*)item
{
NSString *filepath = [_path_to_save stringByAppendingPathComponent:item.filename];
[_items removeObject:item];                         // (1)
NSManagedObjectContext* moc = [_app_delegate managedObjectContext];
[moc deleteObject:item];                           // (2)

NSFileManager *fm = [NSFileManager defaultManager];
[fm removeFileAtPath:filepath handler:nil];                  // (3)
}

処理は3つ。(1)配列から削除、(2)CoreDataから削除、(3)画像ファイルの削除。
(本当はちゃんとエラー処理を入れてトランザクションを形成すべし)。

2008年2月6日水曜日

スクラップブックその2 - 選択枠を描く

このエントリーをブックマークに追加 このエントリーを含むはてなブックマーク

選択された時に画像の周りに灰色の枠線を描く処理を入れてみる。

まず選択画像を表すメンバ変数をビューへ追加する。

WorkView.h


@interface WorkView : NSView {
   :
Item* _selected_item;
}

その上でマウスで画像が選択された時に上記変数へ入れておく。


drawRect: で選択画像の周りに枠線を描画する。
WorkView.m

- (void)drawRect:(NSRect)rect {
[[NSColor whiteColor] set];
NSRectFill(rect);

for (Item* item in [_controller items]) {
[item.image compositeToPoint:NSMakePoint([item.x floatValue], [item.y floatValue])
operation:NSCompositeSourceOver];

if (_selected_item == item) {
// draw selection
[[NSColor lightGrayColor] set];
NSRect rect = [item rect];
[NSBezierPath setDefaultLineWidth:1.5];
[NSBezierPath strokeRect:rect];
}
}
}


こんな感じ。