ページ

2008年1月27日日曜日

NSCollectionView

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

ADCにIconCollectionというサンプルが上がっていた。



10.5で追加されたNSCollectionViewを使いアイコンを表示するというもの。データの準備箇所を除くとコーディング量はかなり少なく、ほとんどがバインディングを使って InterfaceBuilderだけで組み立てられている。アイコンの並び順を替えるとアニメーションまでする。

面白かったので自分でも試してみる。IconCollectionでは NSViewControllerなどを使っているが、試すだけならもっとシンプルにできるはず。9枚のTIFF画像を用意してやってみた。

こんな感じ。


リサイズすると個々の画像がアニメーションして移動するのが見える。"SORT"ボタンで並び替えてもアニメーションが起る。これらは全部自動的に行われる。

NSCollectionView とバインディングが理解できれば簡単にコレクション表示ができる。これはいい。

コーディングは1つのクラスを実装しただけ。

MyController.h


@interface MyController : NSObject {

NSMutableArray *images;
BOOL sort_accending;

IBOutlet NSArrayController *array_controller;
}
- (IBAction)sort:(id)sender;
@end


MyController.m

@implementation MyController

-(id)init
{
if (self = [super init]) {
sort_accending = YES;
images = [[NSMutableArray alloc] init];
for (NSString* path in [[NSBundle mainBundle] pathsForResourcesOfType:@"tiff" inDirectory:nil]) {
NSImage *image = [[[NSImage alloc] initByReferencingFile:path] autorelease];
[images addObject:
[NSMutableDictionary dictionaryWithObjectsAndKeys:image, @"image", [path lastPathComponent], @"name", nil]];
}
}
return self;
}

- (IBAction)sort:(id)sender
{
NSSortDescriptor *sort = [[[NSSortDescriptor alloc]
initWithKey:@"name"
ascending:(sort_accending = !sort_accending)] autorelease];
[array_controller setSortDescriptors:[NSArray arrayWithObject:sort]];
}
@end


init 内の記述は実質データの用意だけ。表示に関してはすべて InterfaceBuilderだけで済む。

その分 InterfaceBuilderの方は少し複雑になる。慣れればどうってことないのだが。




ツールパレットから NScollectionViewをドラッグ&ドロップすると NSCollectionViewItem と NSViewが一緒に作られる。関係は次のような感じ。



右側のView Itemがコレクションの要素1個分に当たり、ここで表示内容を決めてやる。NSCollecionViewItemは、これとNSCollectionViewとの間に入り、コントローラのような役割を果たす。図のように適切にアウトレットとバインディングを設定すると動き出す。分かってしまうと実に簡単。コーディングなしでここまでできるとは楽だ。


サンプル
study01.zip

2008年1月26日土曜日

FireFoxから画像をドラッグ&ドロップして保存する(その2)

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

> FireFoxの場合、ファイル名を取得するには別途 URLタイプのデータを
> Pasteboardから取り出す必要がある(サンプルは取り出している)。

以前の投稿で上記のように書いたが
FireFoxからリンク付きの画像をドロップした場合、"public.url"はリンクのURLが入り画像のファイル名が取れない。

他の flavor typeも試したがまるでダメ。
困っていたところ PasteboardCopyItemFlavorData(..,kPasteboardTypeFileURLPromise,..)の戻り値が -25133 (item flavor does not exist)になったことを思い出した。

もしかして呼び出し時点ではファイルが存在しないから "itm flavor does not exist"になっているのかもしれない。
そう思って、もう一度呼び出すようにした。そうしたら(ローカルに保存された)ファイル名が取得できた。

// 1回目: これでファイルが作成されるが、err == badPasteboardFlavorErr で、flavor_dataからは有効なデータが得られない
err = PasteboardCopyItemFlavorData(pboard, item_id, kPasteboardTypeFileURLPromise, &flavor_data);

// 2回目:flavor_dataにローカル保存されたファイル名が入る。
// 例)file://localhost/Users/hashi/Desktop/im20080121AS1D210BE2101200814.jpg
err = PasteboardCopyItemFlavorData(pboard, item_id, kPasteboardTypeFileURLPromise, &flavor_data);

試行錯誤にずいぶん時間がかかってしまったが分かってみるとこれもあっけなかった。
やれやれ。

次は簡単なスクラップブックを作ってみよう。

2008年1月17日木曜日

Finderへドロップ

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

今度は立場を替えて View上の画像を Finderへドロップしてファイルを作成してみる。



ペーストボードのタイプは NSFilesPromisePboardType を使う。


- (void)mouseDown:(NSEvent *)theEvent {

NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
[pboard declareTypes:[NSArray arrayWithObject:NSFilesPromisePboardType] owner:self];
[pboard setPropertyList:[NSArray arrayWithObject:@"jpg"] forType:NSFilesPromisePboardType];
NSImage *image = [self image];
[image setSize:[self frame].size];
[self dragImage:[self image] at:NSMakePoint(0, 0) offset:NSMakeSize(0, 0)
event:theEvent pasteboard:pboard source:self slideBack:YES];

return;
}


すると画像をドロップしたタイミングで Finderからファイルの書き出し指示がくる。
これが namesOfPromisedFilesDroppedAtDestination:


- (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
{
id dst_path = [[dropDestination relativePath] stringByAppendingPathComponent:@"ocha.jpg"];
id bundle = [NSBundle mainBundle];
id src_path = [bundle pathForResource:@"ocha" ofType:@"jpg"];
NSLog(@"src=%@, dst=%@", src_path, dst_path);
BOOL result = [[NSFileManager defaultManager] copyPath:src_path toPath:dst_path handler:nil];
NSLog(@"rsult=%d", result);
return [NSArray arrayWithObject:@"ocha.jpg"];
}


今回はバンドル内のファイルを指定されたパスへコピーしている。
ファイルがない場合は指定のパスへ直接書き出せば良い。

2008年1月14日月曜日

FireFoxから画像をドラッグ&ドロップして保存する

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

前回 NSDraggingInfo の namesOfPromisedFilesDroppedAtDestination:dropLocation を使いSafariで画像を保存することができたが、FireFoxではできなかった。いろいろ調べた結果、Pasteboard Managerを使えばできることがわかった。

サンプル
sp1.zip

方法は Pasteboard.h に記述されていた。


/* From: Pasteboard.h - "Pasteboard File Promising"
*
* Order of operations on copy or drag
*
* 1) The sender promises kPasteboardTypeFileURLPromise for a file yet to be created.
* 2) The sender adds kPasteboardTypeFilePromiseContent containing the UTI describing
* the file's content.
*
* Order of operations on paste or drop
*
* 3) The receiver asks for kPasteboardTypeFilePromiseContent to decide if it wants the file.
* 4) The receiver sets the paste location with PasteboardSetPasteLocation.
* 5) The receiver asks for kPasteboardTypeFileURLPromise.
* 6) The sender's promise callback for kPasteboardTypeFileURLPromise is called.
* 7) The sender uses PasteboardCopyPasteLocation to retrieve the paste location, creates the file
* and keeps its kPasteboardTypeFileURLPromise promise.
*/


受取側で 3), 4) 5) を行えば Firefoxからドラッグした画像ファイルを任意のフォルダに作成することができる。
コードはこんな感じ(ポイントのみ抜粋)。


// 3) The receiver asks for kPasteboardTypeFilePromiseContent to decide if it wants the file.
PasteboardCopyItemFlavorData(pboard, item_id, kPasteboardTypeFilePromiseContent, &flavor_data);

// 4) The receiver sets the paste location with PasteboardSetPasteLocation.
err = PasteboardSetPasteLocation(pboard, url);

// 5) The receiver asks for kPasteboardTypeFileURLPromise.
PasteboardCopyItemFlavorData(pboard, item_id, kPasteboardTypeFileURLPromise, &flavor_data);


最初に kPasteboardTypeFilePromiseContent を要求して(3)、次にファイルの保存場所を PasteboardSetPasteLocationで指定する。そして最後に kPasteboardTypeFileURLPromiseを要求するとファイルが作成される。


FireFoxの場合、5)の結果は badPasteboardFlavorErr(-25133:item flavor does not exist)が返る。一方 Safariでは保存ファイルのURLが flavor_dataに返る。


(FireFoxの場合の実行ログ)
2008-01-14 18:14:00.578 sp1[29834:10b] PasteboardCopyItemFlavorData: err=0
2008-01-14 18:14:00.578 sp1[29834:10b] flavor_data=<64796e2e>
2008-01-14 18:14:00.579 sp1[29834:10b] PasteboardSetPasteLocation: err=0
2008-01-14 18:14:00.623 sp1[29834:10b] PasteboardCopyItemFlavorData: err=-25133

(Safariの場合の実行ログ)
2008-01-14 18:00:56.273 sp1[29795:10b] PasteboardCopyItemFlavorData: err=0
2008-01-14 18:00:56.274 sp1[29795:10b] flavor_data=<7075626c>
2008-01-14 18:00:56.274 sp1[29795:10b] PasteboardSetPasteLocation: err=0
2008-01-14 18:00:56.277 sp1[29795:10b] PasteboardCopyItemFlavorData: err=0
2008-01-14 18:00:56.279 sp1[29795:10b] flavor_data=<66696c65>......



3)で得られる flavor_dataも上記のように FireFoxと Safariで異なる。FireFoxは "dyn.ah62d4rv4gk8u" という文字列でこれは動的UTIを表す。一方 Safariは "public.jpeg"(これも UTI)を返す。

FireFoxの場合、ファイル名を取得するには別途 URLタイプのデータを Pasteboardから取り出す必要がある(サンプルは取り出している)。

- - - -
Finder が実現できているので何か方法はあるかと思ったが調べてもなかなか情報が集まらない。
その上、Cocoaの情報は見つからず Carbonや Drag Manager、Scrap Manager などがでてきて、一時は無理かと思った。
さんざん時間がかかったあげく最終的にはヒントが身近なところ(Pasteboard.h)にあって方法もあっけなかった。やれやれ。
まあとりあえず良かった。


(付録)16進数文字列を文字に変換する
ruby のpackを使うと簡単にできる。

例:
p ["7075626c69632e6a706567"].pack("H*")
=> "public.jpeg"

2008年1月5日土曜日

Safariから画像をドラッグ&ドロップして保存する

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

Safariからデスクトップ(Finder)へ画像をドラッグ&ドロップすると元々の画像形式・ファイル名でファイルが作成される。



これと同じことがやりたい。前回のペーストボードのタイプで NSFilesPromisePboardType を指定していたのを思い出して調べてみた。
Dragging File Promises

これによると NSDraggingInfo の namesOfPromisedFilesDroppedAtDestination:dropLocation を使うとドラッグソース(今回の場合 Safari)に dropLocationで指すフォルダへファイルを書き出させることができるらしい。早速試してみたところうまくいった。

サンプル DandD-2.zip



Safariからアプリへ画像をドラッグ&ドロップするとデスクトップに画像ファイルが作成される。
おお。これは楽だ。

コードはこんな感じ。


- (BOOL)performDragOperation:(id )sender {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDesktopDirectory, NSUserDomainMask, YES);
NSURL *dropLocation = [NSURL URLWithString:[paths objectAtIndex:0]];
NSArray *filenames = [sender namesOfPromisedFilesDroppedAtDestination:dropLocation];
NSLog(@"%@", filenames);
return YES;
}




調子にのって FireFoxから画像をドラッグ&ドロップしてみるとエラーで怒られた。

2008-01-05 22:06:57.845 DandD[18035:10b] Couldn't get a copy of an HFS Promise from the pasteboard
2008-01-05 22:06:57.849 DandD[18035:10b] Couldn't get a copy of the FSSP data from the pasteboard
2008-01-05 22:06:57.849 DandD[18035:10b] Looked for HFSPromises on the pasteboard, but found none.


ふむ。FireFoxの場合はどうすれば良いのだろうか?

ドラッグ&ドロップのデータタイプ

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

NSPersistentDocumentを使いパッケージが作成できることが分かった。次にパッケージ内に画像ファイルを保存する。Safariなど他のソフトからドラッグ&ドロップされた画像ファイルを取り扱うことを考えているので、できれば元々の画像形式のままパッケージ内にファイルを格納したい。どういう形式で受け取って、どういう方法で保存するか。まずパッケージ格納へいく前にまずは他のソフトからドラッグ&ドロップで受け取れるデータ形式を調べることにする。

簡単な調査用プログラムを用意した。
DandD.zip



このソフトを使って Safariから画像をドラッグ&ドロップしてみた。
(MacOSX 10.5で確認)


Apple files promise pasteboard type
Apple PICT pasteboard type
Apple URL pasteboard type
Apple Web Archive pasteboard type
com.apple.flat-rtfd
com.apple.pasteboard.promised-file-content-type
com.apple.pasteboard.promised-file-url
com.apple.pict
com.apple.webarchive
CorePasteboardFlavorType 0x75726C20
CorePasteboardFlavorType 0x75726C6E
dyn.agu8y63n2nuuha5dbrf1ca2pxqry0wkduqf31k3pcr7u1e3basv61a3k
dyn.agu8y6y4usm1044pxqzb085xyqz1hk64uqm10c6xenv61a3k
dyn.agu8yc6durvwwa3xmrvw1gkdusm1044pxqyuha2pxsvw0e55bsmwca7d3sbwu
dyn.agu8yc6durvwwaznwmuuha2pxsvw0e55bsmwca7d3sbwu
dyn.agu8zs3pcnzme2641rf4guzdmsv0gn64uqm10c6xenv61a3k
NeXT plain ascii pasteboard type
NeXT RTFD pasteboard type
NeXT TIFF v4.0 pasteboard type
NSPromiseContentsPboardType
NSStringPboardType
public.tiff
public.url
public.url-name
public.utf8-plain-text
WebURLsWithTitlesPboardType


safari(3.0) は実に多くの形式を用意してくれる。

次に FireFox2.0。

Apple files promise pasteboard type
Apple PICT pasteboard type
Apple URL pasteboard type
AppleCoreDragItemBounds
com.apple.pasteboard.promised-file-url
com.apple.pict
com.apple.traditional-mac-plain-text
CorePasteboardFlavorType 0x4D4F5A6D
CorePasteboardFlavorType 0x4D5A0000
CorePasteboardFlavorType 0x4D5A0001
CorePasteboardFlavorType 0x4D5A0002
CorePasteboardFlavorType 0x4D5A0003
CorePasteboardFlavorType 0x4D5A0004
CorePasteboardFlavorType 0x4D5A0005
CorePasteboardFlavorType 0x54455854
CorePasteboardFlavorType 0x66737350
CorePasteboardFlavorType 0x70686673
CorePasteboardFlavorType 0x7374796C
CorePasteboardFlavorType 0x75726C20
CorePasteboardFlavorType 0x75726C64
CorePasteboardFlavorType 0x75747874
dyn.agk80q65xna
dyn.agk81a4dgsq
dyn.agk81g7d3ru
dyn.agk81n6xqqu
dyn.agk8y40w6aaau
dyn.agk8y40w6aaba
dyn.agk8y40w6aabu
dyn.agk8y40w6aaca
dyn.agk8y40w6aacu
dyn.agk8y40w6absaa
dyn.agk8y4x44ry
NeXT Rich Text Format v1.0 pasteboard type
NSPromiseContentsPboardType
NSStringPboardType
public.url
public.utf16-plain-text

よく分からない形式が多い。


次にFinderから画像ファイルを渡してみる。


Apple URL pasteboard type
AppleCoreDragItemBounds
CorePasteboardFlavorType 0x626E6368
CorePasteboardFlavorType 0x6675726C
CorePasteboardFlavorType 0x6F726769
CorePasteboardFlavorType 0x73646C74
CorePasteboardFlavorType 0xC4697475
CorePasteboardFlavorType 0xC46C6E6B
CorePasteboardFlavorType 0xC4706431
dyn.agk8086xhre
dyn.agk80e5xdra
dyn.agk81g3dqsu
dyn.agk86rexmsv4u
dyn.agk86rexqr3zu
dyn.agk86rexuqu2u
NSFilenamesPboardType
public.file-url

2008年1月3日木曜日

CoreDataでパッケージ保存

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

画像などのデータを取り扱う場合、CoreDataの保存ファイルとは別に画像ファイルそのままで保存して全体を1つのパッケージとしてまとめたい。ところが、info.plistにはパッケージのオプション(LSTypeIsPackage)はあるものの NSPersistentDocumentではこれは使えない。どうしたものかと Googleで情報を集めたところ方法が見つかった。

(1) Core Data Document-based Applicationでファイルパッケージを扱う
狙いは良いのだが残念なことに肝心の保存・読み込み部分が完成していない。


(2) Using NSPersistentDocument o save and open packages

- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url ofType:(NSString *)fileType error:(NSError **)error
をオーバーライドする方法が記載されていた。が、保存時にエラーが出てうまく動かなかった(10.5にて)。


(3) Packages and Core Data documents

Ruby-cocoaによる実装例になるが同等のコードをobjective-c で書いたところ、こちらはうまくいった。

サンプル:sample20080103-2.zip

Test という名前で保存するとパッケージが作成され、その中に data.xmlという CoreDataによって作成されたデータファイルが保存される。読み込みも Test の指定で行える。

CoreDataで非標準データ型を扱う #2

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

前回の続き。
NSImage も同じようにして簡単に扱える。



NSImage も NSCodingプロトコルを実装しているので、エンティティ定義のデータ型で同じように「変換可能」を選ぶだけで良い。



後は普通に NSManagedObjectへ NSImageを設定してやれば良い。



(抜粋)
- (BOOL)tableView:(NSTableView *)aTableView acceptDrop:(id )info
row:(int)row dropOperation:(NSTableViewDropOperation)operation
{
NSPasteboard* pboard = [info draggingPasteboard];
for (NSString* filename in [pboard propertyListForType:NSFilenamesPboardType]) {
NSLog(@"filename=%@", [filename lastPathComponent]);
NSImage *image = [[NSImage alloc] initByReferencingFile:filename];
id item = [NSEntityDescription insertNewObjectForEntityForName:@"ImageItem"
inManagedObjectContext:[self managedObjectContext]];
[item setValue:[filename lastPathComponent] forKey:@"filename"];
[item setValue:image forKey:@"image"];
}
[_table setNeedsDisplay];
return YES;
}


サンプルプログラム:
sample20080103.zip

2008年1月1日火曜日

CoreDataで非標準データ型を扱う

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

Core Data Programming Guide: Non-Standard Persistent Attributes より

CoreDataのモデルは数値や文字列型を標準でサポートしているが、NSColorやオリジナルクラスなどはサポートしていない。これら非標準のデータ型を扱うには2つの方法がある。一つは2つの属性を用意する方法。これはCoreDataの標準型と非標準型の2つの属性を定義して、その間でのデータ変換をコーディングしなければならず面倒。
もう一つは NSValueTransformer を使う方法で、10.5 から導入された NSKeyedUnarchiveFromDataTransformerName を使う。

NSColor を CoreDataで保存するサンプルを作ってみた。





sample20080101.zip



ポイントは 属性のデータ型で「変換可能」(transformable)を選択すること。値変換の名前を空にしておくとデフォルトで NSKeyedUnarchiveFromDataTransformerNameが使われる。この NSValueTransformer は NSCodingプロトコルを実装したクラスのオブジェクトを自動的にオブジェクト<->NSData 間の変換を行う。これによってモデル側で使っている非標準のオブジェクトを保存時には NSData へアーカイブして保存し、読み込み時はその逆に NSDataからアンアーカイブして目的のオブジェクトに変換する。






コードはこんな感じ。