NVKDragDropDataSourceのインターフェイスファイル
インターフェイスファイルの変更点はクラス名のみです。
//
// NVKDragDropDataSource.h
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008 . All rights reserved.
//
#import <Cocoa/Cocoa.h>
@interface NVKDragDropDataSource : NSObject
{
IBOutlet NSArrayController *arrayController;
int insertionRow;
NSIndexSet *selectionIndexes,*oldSelectionIndexes;
}
@end
インプリメンテーションファイルの変更点
DragDropDataSourceとの違いを赤字で示しながら説明していきます。
- Lissajous.hをインポートしていた部分がNVKPasteboardSupportプロトコルを定義するヘッダファイルに置き換わりました。モデルクラスはこのプロトコルを採用している事が前提となります。
- Lissajous.h経由でRMGRoot.hをインポートしなくなったので、UNDO_MANAGERマクロの定義を取り消す必要がなくなりました。よって#undef文を削除しています。
- 管理対象オブジェクトコンテキストへのアクセスを容易にするためにMOCマクロを追加しました。
- 行番号情報のデータタイプを表す文字列とグローバル変数の接頭詞をRMGからNVKに変更しました。
- 配列コントローラに設定されているエンティティ名に対応するクラスのクラスオブジェクトを返すtargetClassメソッドを追加しました。これはNVKTableViewと同じメソッドです。
//
// NVKDragDropDataSource.m
// RepeatingMotifGenerator
//
// Copyright NovemberKou 2008 . All rights reserved.
//
#import "NVKDragDropDataSource.h"
#import "NVKPasteboardSupport.h"
#define MOC [arrayController managedObjectContext]
#define UNDO_MANAGER [[arrayController managedObjectContext] undoManager]
// Pboard type
NSString *NVKRowsType = @"NVKRowIndexesPboardType";
@implementation NVKDragDropDataSource
// 配列コントローラに設定されているエンティティ名に対応するクラスのクラスオブジェクトを返す
- (Class)targetClass
{
NSEntityDescription *entity = [NSEntityDescription entityForName:[arrayController entityName]
inManagedObjectContext:MOC];
return NSClassFromString([entity managedObjectClassName]);
}
初期化メソッド、アクセサメソッド、ユーティリティメソッド
awakeFromNibメソッドにターゲットとなるモデルクラスがNVKPasteboardSupportを採用しているかどうかをチェックするコードを追加しました。していない場合はエラーメッセージを出力するので、デバッグの際に役立つでしょう。
deallocメソッドは変更なしです。アクセサメソッドとユーティリティメソッドも変更ありません。
//
// NVKDragDropDataSource.m
//
#pragma mark -
#pragma mark Initializer and Deallocator
- (void)awakeFromNib
{
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
insertionRow = -1;
[nc addObserver:self
selector:@selector(managedObjectChanged:)
name:NSManagedObjectContextObjectsDidChangeNotification
object:MOC];
selectionIndexes = oldSelectionIndexes = nil;
[self bind:@"selectionIndexes"
toObject:arrayController
withKeyPath:@"selectionIndexes"
options:nil];
// debug support
if(![[self targetClass] conformsToProtocol:@protocol(NVKPasteboardSupport)])
NSLog(@"[NVKDragDropDataSource]:Class '%@' does not adopt NVKPasteboardSupport!",[self targetClass]);
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[selectionIndexes release];
[oldSelectionIndexes release];
[super dealloc];
}
#pragma mark -
#pragma mark Accessor methods
- (NSIndexSet *)selectionIndexes {return selectionIndexes;}
- (void)setSelectionIndexes:(NSIndexSet *)newSelection
{
if(![UNDO_MANAGER isUndoing] && ![UNDO_MANAGER isRedoing])
{
[newSelection retain];
[oldSelectionIndexes release];
oldSelectionIndexes = selectionIndexes;
selectionIndexes = newSelection;
}
}
#pragma mark -
#pragma mark Utility methods
- (void)setActionName:(NSString *)actionName
{
if([UNDO_MANAGER isUndoing] || [UNDO_MANAGER isRedoing])
return;
if(![[UNDO_MANAGER undoActionName] isEqualToString:@""])
return;
[UNDO_MANAGER setActionName:NSLocalizedString(actionName,nil)];
}
管理対象オブジェクトが追加、削除された時の並べ替え
通知を受けて管理対象オブジェクトの順番を並べ直す際の処理ですが、これまでは順序を表す属性を@"order"と固定の文字列で直接指定していました。これをクラスメソッドorderKeyを使って得た文字列を使う様に変更します。
また追加と削除のアクション名をターゲットクラスのクラスメソッドを使って得る様に変更します。
//
// NVKDragDropDataSource.m
//
#pragma mark -
#pragma mark Check Managed Objects
- (void)managedObjectChanged:(NSNotification *)notification
{
int order = 0;
NSSet *insertedObjects,*deletedObjects;
NSEnumerator *enumerator;
NSManagedObject *object;
NSMutableArray *insertedTargetObjects;
NSString *orderKey = [[self targetClass] orderKey];
insertedObjects = [[notification userInfo] objectForKey:NSInsertedObjectsKey];
deletedObjects = [[notification userInfo] objectForKey:NSDeletedObjectsKey];
if([insertedObjects count] > 0 && ![UNDO_MANAGER isUndoing] && ![UNDO_MANAGER isRedoing])
{
insertedTargetObjects = [NSMutableArray arrayWithCapacity:[insertedObjects count]];
// 配列コントローラが管理するオブジェクトの中から挿入されたオブジェクトを抜き出す
enumerator = [[arrayController arrangedObjects] objectEnumerator];
while(object = [enumerator nextObject])
if([insertedObjects containsObject:object])
[insertedTargetObjects addObject:object];
if(insertionRow < 0)
insertionRow = [[arrayController arrangedObjects] count] - [insertedTargetObjects count];
// 挿入されたオブジェクト以外のオブジェクトのorderを設定する
order = 0;
enumerator = [[arrayController arrangedObjects] objectEnumerator];
while(object = [enumerator nextObject])
{
if(order == insertionRow)
order += [insertedTargetObjects count];
if(![insertedTargetObjects containsObject:object])
[object setValue:[NSNumber numberWithInt:order++] forKey:orderKey];
}
// 挿入されたオブジェクトのorderを設定する
order = insertionRow;
enumerator = [insertedTargetObjects objectEnumerator];
while(object = [enumerator nextObject])
[object setValue:[NSNumber numberWithInt:order++] forKey:orderKey];
insertionRow = -1;
[arrayController rearrangeObjects];
}
if([insertedObjects count] > 0)
{
[arrayController setSelectedObjects:[insertedObjects allObjects]];
[self setActionName:[[self targetClass] addActionName]];
}
if([deletedObjects count] > 0)
{
if([UNDO_MANAGER isUndoing])
[arrayController setSelectionIndexes:oldSelectionIndexes];
[self setActionName:[[self targetClass] deleteActionName]];
}
}
データソースのメソッド
以下の点を変更しています。
- Lissajousと直接クラス名を記述していた部分を[self targetClass]に変更した。
- RMGRowsTypeをNVKRowsTypeに変更した。
- アクション名を固定文字列からターゲットクラスのクラスメソッドに変更した。
- @"order"をターゲットクラスのorderKeyメソッドに変更した。
- orderアクセサメソッドの呼び出しをvalueForKey:orderKeyに変更した。
//
// NVKDragDropDataSource.m
//
#pragma mark -
#pragma mark Data Source method
// ゼロを返すとテーブルビューは後戻りしてバインディングからデータを得る
- (int)numberOfRowsInTableView:(NSTableView *)tableView
{
return 0;
}
// nilを返すとテーブルビューは後戻りしてバインディングからデータを得る
- (id)tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn
row:(int)rowIndex
{
return nil;
}
- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes
toPasteboard:(NSPasteboard *)pboard
{
[[self targetClass] copyObjects:[[arrayController arrangedObjects] objectsAtIndexes:rowIndexes]
toPasteboard:pboard];
[pboard addTypes:[NSArray arrayWithObject:NVKRowsType] owner:nil];
[pboard setData:[NSArchiver archivedDataWithRootObject:rowIndexes]
forType:NVKRowsType];
return YES;
}
- (NSDragOperation)tableView:(NSTableView *)tableView
validateDrop:(id <NSDraggingInfo>)info
{
NSDragOperation dragOperation;
dragOperation = ([info draggingSource] != tableView) ? NSDragOperationCopy : NSDragOperationMove;
if([[NSApp currentEvent] modifierFlags] & NSAlternateKeyMask)
dragOperation = NSDragOperationCopy;
return dragOperation & [info draggingSourceOperationMask];
}
- (NSDragOperation)tableView:(NSTableView *)tableView
validateDrop:(id <NSDraggingInfo>)info
proposedRow:(int)row
proposedDropOperation:(NSTableViewDropOperation)operation
{
if(operation == NSTableViewDropOn)
[tableView setDropRow:row dropOperation:NSTableViewDropAbove];
return [self tableView:tableView validateDrop:info];
}
- (void)rearrangeObjects
{
[[UNDO_MANAGER prepareWithInvocationTarget:self] rearrangeObjects];
[arrayController rearrangeObjects];
}
- (BOOL)tableView:(NSTableView*)tableView
acceptDrop:(id <NSDraggingInfo>)info
row:(int)row
dropOperation:(NSTableViewDropOperation)operation
{
NSPasteboard *pboard = [info draggingPasteboard];
NSDragOperation dragOperation = [self tableView:tableView validateDrop:info];
if(dragOperation == NSDragOperationCopy)
{
insertionRow = row;
[[self targetClass] pasteFromPasteboard:pboard controller:arrayController];
[UNDO_MANAGER setActionName:NSLocalizedString([[self targetClass] dragCopyActionName],nil)];
return YES;
}
if(dragOperation == NSDragOperationMove)
{
int i,order,index,first,last;
id object;
NSIndexSet *rowIndexes;
NSArray *arrangedObjects = [arrayController arrangedObjects];
NSEnumerator *enumerator = [arrangedObjects objectEnumerator];
NSMutableArray *orders = [NSMutableArray arrayWithCapacity:[arrangedObjects count]];
NSString *orderKey = [[self targetClass] orderKey];
while(object = [enumerator nextObject])
[orders addObject:[object valueForKey:orderKey]];
rowIndexes = [NSUnarchiver unarchiveObjectWithData:[pboard dataForType:NVKRowsType]];
// 挿入された行までのドラッグされていないオブジェクトの番号を振り直す
first = row < [rowIndexes firstIndex] ? row : [rowIndexes firstIndex];
if(first == 0)
order = 0;
else
order = [[[arrangedObjects objectAtIndex:first-1] valueForKey:orderKey] intValue] + 1;
for(i=first;i<row;i++)
{
if([rowIndexes containsIndex:i]) continue;
object = [arrangedObjects objectAtIndex:i];
[object setValue:[orders objectAtIndex:order++] forKey:orderKey];
}
// ドラッグされているオブジェクトの番号を振り直す
index = [rowIndexes firstIndex];
while(index != NSNotFound)
{
object = [arrangedObjects objectAtIndex:index];
[object setValue:[orders objectAtIndex:order++] forKey:orderKey];
index = [rowIndexes indexGreaterThanIndex:index];
}
// 挿入された行以降のドラッグされていないオブジェクトの番号を振り直す
last = row > [rowIndexes lastIndex] ? row : [rowIndexes lastIndex];
for(i=row;i<last;i++)
{
if([rowIndexes containsIndex:i]) continue;
object = [arrangedObjects objectAtIndex:i];
[object setValue:[orders objectAtIndex:order++] forKey:orderKey];
}
[self rearrangeObjects];
[UNDO_MANAGER setActionName:NSLocalizedString([[self targetClass] dragOperationActionName],nil)];
return YES;
}
else
return NO;
}
爆発アニメーション終了時の削除処理
テーブルビューのアイテムがドラッグされてドロップを受け入れられない場所でリリースされた場合、爆発アニメーションが表示された後にデータソースにanimationEffectDidEnd:メッセージが送られてきます。データソースはドラッグされたアイテムを削除する処理を実行する必要があります。
このメソッドでは以下の処理を行なっています。
- ドラッグ用ペーストボードから行番号情報を取り出す。
- 行番号情報からドラッグされた管理対象オブジェクトを特定する。
- 特定した管理対象オブジェクトを管理対象オブジェクトコンテキストから削除する。
- アンドゥマネージャにアクション名を設定する。
- 管理対象オブジェクトコンテキストに未処理のまま残っている変更点を更新する
5を実行しないと削除されるのにかなり時間がかかります。この解決策がなかなかわかりませんでした。イベントループをまたいでしまうので、手動でprocessPendingChangesを呼ばないといけないという事なのでしょうか?
//
// NVKDragDropDataSource.m
//
// 爆発アニメーション終了後にテーブルビューから呼び出される
- (void)animationEffectDidEnd:(void *)contextInfo
{
NSPasteboard *pboard = [NSPasteboard pasteboardWithName:NSDragPboard];
NSManagedObject *draggedObject;
NSEnumerator *enumerator;
NSIndexSet *rowIndexes;
NSArray *draggedObjects;
rowIndexes = [NSUnarchiver unarchiveObjectWithData:[pboard dataForType:NVKRowsType]];
draggedObjects = [[arrayController arrangedObjects] objectsAtIndexes:rowIndexes];
enumerator = [draggedObjects objectEnumerator];
while(draggedObject = [enumerator nextObject])
[MOC deleteObject:draggedObject];
[self setActionName:[[self targetClass] deleteActionName]];
// これを実行しないとなかなか消えてくれない
[MOC processPendingChanges];
}

ホーム
前へ