ページ

2010年4月14日水曜日

Application List (1) 雛形作成

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

SimpleCap で要望が出ているプリファレンス内のヘルパーアプリ登録の UI改善に乗り出す。

現在はこんな感じ。

今見るとちょっとトホホ感がある。。

これを Mac用アプリでよくあるインターフェイスに変える。たとえば OS X のシステム環境設定にあるアカウントのログイン項目の UI:

[+] [ー] でアプリの増減が行える他、Finderからのドラッグ&ドロップでも登録ができる。SimpleCap の場合、並び替えもできた方がいいだろう。

この UI の実装に取り組む。いつも通りサンプルを作って色々試した後、SimpleCap へ組み込むアプローチを取る。今回はドラッグ&ドロップできるところまで実装してみた。こんな感じ。
リスト表示には NSTableView を使い、ボタンは 23x20の NSButton を使い、 Imageに NSAddTemplate/NSRemoveTemplate、Bezel に Gradient を割り当てるとそれっぽいものができた。

アーキテクチャはいつもの通り Cocoa bindings を使った MVCを採用。今回モデルは NSMutableArray で管理し、これを Array Controller を通じて NSTableViewへ接続(bind)する。
表示一件分を表すモデル用のクラスとして ApplicationEntry を定義した。
ApplicationEntry.h

@interface ApplicationEntry : NSObject {

NSString* name;
NSString* path;
NSImage* icon;
}
@property (copy) NSString* name;
@property (copy) NSString* path;
@property (retain) NSImage* icon;
@end


これらを配列として管理する NSMutableArray は(サンプルなので)AppListAppDelegate の appList_ で管理している(InterfaceBuilder上の App List App...)。
AppListAppDelegate.h

@interface AppListAppDelegate : NSObject {
    NSWindow *window;
IBOutlet NSTableView* tableView_;
IBOutlet NSArrayController* arrayController_;
NSMutableArray* appList_;
}
@property (assign) IBOutlet NSWindow *window;
@property (retain) NSMutableArray* appList;



Interface Builder を使い、これらをつなげ(bind)て行く。

イメージ:
AppListAppDelegate.appList <-- Array Controller <-- NSTableColumn

NSTableColum の bindings の設定はこう。arrangedObjects は配列(appList)内のオブジェクト(ApplicationEntry)を表し、そのプロパティ name を表の表示データとして割り当てる。
ArrayController の bindings。AppListAppDelegate.appList へ bind してある。

MVC がつながった。
Model: appLisst
View: NSTableColumn
Controller: ArrayController

これだけで基本的な表示はおしまい。表示に関するコードを書かずにここまでできる。


次はドラッグ&ドロップの実装。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: でドロップ処理

今回は、まずファインダからのドラッグ&ドロップだけを受け入れるので 1, 3, 4 を実装する。なお 3, 4 のメソッドは NSTableViewDataSource プロトコルで定義されていて NSTableView の DataSourceに対して呼び出しがかかる。この為、この DataSource で実装を書く必要がある。

今回は AppListAppDelegate を NSTableView の DataSource として Interface Builder で設定した。1, 3, 4 の実装は次の通り。

AppListAppDelegate.m

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application 
[tableView_ registerForDraggedTypes:
[NSArray arrayWithObjects:NSFilenamesPboardType, nil]];
}

NSFilenamesPboardType でファイル名の配列を受け取るようにする。


- (NSDragOperation)tableView:(NSTableView*)tv
 validateDrop:(id <NSDraggingInfo>)info
 proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)op
{
    return NSDragOperationEvery;
}

続いて 3.ドロップ受け入れ準備ではすべてのドラッグを受け入れることを返す。

最後に 4. ドロップ処理。

- (BOOL)tableView:(NSTableView *)aTableView
 acceptDrop:(id <NSDraggingInfo>)info
 row:(NSInteger)row dropOperation:(NSTableViewDropOperation)operation
{
    NSPasteboard* pboard = [info draggingPasteboard];
NSArray*filenames = [pboard propertyListForType:NSFilenamesPboardType];

for (NSString* filename in filenames) {
ApplicationEntry* entry = [[[ApplicationEntry alloc] init] autorelease];
entry.path = filename;
entry.name = [filename lastPathComponent];
[arrayController_ insertObject:entry atArrangedObjectIndex:row];
}

return YES;
}


NSPaseboard からドラッグ&ドロップされたファイル名配列を取得し、1つ1つを ApplicationEntry のインスタンスに割り当てる。そのインスタンスを ArrayController を通じて配列へ追加する。appList (NSMutableArray) へ直接追加しないのは、ArrayController を通した方が NSTableColumn の再描画が自動的に行われる為。appList 追加だけだと NSTableColumn は再描画されずドラッグ&ドロップしても何も表示が変わらない。

これで雛形がラフなものだができた。
コードを書く量がほんと少ない。bindings を使う度に毎回Cocoa のすごさを感じる。

- - - -
現状はファイル名を表示しているが、これをアイコン画像付きで、表示もアプリ名にする必要がある。次回取り組んでみよう。