ページ

2008年2月29日金曜日

画面キャプチャその1 - 画面全体を黒くする

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

スクリーンショットの取り方が分かったので、これらを使った画面キャプチャツールを作ってみよう。選択したウィンドウのキャプチャ画像を作成するようなもの。イメージとしては画面全体が暗くした後、マウスが置いてあるウィンドウだけ明るく表示する。そこでマウスをクリッックするとキャプチャ画像がデスクトップへ作られる。

最初の一歩として画面全体を暗くするコードを書いてみた。こんな感じ。



ソース:FullScreenSample-1.zip

実行すると画面全体が暗くなる。クリックするとプログラムは終了する。それだけ。


スクリーン上、他のウィンドウの上に描画するには画面サイズいっぱいの NSWindowを作り、一番上に表示すれば良い。



ただし普通に作ると背景色によってそれより下のウィンドウやデスクトップがすべて隠れてしまう。そこで NSWindow#setOpque: を使い背景を透明にしてしまう。さらに NSViewをその上に配置し、ここで半透明の黒で塗りつぶす。これで画面全体が暗くなる。

NSWindowの生成コードは次のようになる。

MyController.m

 NSScreen* main_screen = [NSScreen mainScreen];
NSRect fullscreen_frame = [main_screen frame];
_fullscreen_window = [[NSWindow alloc] initWithContentRect:fullscreen_frame
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered
defer:NO
screen:main_screen];
[_fullscreen_window setReleasedWhenClosed:YES];
[_fullscreen_window setDisplaysWhenScreenProfileChanges:YES];
[_fullscreen_window setDelegate:self];
[_fullscreen_window setBackgroundColor:[NSColor clearColor]];
[_fullscreen_window setOpaque:NO];
[_fullscreen_window setHasShadow:NO];
[_fullscreen_window setLevel:NSScreenSaverWindowLevel + 1];
[_fullscreen_window makeKeyAndOrderFront:self];

_fullscreen_view = [[[MyView alloc] initWithFrame:fullscreen_frame] autorelease];
[_fullscreen_window setContentView:_fullscreen_view];
[_fullscreen_view setNeedsDisplay:YES];
[_fullscreen_view setController:self];


画面サイズは NSScreen から取得できる。NSWindowはタイトルバーなどを持たない NSBorderlessWindowMask で作成する。ポイントは setOpaque:NO と setBackgroundColor:[NSColor clearColor]。前者だけでは透明にならない。

NSView は半透明の黒で塗りつぶすだけ。

MyView.m
- (void)drawRect:(NSRect)rect {
// Drawing code here.
[[NSColor colorWithDeviceRed:0.0 green:0.0 blue:0.0 alpha:0.75] set];
NSRectFill([self frame]);
}


- - - -
次は CG系関数とマウストラッキングを使ってウィンドウ選択の視覚効果を組み込む。

2008年2月28日木曜日

スクリーンショットを撮る

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

以前からこのブログの画像はプレビュー(Preview.app)のグラブ機能を使って作成している。が、イマイチ使いづらい。以前利用していたスクリーンショットを撮るツールが Leopardになってから使えなくなったので仕方なくプレビューを使っているが、いっそのこと自分でツールを作ろうかと考えて少し前から調べ始めている。

今回画面全体のスクリーンショットを撮る簡単な方法が見つかったのでサンプルを作ってみた。



ソースコード:ScreenshotSample.zip


実行すると何も表示されないウィンドウが表示される。ここで "Capture"ボタンを押すと画面全体のスクリーンショットが表示される。

ソースコードは十数行だけ。
MyController.m

@implementation MyController

-(IBAction)capture:(id)sender
{
CGWindowID window_id = [window windowNumber];

CGImageRef cgimage = CGWindowListCreateImage(CGRectInfinite, kCGWindowListOptionOnScreenBelowWindow,window_id, kCGWindowImageDefault);

NSBitmapImageRep *bitmap_rep = [[NSBitmapImageRep alloc] initWithCGImage:cgimage];
NSImage *image = [[[NSImage alloc] init] autorelease];
[image addRepresentation:bitmap_rep];
[bitmap_rep release];
[view setImage:image];

CGImageRelease(cgimage);
}

@end


MacOSX 10.5 から使えるようになった各種の関数を使うと表示中のウィンドウを簡単にキャプチャできるようになる。


ADCからサンプルコードが出ていて、こちらは表示中のウィンドウ一覧を表示・選択できて様々なオプションを試すことができる。
Son Of Grab



- - - -
なお ADCのサンプルでは CGWindowID の取り方が出ていなかった。この為、最初は下記のように kCGNullWindowID を使っていたのだが、そうするとサンプルアプリのウィンドウまでキャプチャされてしまう。

CGWindowID window_id = kCGNullWindowID;




そこで NSWindow#windowNumber を使ってみたところ(そして kCGWindowListOptionOnScreenBelowWindowオプション)アプリのウィンドウを省くことができるようになった。

参照:Leopard CGWindowList* APIs
http://shiftedbits.org/2007/11/08/leopard-cgwindowlist-apis/

CGWindowID と NSWindow#windowNumberは同じではないとのことだが、今回はうまくいっていった。Carbonには HIWindowGetCGWindowID (MacWindows.h) が新しく用意されているので、こちらを使っても良い。

CGWindowID window_id = HIWindowGetCGWindowID( [window windowRef] );


試したところうまくいった。


その他参照情報:
CocoaDev: ScreenShotCode
http://www.cocoadev.com/index.pl?ScreenShotCode
MacOSXでのスクリーンショット取得方法の遷移を時系列で見るようでちょっと面白い。

2008年2月27日水曜日

NSRect を CoreDataで扱う(その3)

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

さて今度は CoreDataで NSRectを保存してみる。

ソースコード:sp5.zip



サンプルを実行すると新規ドキュメントが一つ作成される。「ADD」ボタンを押すと色の着いた四角が追加される。この四角が CoreDataのエンティティに対応する。



エンティティのヘッダ SampleEntity.h

@interface SampleEntity :  NSManagedObject  
{
}

@property (retain) NSColor* color;
@property (retain) NSValue* frame;
@property (retain) NSNumber* order;

@end


frameに今回の目的である NSRect を格納している(実際には NSValueでラップしている)。


プロジェクトはテンプレート "Core Data Document-based Application" を使い必要なクラス(NSPersistentDocumentのサブクラス)を自動生成している。これに四角を描画する MyView を追加してある。エンティティデータへのアクセスは NSArrayControllerを使って行う。各オブジェクト間の関連は次のとおり。



再描画の為に MyViewは Array Controller の managedObjectsを監視(Observing)している。

MyView.h
-(void)awakeFromNib
{
[_array_controller addObserver:self
forKeyPath:@"arrangedObjects"
options:NSKeyValueObservingOptionNew
context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
[self setNeedsDisplay:YES];
}


アウトレットで接続した Array Controller を通じて SampleEntityの配列を取り出し、ソートし、四角を描画する。

- (void)drawRect:(NSRect)rect {
NSArray *list = [_array_controller valueForKey:@"arrangedObjects"];
NSSortDescriptor* sort_desc = [[[NSSortDescriptor alloc] initWithKey:@"order" ascending:YES] autorelease];
for(SampleEntity *entity in [list sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort_desc]]) {
[entity.color set];
NSRectFill([entity.frame rectValue]);
}
}



これで表示までが整った。最後に前回同様 NSKeyedArchiver/NSKeyedUnarchiver にカテゴリを追加すれば NSRect(をラップした NSValue)を CoreDataで保存/読み込みができるようになる。


無事にファイルが作成できて、これを読み込むこともできる。


"DUMP"ボタンを押した場合は SampleEntityオブジェクトの配列をコンソールにデバッグ出力する。



なお読み込んだ CoreDataを NSArrayController に反映させるには属性の "Prepares Content" にチェックを入れて置く必要がある。


- - - -
カテゴリの追加で NSRectが CoreDataで扱えるようになった。ただしわざわざ NSKeyedArchiverから構造体サポートを取り除いた(あるいは付けなかった)理由があることから、これはやるべきでは無いかもしれない。正攻法としては ADCのドキュメントで紹介されているような2つの属性を用意する方法がある。

Non-Standard Persistent Attributes

2008年2月26日火曜日

NSRect を CoreDataで扱う(その2)

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

前回紹介したサイトに掲載されていたNSKeyedArchiver にカテゴリを追加する方法を試してみる。


まず CoreDataへ行く前に NSKeyedArchiver/NSKeyedUnarchiver を実装した簡単なサンプルで試してみる。

ソースコード:ArchiveSample.zip



"Archive" ボタンを押すと NSRect {{10, 20} , {100, 200}}がデスクトップ上に archiver.sampleというファイル名で保存される。
"UnArchive" ボタンを押すと archiver.sampleを読み込み、NSRectに戻して画面に表示する。

MyController.m

-(id)init
{
if (self = [super init]) {
rect = [[NSValue valueWithRect:NSMakeRect(10, 20, 100, 200)] retain];
rect_string1 = NSStringFromRect([rect rectValue]);
rect_string2 = @"";
}
return self;
}


インスタンス変数 rect がArchiver対象のNSRect。rect_string1, 2 は画面表示用の文字列でラベルから bindingされている(文字列を変更すると画面に反映される)。

- (IBAction)archive:(id)sender
{
BOOL result = [NSKeyedArchiver archiveRootObject:rect toFile:[self filename]];
}


Archiverボタンが押されたら NSKeyedArchiver#archiveRootObject:toFile: で rectを指定し保存を行う。通常では NSKeyedArchiverは構造体をサポートしていないので下記のエラーが出る。

*** -[NSKeyedArchiver encodeValueOfObjCType:at:]: this archiver cannot encode structs



そこで サイトに紹介されていたコードを拝借してカテゴリで Cの構造体をサポートするコードを追加する。

@interface NSKeyedArchiver (MyKeyedArchiver)
- (void)encodeValueOfObjCType:(const char *)valueType at:(const void *)address;
@end

@implementation NSKeyedArchiver (MyKeyedArchiver)
- (void)encodeValueOfObjCType:(const char *)valueType at:(const void *)address
{
NSMutableData *datas = [NSMutableData data];
NSArchiver *arch = [[NSArchiver alloc] initForWritingWithMutableData:datas];
[arch encodeValueOfObjCType:valueType
at:address];
[self encodeObject:[NSData dataWithData:datas]];
[arch release];
}
@end


するとエラーが出なくなる。

UnKeyedArchiverも同様にカテゴリを追加する。

- - - -
さて次回は CoreData で使ってみる。

2008年2月25日月曜日

NSRect を CoreDataで扱う

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

以前、NSRect などが CoreDataで扱えたら便利と書いたが、NSValue というラッパークラスがあることに気がついた。これが使えないだろうか。

NSValue Class Reference

NSValue の生成メソッド:

– initWithBytes:objCType: 
+ valueWithBytes:objCType:
+ value:withObjCType:
+ valueWithNonretainedObject:
+ valueWithPointer:
+ valueWithPoint:
+ valueWithRange:
+ valueWithRect:
+ valueWithSize:


NSRect以外に NSPointや NSRange などよく使う構造体をオブジェクトとして扱うことができるようになる。


CoreDataのエンティティ属性のデータ型を「変換可能」(Transformable)にすれば、NSRectもCoreDataへ格納できるのでは?

参考:Non-Standard Persistent Attributes

さっそくやってみた。



エンティティのコードを生成させて型を NSValue に書き換えた。

@interface SampleEntity :  NSManagedObject 
{
}

@property (retain) NSValue* frame;    // frameにはNSRectが入る

@end



この値を保存するコードを実行してみる。


エラーが出た。。
*** -[NSKeyedArchiver encodeValueOfObjCType:at:]: this archiver cannot encode structs


NSKeyedArchiverでは構造体をエンコードできないとのこと。

Transformable Attributes によれば「変換可能」の場合の NSValueTransformer はデフォルトで NSKeyedUnarchiveFromDataTransformerName タイプが使われる。この場合、NSKeyedArchiverによってエンコードされたデータがファイルへ書き込まれることになる。このエンコード過程でエラーが発生したようだ。


Googleで探してみると同様の問題を取り上げたページがいくつかあった。

Re: coding struct with keyed archivers

Re: NSKeyedArchiver and NSValue

CocoaDev: EncodingNSRectAndNSPointInAKeyedArchiver

Archiver une NSValue avec NSKeyedArchiver
NSKeyedArchiverにカテゴリでCデータをアーカイブ/アンアーカイブするメソッドを実装する例が載っていた。


- - - -
簡単にはできそうもないな。

2008年2月24日日曜日

スクラップブックその12 - 画像の拡大縮小

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

画像を拡大縮小できるようにしてみる。



ソースコード:sp4-12.zip


拡大縮小はよくある四方に□をつけて行うやつではなく、ウィンドウのように右下だけで行うようにする。
<=このアイコンを画像の右下に表示する。この画像は OSについていた resize.gifを拝借している。

リサイズアイコンは画像が選択された時だけ表示して機能するようにする。こんな感じ。




機能実装のポイントは次のとおり。
 (1) リサイズアイコンの表示
 (2) リサイズアイコンドラッグ時の処理(拡大縮小)


(1) リサイズアイコンの表示
以前、導入した ItemIcon クラスを改良して使うことにする。初期化時に画像の4角(左上、右上、左下、右下)のどこへ表示するか指定し、位置決めの為に以前の setOrigin: をやめて、紐づける画像のサイズを渡す setBaseRect: に変更した。
その後、WorkViewに新たに ItemIconのインスタンス変数を追加し、表示処理を加えた。

(2) リサイズアイコンドラッグ時の処理(拡大縮小)
mouseDown: の中でリサイズアイコンのヒットテストを行い、押されているようなら新設のメソッド resizeIcon: を呼ぶ。

WorkView.m

-(void)resizeItem:(Item *)item
{
NSPoint current_point;
NSEvent* theEvent;

while (1) {
theEvent = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
current_point = [self convertPoint:[theEvent locationInWindow]
fromView:nil];

[self autoscroll:theEvent];

[item setSize:NSMakeSize(current_point.x - [item.x floatValue],
current_point.y - [item.y floatValue])];
[_icon setBaseRect:[item rect]];
[_resize_icon setBaseRect:[item rect]];
[self setNeedsDisplay:YES];
if ([theEvent type] == NSLeftMouseUp) {
break;
}
}
}


ここでイベントループを作り、マウスボタンが離されるまで画像サイズの拡大縮小計算を行う。ドラッグの時にやっていた移動量のチェックは今回は行っていない。

画像をビューへ描画するのに以前 compositeToPoint:operation: を使っていたが、これでは拡大縮小表示ができないので別のメソッド NSView#drawInRect:fromRect:operation:fraction: を使う。

fromRect: に NSZeroRectを指定すると画像のサイズが使われ、これが drawInRect: で指定した領域へ写像される(拡大縮小が行われる)。



忘れずに dragImage: に渡す画像作成も compsiteToPoint: から drawInRect: へ切り替えておく。


なお compsiteToPoint: を drawInRect: へ切り替えただけだと画像が上下反転して表示されてしまう。



これは drawInRect: の場合はビューの flip状態を見て描画する為。スクラップブックでは描画先のビューである WorkView の isFlipped で YESを返すようにしている。この為、drawInRect: を使うと図のように上下反転してしまう。そこで画像も反転表示するように setFlipped: を使い flip属性を変えてしまう。最終的な WorkView#drawRect: は次のようになる。

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

for (Item* item in [_controller items]) {
[item.image setFlipped:YES];
[item.image drawInRect:[item rect]
fromRect:NSZeroRect operation:NSCompositeSourceOver
fraction:1.0f];
[item.image setFlipped:NO];

if (_selected_item == item) {
// draw selection
[[[NSColor grayColor] colorWithAlphaComponent:0.3f] set];
[NSBezierPath fillRect:[item rect]];
[_icon draw];
[_resize_icon draw];
}
}
}


これで画像が正しく表示されるようになった。

- - - -
選択すると灰色になるがちょっと見づらい。枠に戻すか、別の表現の方がよさそう。

2008年2月23日土曜日

スクラップブックその11 - アイコン表示の変更ほか

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

マウスオーバーでアイコンの表示・非表示を制御するとちらちらして少々うっとおしい。そこで選択された時のみアイコンを表示するようにする。

mouseMoved: の中でマウス下の画像をチェックしていたがそれが不要になった。画像が選択されている (_selected_item != nil)の時だけマウスの位置をチェックするようにする。

WorkView.m

- (void)mouseMoved:(NSEvent *)theEvent
{
if (_selected_item == nil) {
return;
}

NSPoint cp = [self convertPoint:[theEvent locationInWindow] fromView:nil];

if ([_icon inRectAtPoint:cp]) {
[_icon setState:2];
} else {
[_icon setState:1];
}

[self setNeedsDisplayInRect:[_icon frame]];
[self setNeedsDisplay:YES];
}


アイコンの上にマウスがあるかを ItemIcon#inRectAtPoint で判断するだけの処理となる。すっきりした。

これに合わせて mouseDown: の処理も少し書き換える。具体的には、(1)画像選択時にアイコンを左上に表示する (2)画像選択時にアイコンを消す (3)画像をドラッグ中にアイコンの位置も合わせて移動させる。


ソースコード:sp4-11.zip



- - - -

WorkView.m の initWithFrame: 内にあった ItemIconの作成を今回 awakeFromNibへ移した。XCode上の実行では問題なかったが、アプリケーションとしてビルドし、別のマシンで実行したところアイコン表示が出ていないことに気がついたので。

2008年2月22日金曜日

スクラップブックその10 - 画像をファインダへドラッグ&ドロップする

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

スクラップブック上の画像をファインダへドラッグ&ドロップできるようにする。こんな感じ↓



をつかみファインダ上でドロップするとその画像のファイルがコピーされる。

ソースコード:sp4-10.zip


他アプリへのドラッグ&ドロップは NSView#dragImage:at:offset:event:pasteboard:source:slideBack: を使えば容易に実現できる。方法は以前の投稿で紹介した(「Finderへドロップ」)。

実装はまず最初にアイコンが押された(ドラッグに入った)かどうかの判定から入る。

WorkView.m

- (void)mouseDown:(NSEvent *)theEvent
{
   :
   :
if ([_icon inRectAtPoint:start_point]) {
   // 他アプリへのドラッグ&ドロップ処理
  }
   :
  // 通常の画像のドラッグ処理
}


アイコンが押されたらペースとボードとドラッグ用の画像を用意して dragImage:... を呼出す。
まず新規に NSImage を作成し、これにコピー元となる画像を半透明で描画する。

  NSImage *dragged_image = [[[NSImage alloc] initWithSize:[item image].size] autorelease];
[dragged_image lockFocus];
[[item image] compositeToPoint:NSZeroPoint
operation:NSCompositeSourceOver
fraction:0.8f];
[dragged_image unlockFocus];


続いて NSPasteboardの準備。NSFilesPromisePboardType を登録しておく。登録しておくとドロップ時に namesOfPromisedFilesDroppedAtDestination: がコールバックされる。

  NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
[pboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
[pboard setPropertyList:[NSArray arrayWithObject:[item.filename pathExtension]]
forType:NSFilesPromisePboardType];


最後に drawImage:... を呼出す。これでドラッグ操作が開始される。

  [self dragImage:dragged_image
at:NSMakePoint([item.x floatValue], [item.y floatValue]+[item.height floatValue])
offset:NSZeroSize
event:theEvent
pasteboard:pboard
source:self
slideBack:YES];



ペーストボードには NSFilesPromisePboardType が登録されているので、ファインダへドロップした時に namesOfPromisedFilesDroppedAtDestination: が呼出される。ここで画像のコピー処理を書いてやれば良い。

- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
{
id dst_path = [[dropDestination relativePath] stringByAppendingPathComponent:_dragged_filename];
id src_path = [[_controller pathToSave] stringByAppendingPathComponent:_dragged_filename];

[[NSFileManager defaultManager] copyPath:src_path
toPath:dst_path
handler:nil];
return [NSArray arrayWithObject:_dragged_filename];
}


ドラッグしている画像のファイル名はこのメソッドだけでは得られないので、ドラック開始前にあらかじめメンバ変数 _dragged_filename へ取っておく。



なおこのままだと自分自身へもドロップができてしまう。この場合、実体の画像ファイルが1つなのに画面上は2つの同じ画像が表示される状態になる。これはこれで使い道もあるのだが、現状では削除した場合に矛盾が生じてしまうので禁止することにする。draggingEntered: と performDragOperation: に自分自身へのドロップかどうかのチェックを入れておく。

- (NSDragOperation)draggingEntered:(id )sender
{
if (self == [sender draggingSource]) {
return NSDragOperationMove;
}
return NSDragOperationCopy;
}

- (BOOL)performDragOperation:(id )sender {

if (self == [sender draggingSource]) {
return NO;
}
 :


NSDraggingInfo#draggingSource にはドラッグ元のオブジェクト(id)が入る(ドラッグ元が他のアプリケーションの場合は nilとなっていた)。



ここまでで簡単なスクラップブックの機能が実装できた。振り返って見ると改めて cocoaの強力さを感じた。ところどころ良くできているのに感心し、それとともに流れが分かってくると結構楽しかった。
なお、検証目的でとにかく早く作っていったので、メモリ確保の後始末(release)や描画の効率化、エラー処理の実装などは手を抜いてきた。きちんと作るとこの辺りで結局コード量は膨らむと思う。


- - - -
スクラップブックはこの後も検証目的ですこしずつ改良を加えていくつもりだが、そろそろちゃんとしたアプリの制作にとりかかろうと思う。最終目的はスクラップブックに近いものだがその前に小さなツールをいくつか作ってみたい。今後はそのあたりの開発過程もこのブログで紹介していきたいと思う。

2008年2月21日木曜日

スクラップブックその9 - 画像上にアイコンを表示する

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

マウスカーソルが画像の上に来たら、左上にアイコンを表示するようにする。



ソースコード:sp4-9.zip

このアイコンは他のアプリケーション(ファインダなど)へドラッグ&ドロップして画像をコピーする時につかむ位置を表している。ドラッグ処理は次回へ取っておいて、今回は表示する処理を追加する。


アイコンを管理する為に新しいクラス ItemIcon を追加した。

ItemIcon.h

@interface ItemIcon : NSObject {

NSImage *_image;
int _state;
NSRect _frame;
}
- (id)initWithImage:(NSImage*)image;
- (void)setOrigin:(NSPoint)point;
- (BOOL)inRectAtPoint:(NSPoint)point;
- (void)setState:(int)state; // 0=off/1=on/2=pushed
- (void)draw;
- (NSRect)frame;


ItemIconはアイコン画像と表示位置を保持する。これらの情報を使いマウスポインタがアイコン上にあるかどうかのチェックや、アイコン画像の描画処理を行う。3種類の状態(0=無効、1=画像上にマウスあり、2=アイコン上にマウスあり)を持ち、マウスの位置によって表示の有無や、透明度を変えている。


WorkView はこのインスタンスを変数として持ち、マウスポインタの位置などによって状態を制御する。

WorkView
- (void)mouseMoved:(NSEvent *)theEvent
{
   :
   :
[_icon setOrigin:[_mouseon_item rect].origin];
if ([_icon inRectAtPoint:cp]) {
[_icon setState:2];    // 2: アイコンの上にマウスがある
} else if (_mouseon_item) {
[_icon setState:1];    // 1: 画像の上にマウスがある(しかしアイコンの上には無い)
} else {
[_icon setState:0];    // 0: 画像の上にマウスが無い
}

[self setNeedsDisplayInRect:[_mouseon_item rect]];
[self setNeedsDisplay:YES];
}


マウス移動のイベントハンドラ(mouseMoved:)内でマウスポインタをチェックし、状態を決めている。そして drawRect: 内で描画する。これは ItemIcon#draw を呼び出すだけで良い。


アイコン画像はバンドル内に配置し、アプリ起動時に MyController がこれを読み込み NSImageクラスのインスタンスとして作成する。

MyController.m
- (void)awakeFromNib
{
   :
NSMutableDictionary* dict = [NSMutableDictionary dictionary];
NSArray* paths = [[NSBundle mainBundle] pathsForResourcesOfType:@"png"
inDirectory:nil];
for (NSString* file in paths) {
NSImage *image = [[[NSImage alloc] initWithContentsOfFile:file] autorelease];
[dict setObject:image forKey:[[file lastPathComponent] stringByDeletingPathExtension]];
}
_icons = [dict retain];
}


NSDictionary へ名前付きで入れておき、WorkViewが ItemIconを作成する時に取り出して使う。

- - - -

次回はこのアイコンをドラッグして別アプリ(ファインダなど)へコピーする処理を追加する。

2008年2月20日水曜日

スクラップブックその8 - マウスカーソル下の画像を反転させる

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

マウストラッキングの仕組みが分かったのでスクラップブックで使ってみる。まずはマウスカーソルが画像の上に乗ったら色を変える処理を加えてみよう。こんな感じ。



画像の上にマウスカーソルが乗ると赤くなる。

サンプル:sp4-8.zip


マウストラッキングには NSTrackingArea を使う。

WorkView.m

  _tracking_area = [[NSTrackingArea alloc]
initWithRect:[self bounds]
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved |NSTrackingActiveInKeyWindow | NSTrackingInVisibleRect| NSTrackingEnabledDuringMouseDrag)
owner:self
userInfo:nil];
[self addTrackingArea:_tracking_area];


NSScrollViewを使う場合は、トラッキングオプションに NSTrackingInVisibleRect を加えてやるとスクロールバーがトラッキング領域から外れるので便利。

これで mouseMoved: メッセージが送られてくるようになるので、マウスカーソル下に画像があるかチェックする。
- (void)mouseMoved:(NSEvent *)theEvent
{
NSPoint cp = [self convertPoint:[theEvent locationInWindow] fromView:nil];
_mouseon_item = nil;
for (Item* item_test in [[_controller items] reverseObjectEnumerator]) {
if (NSPointInRect(cp, [item_test rect])) { // ヒットテスト
_mouseon_item = item_test;
break;
}
}
if (_mouseon_item) {
[self setNeedsDisplayInRect:[_mouseon_item rect]];
}
[self setNeedsDisplay:YES];
}


画像があったら _mouseon_item へ入れて再描画する。

- (void)drawRect:(NSRect)rect {
 :
if (_mouseon_item == item) {
[[[NSColor redColor] colorWithAlphaComponent:0.3f] set];
[NSBezierPath fillRect:[item rect]];
}


これでマウスカーソル下の画像が赤くなる。

画像の数が多くなると上記のヒットテストは無駄が多い。mouseMoved:が呼ばれる頻度を考えるともっと負荷をかけない方法が必要。

- - - -

仕組みができたので次はこれを利用して画像にアイコンを貼付ける。

2008年2月19日火曜日

Xcode - リサーチアシスタント

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

XCodeに「リサーチアシスタント」なるものが追加されていた。



コード中、メソッドなどを選択するとそれに関連する情報が表示されるようになる。出し方はメニュー「ヘルプ > リサーチアシスタントを表示」を使う。


関連APIや関連ドキュメントの表示は役立ちそう。


なお「抽象」は「要約」(abstract)の誤訳では?英語版を見ていないのでわからないが。

2008年2月18日月曜日

NSTrackingArea

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

前回に引き続きマウスとラッキングの検証。

MacOSX10.5より NSTrackingArea が追加された。解説が ADCのドキュメントにある。

Using Tracking-Area Objects


早速サンプルを作ってみた。
NSTrackingAreaSample.zip


サンプルの動作は前回同様、ビューの領域にマウスカーソルが入ると色が変わるというもの。

さて以前のトラッキングとどう変わったかというと...
・トラッキングに関する情報が1つのクラスにまとめられた
・トラッキングオプションで受け取るイベントや振る舞いを設定することができるようになった
・トラッキング登録のメソッドが addTrackingRect:owner:userData:assumeInside: から addTrackingArea: に変更された。登録のタイミングが緩和され initWithFrame: でも良くなった。
・ビューの大きさが変更になると updateTrackingAreas が送られるようになった。これを使い、トラッキング領域の再設定ができる。

トラッキングオプションはリファレンスマニュアルに説明がある。
NSTrackingArea Class Reference

登録部分のコード例を示す。

- (id)initWithFrame:(NSRect)frame {
self = [super initWithFrame:frame];
if (self) {
_mouse_status = 0;

_tracking_area = [[NSTrackingArea alloc] initWithRect:[self bounds]
options:(NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveInKeyWindow | NSTrackingEnabledDuringMouseDrag )
owner:self
userInfo:nil];
[self addTrackingArea:_tracking_area];
}
return self;
}


これだけで mouseEntered:, mouseMoved:, mouseExited が送られてくるようになる。これまでの方法と違って mouseEntered: と mouseExited: の中で setAcceptsMouseMovedEvents: を呼ぶ必要もなく、登録タイミングも initWithFrame: 内で良いのでわかりやすい。トラッキング情報を1つのクラスにまとめることでトラッキングに関する個所がシンプルになった。悪くない。

なおビューを NSScrollView の中で使う場合はオプションに NSTrackingInVisibleRect を指定すると表示領域のみがトラッキング対象となるので使いやすい。

2008年2月17日日曜日

マウス移動のトラッキング

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

NSViewではマウス移動をトラッキングすることができる。トラッキングを使えば画像の上にマウスカーソルを置いた時に反転表示したり枠線を付けたりすることができる。

トラッキングの動きを見るサンプルを作ってみた。
MouseTrackingSample.zip



マウスをWindow内の灰色の部分へ移動させると白色に反転する。




トラッキングを開始すると次のメッセージを受け取ることができるようになる。
mouseEntered:
mouseMoved:
mouseExited:

ただしデフォルトではこれらのメッセージが飛ぶことはなく、いくつかの準備が必要。やり方は ADCのドキュメントに書いてある。
Handling Mouse-Tracking Events


(1) mouseEnteredとmouseExited
まずトラッキングイベントの受け取り登録を NSView#addTrackingRect:owner:userData:assumeInside: を使って行う。ドキュメントによれば initWithFrame: ではなく、viewDidMoveToWindow: 内で行うのが良いとのこと。

- (void)viewDidMoveToWindow {
tag = [self addTrackingRect:[self bounds] owner:self userData:NULL assumeInside:NO];
}


これで mouseEntered: と mouseExited: がこのビューへ送られるようになる。

(2) mouseMoved:
mouseMoved: メッセージを受け取るにはこれらに加えてさらに親ウィンドウに setAcceptsMouseMovedEvents:YES を送る必要がある。デフォルトではイベント量が多く、余計な負荷となってしまう為にこの設定が NOになっている。mouseEntered: でマウスがトラッキング領域に入ったところでこのメッセージを送る。すると mouseMoved: メッセージが送られてくるようになる。そのままだとメッセージが送られ続けるのでマウスカーソルが外に出た時点、つまり mouseExited: が送られた時点で NOを設定する。

その他、わかったこと:

  1. ビューがファーストレスポンダでないと mouseMoved:が送られない。mouseEntered: の中で [[self window] makeFirstResponder:self]; としてやる。

  2. moueMoved: はマウスを押している(ドラッグしている)間は発生しない。これは ADCドキュメントにも書かれている。


  3. なおビューの大きさが変わった場合はこれに合わせてトラッキング領域も変更する必要がある。setFrame: と setBounds: にトラッキング領域を再設定するコードを書いておく。

    - (void)setFrame:(NSRect)frame
    {
    [super setFrame:frame];
    [self removeTrackingRect:tag];
    tag = [self addTrackingRect:[self bounds] owner:self userData:NULL assumeInside:NO];
    }


    - - - -
    MacOSX 10.5からは NSTrackingAreaが新しく導入されている。
    Using Tracking-Area Objects
    この検証はまた後日。

2008年2月16日土曜日

HUDを探して...

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

HUD ( Heads Up Display )とは Appleのアプリケーションでよく使われている黒い半透明のパネルのこと。
Interface Builder 3.0でも使われているこんなやつ。



iPhoto の例



Interface Builder3.0から HUD Window というオブジェクトが追加されている。




実体は NSPanel で Style に新たに追加された NSHUDWindowMask を指定する。Interface Builderでは HUDにチェックが入っている。




これを使うとこんな感じになる。




ただ難点があって、その上に載せるボタンなどのコントロールが Aqua(もしくはメタル、テクスチャ)表現のみしかない。この為、Appleのソフトと違ってちょっと違和感のある表示になってしまう。




ボタンなどのコントロール類を HUD表示に替える方法を探したが、残念ながら見つからなかった。他にも同様に探している人もいるようだが、どうも Appleの方から(今のところは)公式に提供はされていない様子だった。

関連リンク:
HUD Controls in Leopard

Re: HUD controls



そんな中、何人かの人が独自にコントロールを作ったりしている。

(1) iLife Controls; HUD windows and more

提供されていたサンプルをビルドして実行した結果が下のウィンドウ。これはなかなか良い。



コントロールはパスで描画しているのではなく TIFF画像であらかじめ用意したものを表示しているようだ。


利用ライセンスは不明。

日本語での紹介ページ:
iLifeControls.frameworkの使い方(きりかリポーツ)


(2) HMBlkAppKit

HMDTで提供されているフレームワーク。修正BSDライセンスでライセンス表記等があれば自由に利用できる。元々はシイラプロジェクトで作られたもののようだ。


- - - -
せっかく HUD Window が提供されているのに、それに合うコントロールが提供されていないのは残念。
しかし HUDは Appleの Human Interface Design の Guideline的にはどうなのだろうか。個人的には好きなデザインではあるが。

2008年2月15日金曜日

IKSlideshow

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

MacOSX 10.5 から手軽にスライドショーができる IKSlideshowが加わった。

IKSlideshow Class Reference

このクラスを使うとおなじみのスライドショーが数行のコードで作れてしまう。







さっそくサンプルを作ってみた。



サンプル:IKSSample.zip

ボタンを押すと壁紙(/Library/Desktop Pictures/*.jpg)のスライドショーが始まる。

書いたコードは MyControllerクラスのみ。

MyController.h

#import 
#import

@interface MyController : NSObject {

NSMutableArray *_image_list;
}
-(IBAction)start:(id)sender;

@end


MyController.m
@implementation MyController

-(id)init .....(1)
{
self = [super init];

NSString* path = @"/Library/Desktop Pictures";
NSArray* array = [[NSFileManager defaultManager] directoryContentsAtPath:path];

_image_list = [[NSMutableArray alloc] init];

for (NSString* name in array) {
if ([[name pathExtension] isEqualToString:@"jpg"]) {
[_image_list addObject:[path stringByAppendingPathComponent:name]];
}
}

return self;
}

-(IBAction)start:(id)sender ...(3)
{
[[IKSlideshow sharedSlideshow]
runSlideshowWithDataSource:(id)self
inMode:IKSlideshowModeImages
options:nil];
}
- (NSUInteger)numberOfSlideshowItems ...(2a)
{
return [_image_list count];
}

- (id)slideshowItemAtIndex: (NSUInteger)index ....(2b)
{
return [_image_list objectAtIndex:index];
}

@end


(1) インスタンス初期化時(init)に /Library/Desktop Pictures/*.jpg のパス一覧を作り _image_listへ格納しておく。
(2a)(2b) IKSlideshowはスライドショーで表示する内容をデータソースから取得する。データソースは IKSlideshowDataSourceプロトコルで定義されている。画像数を返す numberOfSlideshowItems と 画像データ(ファイル名)を返す slideshowItemAtIndex: の2つを最低限実装する必要がある。
(3) ボタンが押されたら IKSlideshow のインスタンスへ メッセージ runSlideshowWithDataSource:inMode:options: を送る。するとスライドショーが始まる。

コードはこれだけ。データソースで提供するデータはファイル名でも良いし、NSImageでも良い。

IKSlideshow.h 抜粋
// export an item to the given application
// The item can be:
// - NSImage *
// - NSString * (path)
// - NSURL *
// - an NSArray of NSImage / NSString / NSURL
+ (void) exportSlideshowItem: (id) item
toApplication: (NSString *) applicationBundleIdentifier;
@end




ImageKitは他にもいろいろと面白いクラスがある。今回の IKSlideshowも含めてプログラミングガイドが ADCで出ている。
Introduction to Image Kit Programming Guide

ADCのサンプルプログラム
IKSlideshowDemo

木下さんによる紹介もあった。
LeopardのImage KitでiPhotoを作る--一手間加えてiPDF?も作っちゃおう


- - - -
これだけ簡単に利用できると画像や PDFを使うアプリでは今後ついてて当たり前の機能になるな。

2008年2月14日木曜日

スクラップブックその7 - 選択の表現を替える

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

画像が選択されていた時には枠線を表示していたが、グレーの半透明色で塗りつぶすように変更した。



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]+[item.height floatValue])
operation:NSCompositeSourceOver];

if (_selected_item == item) {
// draw selection
[[[NSColor grayColor] colorWithAlphaComponent:0.3f] set];
[NSBezierPath fillRect:[item rect]];
}
}
}

画像を描画した上に半透明色で上書きする。NSColor の colorWithAlphaComponentを使うと半透明色で描画ができる。


それと画像以外の個所をクリックした時に選択を外すようにした。現状ではメンバ変数 _selected_iem = nil だけで良い。

2008年2月13日水曜日

スクラップブックその6 - サイズ固定と自動スクロール

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

前回のスクラップブックでドラッグにあわせて Viewのサイズを変えたが、使いづらいのでサイズを固定することにした。
mouseDown: の中で呼出していた resizeWithRect: をコメントアウトし、InterfaceBuilderで WorkViewのサイズを(1000,1000)、Windowの最大サイズを (1000,1000)にした。




Viewが広くなったのでスクロールしていろいろな場所に画像を配置することができる。どうせなら画像をドラッグして画面の端へ移動させた時に自動的にスクロールするようにしたい。



Viewにはこれを簡単に行うことのできる便利なメソッドが用意されている。

[NSView autoscroll:]

このメッセージを NSViewへ送ると、外側の NSScrollView がこれを適切に処理してくれる。

Supporting Automatic Scrolling

ドラッグ処理に1行加える。

WorkView.m
- (void)mouseDown:(NSEvent *)theEvent
  :
while (1) {
theEvent = [[self window] nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
current_point = [self convertPoint:[theEvent locationInWindow]
fromView:nil];

[self autoscroll:theEvent];  // <--追加
  :




Cocoaは良くできている...感心。

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];
}
}
}


こんな感じ。