ページ

2008年2月5日火曜日

スクラップブックその1 (4)ビュー

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

WorkView はドラッグ&ドロップの受け入れと、画像の表示を行う。
ドロップされた画像の保存は以前紹介した Pasteboard Managerを使って行っている。それ以外は MyControllerから image(NSManagedObject)のインスタンスを取得し、必要な設定を行う。


- (BOOL)performDragOperation:(id )sender {

  :

NSImage *image = [[NSImage alloc] initByReferencingURL:[NSURL URLWithString:filepath]];
NSSize size = [image size];
Item *item = [_controller createItem];

item.image = image;
item.x = [NSNumber numberWithFloat:(point.x - size.width / 2)];
item.y = [NSNumber numberWithFloat:(point.y - size.height / 2)];
item.width = [NSNumber numberWithFloat:size.width];
item.height = [NSNumber numberWithFloat:size.height];
item.filename = [filepath lastPathComponent];
  :
}



貼付けられた画像のドラッグ処理は mouseDown: 内で行う。
ADCのドキュメント Handling Mouse Dragging Operations によればドラッグ処理は2つのアプローチがあるとのこと。

 The Three-Method Approach
 The Mouse-Tracking Loop Approach

前者は mouseDown: で開始、mouseDragged: でドラッグ処理、mouseUp: で終了と、その名のとおり3つのメソッドで処理を記述する。この方法の良いところはそれぞれのイベントハンドリングを適切なメソッドで書けるところ。イベント毎の責務が分かれている為、見た目も比較的すっきりする。ただ3つのイベントを連携させる必要があるためケースによっては複雑になるかもしれない。

後者は mouseDown: 内に自前でイベントループを作る。見た目は昔っぽい?が、マウス押しから、ドラッグ、離すまでの処理が一カ所にまとまっているので分かりやすいし、一連の動きを制御しやすい。

説明が長くなったがサンプルでは後者のアプローチを採用している。コードそのものは(忘れてしまったが)どこかのソースコードを参考にして過去に書いたものを流用している。


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

if (!is_moving &&
((fabs(current_point.x - last_point.x) >= 2.0) ||
(fabs(current_point.y - last_point.y) >= 2.0))) {
is_moving = YES;
}

if (is_moving) {
_selected_item = nil;
moving_vector = NSMakePoint(current_point.x - last_point.x,
current_point.y - last_point.y);
[item moveWith:moving_vector];
[self setNeedsDisplay:YES];
}
last_point = current_point;

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

if (!is_moving &&
((fabs(current_point.x - last_point.x) >= 2.0) ||
(fabs(current_point.y - last_point.y) >= 2.0))) {
is_moving = YES;
}

if (is_moving) {
_selected_item = nil;
moving_vector = NSMakePoint(current_point.x - last_point.x,
current_point.y - last_point.y);
[item moveWith:moving_vector];
[self setNeedsDisplay:YES];
}
last_point = current_point;

if ([theEvent type] == NSLeftMouseUp) {
break;
}
}
 :


点の移動量が 2.0以上の場合をドラッグとみなし、移動量をドラッグ対象の画像の位置に反映させて再描画させている。ドラッグ対象の画像は同じ mouseDown: の先頭でヒットテストを行い決定する。

- (void)mouseDown:(NSEvent *)theEvent
{
NSPoint start_point = [self convertPoint:[theEvent locationInWindow] fromView:nil];
Item* item = nil;
// hit test
for (Item* item_test in [[_controller items] reverseObjectEnumerator]) {
if (NSPointInRect(start_point, [item_test rect])) {
item = item_test;
break;
}
}
 :
 :
(この後、イベントループ)

2008年2月4日月曜日

スクラップブックその1 (3)コントローラ

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

コントローラ MyControllerは、ビュー(WorkView)とモデル(Item、CoreData)との間を取り持つ。

i. 起動時

プログラム起動時にCoreDataから Itemの配列を取り出して配列 _items へ格納する。
また同時に itemsフォルダ内の画像ファイルをそれぞれ Item.image へ格納していく。
WorkViewが itemsを元に画像を表示する。


- (void)awakeFromNib
{
_path_to_save = [[_app_delegate applicationSupportFolder] retain];
NSFileManager *file_manager = [NSFileManager defaultManager];
[file_manager createDirectoryAtPath:_path_to_save
attributes:nil];

NSManagedObjectContext *moc = [_app_delegate managedObjectContext];
NSEntityDescription *entity_descriptin = [NSEntityDescription entityForName:@"Item"
inManagedObjectContext:moc];
NSFetchRequest *request = [[[NSFetchRequest alloc] init] autorelease];
[request setEntity:entity_descriptin];

NSError *error;
_items = [[moc executeFetchRequest:request error:&error] retain];
if (_items == nil) {
_items = [[NSMutableArray alloc] init];
}

[_items makeObjectsPerformSelector:@selector(willAccessValueForKey:) withObject:nil];

for (Item* item in _items) {
item.image = [[NSImage alloc]
initWithContentsOfFile:[_path_to_save stringByAppendingPathComponent:item.filename]];
}

}



画像がドラッグ&ドロップされた時、WorkViewが新しい imageを要求する(createItem)。これを受けてMyControllerでは、sp4_AppDelegateから NSManagedObjectContext を取得し、新しい image(NSManagedObject)を作成して返す。

-(Item*)createItem
{
Item *item = [NSEntityDescription insertNewObjectForEntityForName:@"Item"
inManagedObjectContext:[_app_delegate managedObjectContext]];
[_items addObject:item];
return item;
}



WorkViewはitemを使って画像を表示するが、その生成(と今後実装予定の削除など)については MyControllerが中を取り持つ形となる。

2008年2月3日日曜日

スクラップブックその1 (2)アーキテクチャ

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

サンプルアプリは Xcodeで新規プロジェクトを作成する時に「Core Data Application」テンプレートを使った。
イメージを下に示す。



テンプレートによって作成された sp4_AppDelegate はほとんど触らず、別途 MyControllerを作成してこれに Viewとの相手をさせる。画像(Item)の管理は NSMutableArray で行っている。Itemは NSManagedObjectであり CoreDataで管理されているのだが、Viewで扱う場合はNSArrayの方が何かと扱いやすい。NSArrayControllerを作りこれを今回の WorkViewとつなげられれば良いのだが、自前でバインディングを導入する方法がわからないため今回は断念(そもそもそんなことができるのだろうか)。

ドラッグされた画像の保存は以前紹介した Pasteboard Managerを使っている。コードも以前紹介したサンプルのまんま。

2008年2月2日土曜日

スクラップブックその1 (1)モデル

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


画像の表示と保存に必要な情報が定義されている。位置情報は float、imageは Transformable(変換可能)でかつ「一時」的なデータとしている。imageの実態は別途ファイルで管理しているので CoreData上ではメモリ上にあるだけで良い。


モデリングツールで定義した後、このモデルのソースコードを作成する。モデリングツールを開いたままで、メニューから「新規ファイル」を選び、一番したの「設計 - 管理オブジェクトクラス」を選ぶと続いてアシスタントが表示される。



ソースコードを作らなくとも CoreDataは使えるが、モデルに関する特別な処理(値変換など)を記述する場合にはあった方がカスタマイズしやすい。

サンプルではドラッグ&ドロップ処理用のメソッドをいくつか定義している。


- (NSRect)rect
{
return NSMakeRect([self.x floatValue], [self.y floatValue], [self.width floatValue], [self.height floatValue]);
}

- (void)moveWith:(NSPoint)moving_vector
{
self.x = [NSNumber numberWithFloat:([self.x floatValue] + moving_vector.x)];
self.y = [NSNumber numberWithFloat:([self.y floatValue] + moving_vector.y)];
}


CoreDataでは数値型がすべて NSNumberになるのが難点。NSRectなどの構造体がそのまま扱えると便利なのだが。

2008年2月1日金曜日

スクラップブックその1

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

スクラップブック(のプロトタイプ)を作ってみた。



上はアプリに Safari上の画像をドラッグ&ドロップして配置したもの。配置した画像はドラッグして移動できる。画像はデスクトップの itemsフォルダに保存される。




画像の配置情報は CoreDataで管理している。




アプリを立ち上げ直すと、前回終了時の画像が表示される。

今日はここまで。
コードの解説と改良はまた明日。


サンプル:sp4.zip

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);

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

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