ページ

2010年4月17日土曜日

Application List (4) ドラッグ&ドロップ

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

(前回)Cocoaの日々: Application List (3) アイコン表示

NSTableView 内でドラッグ&ドロップを使った並び替えの機能を実装する。

再びリファレンスを見て必要な実装を入れる。
Table View Programming Guide: Using Drag and Drop in Tables

1. registerForDraggedTypes: でサポートするデータタイプを定義
2. tableView:writeRowsWithIndexes:toPasteboard: でドラッグ開始
3. tableView:validateDrop:proposedRow:propsedDropOperation: でドロップ受け入れ準備
4. tableView:acceptDrop:row:dropOperation: でドロップ処理


まずデータタイプを定義。


#define AppListTableViewDataType @"AppListTableViewDataType"
 :


- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[tableView_ registerForDraggedTypes:
[NSArray arrayWithObjects:
  NSFilenamesPboardType, AppListTableViewDataType, nil]];
            :
}



次にドラッグ開始時の処理。ドラッグ対象の行データを NSKeyedArchiver を使い  NSData に変換し、ペーストボードへ登録する。

- (BOOL)tableView:(NSTableView *)tv writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard*)pboard
{
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:rowIndexes];
    [pboard declareTypes:[NSArray arrayWithObject:AppListTableViewDataType] owner:self];
    [pboard setData:data forType:AppListTableViewDataType];
    return YES;
}


ドロップ受け入れ判定。NSTableView 内の場合、NSDragOperationMove (移動) とする。

- (NSDragOperation)tableView:(NSTableView *)aTableView validateDrop:(id < NSDraggingInfo >)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)operation
{
[aTableView setDropRow:row dropOperation:NSTableViewDropAbove];
if ([info draggingSource] == tableView_) {
return NSDragOperationMove;
return NSDragOperationEvery;
}


最後はドロップ時の処理だが、とりあえずここまでで動かしてみる。

ん?ドラッグしても選択されるだけで、行をドラッグできない。なぜだろう。



いろいろ調べていると NSTableView はドラッグ時に -[NSCell hitTestForEvent:inRect:ofView:] を呼び出し、この結果でドラッグするかを判断しているらしい (v10.5から)。

情報源:

reordering table cells with 10.5 sdk | Cocoabuilder

上記に掲載されていた情報を引用すると:
NSTableView/NSOutlineView - Cell Hit Testing, Drag and Drop, and
Cell EditingNSTableView now uses the new NSCell hit testing API to
perform certain actions. Custom NSCell subclasses in applications
that link on or after Leopard should properly implement -
hitTestForEvent:inRect:ofView:; see NSCell.h for more information.

NSTableView performs hit testing in the cells to do the following
actions:
 - Drag and Drop: NSTableView calls hitTestForEvent:inRect:ofView in
 canDragRowsWithIndexes:atPoint. If the hit cell returns
 NSCellHitTrackableArea, the particular row will be tracked instead
 of dragged.
 - Cell Editing: When NSTableView recieves a mouse down, single-click
 editing of text (like Finder) will happen if there is only one row
 selected, and the cell returns NSCellHitEditableTextArea.

 See the DragNDropOutlineView demo application for an example of how
 to properly implement the NSCell methods.
※上記は developer.apple.com の releaesnotes に掲載されていたらしいが、みつからなかった。


関連情報:

NSCell Class Reference - Hit Testing

NSCell Class Reference - hitTestForEvent:inRect:ofView:

DragNDropOutlineView


そこで NSCell のサブクラスで hitTestForEvent:inRect:ofView: を実装してみた。といっても単純に NSCellHitContentArea を返すだけ。
ApplicationCell.m

- (NSUInteger)hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView {
return NSCellHitContentArea;
}



これでどうだろう。
ドラッグできた。挿入位置に横線が入るようになった。ただ描画がおかしい。何故かドラッグしている行が一番上の行に描画されている。うーむ。