Controllerと たわむれる11
前回のつづき
QLPreviewPanelです。
QLPreviewPanelを完全に使い切るには
- QLPreviewPanelController(非形式プロトコル)
- QLPreviewPanelDataSource(形式プロトコル)
- QLPreviewPanelDelegate(形式プロトコル)
の3つのコントローラと
- QLPreviewItem(形式プロトコル)
というモデル(コントローラにしてもよい)が必要です。
QLPreviewItemはIKImageBrawserItemとほぼ一緒なので説明は省きます。
QLPreviewPanelは自身が表示されようとするとき、現在のレスポンダチェーンからQLPreviewPanelControllerに準拠したオブジェクトを探し、それがコントローラになれる事を表明していれば、それを自身のコントローラに設定します。
ついで、自身の表示内容をQLPreviewPanelDataSourceに準拠したdatasouceから受け取りそれを表示します。
そして、何らかのアクションを受けた場合はQLPreviewPanelDelegateに処理をゆだねます。
QLPreviewPanelDataSourceは、もうおなじみのあれです。表示内容をこいつが決めます。
QLPreviewPanelDelegateはQLPreviewPanelが表示されたり閉じられたりするときのズームの起点、終点を指定したり、QLPreviewPanelに投げられた(QLPreviewPanelが使わない)イベントを処理します。
QLPreviewPanelControllerの役割はぶっちゃけて言えば、QLPreviewPanelのdatasourceとdelegateの設定と管理です。
ドキュメントにもある通りWindow controllerかwindow delegateを使用するのが楽です。
モデル寄りのdetasourceとビュー寄りのdelegateの双方を仲介する役に回ることが多いからです。
てことでControllersWindowControllerをQLPreviewPanelのコントローラとして使用します。
今回はモデルの事も良く知ってますのでdatasourceもこいつに任せます。delegateは保留です。
それと、今回プレビューするのは選択されたアイテムだけを対象にします。ごく普通です。
ControllersWindowController.h
#import <Cocoa/Cocoa.h> enum { typeUnknown = 0, typeList = 1, typeIcon = 2, typeFlow = 3, }; @class QLPreviewPanel; // 追加 @interface ControllersWindowController : NSWindowController { NSArray *contents; IBOutlet NSView *placeholder; IBOutlet NSArrayController *controller; NSViewController *viewController; NSMutableDictionary *viewControllers; NSInteger viewType; QLPreviewPanel *previewPanel; // 追加 } @property (retain) NSArray *contents; @property (assign) NSViewController *viewController; @property NSInteger viewType; @property (retain) QLPreviewPanel *previewPanel; // 追加 - (IBAction)togglePreviewPanel:(id)seder; // 追加 @end
QLPreviewPanelをプロパティで持ちました。assignでも良さそうな気がしますが、今回はretainにしました。assignでも問題はないはずです。
それと、アクションとしてtogglePreviewPanel:を追加しました。
これをツールバーのボタンのアクションにします。
では実装部。
ControllersWindowController.m
#import "ControllersWindowController.h" #import "ControllersListViewController.h" #import "ControllersIconViewController.h" #import "ControllersFlowViewController.h" #import <Quartz/Quartz.h> @interface ControllersWindowController (CWC_QLPreiewPanelDataSource) <QLPreviewPanelDataSource> @end @implementation ControllersWindowController @synthesize contents; @synthesize viewController; @synthesize viewType; @synthesize previewPanel; - (id)init { self = [super initWithWindowNibName:@"MainWindow"]; if(self) { viewControllers = [[NSMutableDictionary alloc] init]; } return self; } - (void)dealloc { self.previewPanel = nil; self.contents = nil; [viewControllers release]; [super dealloc]; } - (void)awakeFromNib { self.viewType = typeList; [controller addObserver:self forKeyPath:@"selectionIndexes" options:0 context:NULL]; }
へんなカテゴリがありますが、これはQLPreviewPanelDataSource形式プロトコルに準拠してる宣言です。これやっとかないと怒られます。
@synthesize previewPanel;はおまじない。
- (void)awakeFromNibでKVOでのアレイコントローラのselectionIndexesの監視を開始してます。
- (void)switchViewByType:(NSInteger)type { Class viewControllerClass = Nil; switch(type) { case typeList: viewControllerClass = [ControllersListViewController class]; break; case typeIcon: viewControllerClass = [ControllersIconViewController class]; break; case typeFlow: viewControllerClass = [ControllersFlowViewController class]; break; default: NSLog(@"Unknown view type."); break; } if(!viewControllerClass) return; if(self.viewController) { [self.viewController.view removeFromSuperview]; } self.viewController = [viewControllers objectForKey:[NSNumber numberWithInteger:type]]; if(!self.viewController) { self.viewController = [[[viewControllerClass alloc] init] autorelease]; self.viewController.representedObject = controller; [viewControllers setObject:self.viewController forKey:[NSNumber numberWithInteger:type]]; } NSView *view = self.viewController.view; view.frame = placeholder.frame; [view setFrameOrigin:NSZeroPoint]; [placeholder addSubview:view]; } - (void)setViewType:(NSInteger)newType { if(newType == viewType) return; viewType = newType; [self switchViewByType:viewType]; }
ここはそのまま。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if([keyPath isEqualToString:@"selectionIndexes"]) { [self.previewPanel reloadData]; return; } [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; } - (IBAction)togglePreviewPanel:(id)sender { if(!self.previewPanel) { [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil]; return; } if(self.previewPanel.isVisible) { [[QLPreviewPanel sharedPreviewPanel] orderOut:nil]; } else { [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil]; } }
アレイコントローラのselectionIndexesが変更されたらQLPreviewPanelをリロードさせます。
- (IBAction)togglePreviewPanel:(id)senderでQLPreviewPanelの表示非表示をトグルしてます。
まず、previewPanelプロパティがnilならまだQLPreviewPanelのコントローラになってませんので、QLPreviewPanelを表示させます。コントローラになっていて且つQLPreviewPanelが表示されていれば非表示に、表示されていなければ表示します。
#pragma mark#### QLPreviewPanelController #### - (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel { return YES; } - (void)beginPreviewPanelControl:(QLPreviewPanel *)panel { self.previewPanel = panel; panel.dataSource = self; } - (void)endPreviewPanelControl:(QLPreviewPanel *)panel { panel.dataSource = nil; self.previewPanel = nil; } @end
QLPreviewPanelController部分です。
- (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panelでYESを返す事によって、QLPreviewPanelのコントローラになれる事をQLPreviewPanelに伝えます。
QLPreviewPanelのコントローラになるとQLPreviewPanelが- (void)beginPreviewPanelControl:(QLPreviewPanel *)panelを呼び出します。
まず、previewPanelプロパティにQLPreviewPanelをセットします。これがセットされているインスタンスは現在QLPreviewPanelのコントローラになっているインスタンスです。ControllerWindowControllerのインスタンスは複数ありますので、このプロパティを参照する事でそれがQLPreviewPanelのコントローラであるかどうかを判別出来るようになります。
さらに、QLPreviewPanelのdataSourceに自身を設定します。
- (void)endPreviewPanelControl:(QLPreviewPanel *)panelはコントローラで無くなる直前に呼ばれます。
beginPreviewPanelControl:と逆の事を行ってます。
@implementation ControllersWindowController (CWC_QLPreiewPanelDataSource) - (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel { return controller.selectedObjects.count; } - (id <QLPreviewItem>)previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index { return [controller.selectedObjects objectAtIndex:index]; } @end
QLPreviewPanelのdataSouce部分です。
- (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel
は、プレビューするアイテムの数を返します。選択アイテムをプレビューするのでアレイコントローラのselectedObjectsのcountをそのまま返しています。
- (id
は、指定されたインデックスのアイテムを返します。まあ、見たままですね。
このアイテムはQLPreviewItem非形式プロトコルに準拠している必要があります。
@implementation NSDictionary (ControllersWindowController_QLPreviewItem) - (NSURL *)previewItemURL { return [NSURL fileURLWithPath:[self objectForKey:@"fullpath"]]; } - (NSString *)previewItemTitle { return [self objectForKey:@"filename"]; } @end
で、そのQLPreviewItem非形式プロトコルの部分。
管理しているモデルの実体はNSDictionaryですのでこれをカテゴリで拡張してそれに準拠させます。IKImageBrowserItemの時と全く同じです。
- (NSURL *)previewItemURL
でプレビューすべきアイテムのURLを返します。このメソッドは必須です。
- (NSString *)previewItemTitle
でタイトルバーに表示されるタイトルを返します。このメソッドはオプションです。
これで、QuickLookボタンによるQuickLookが可能になりました。
が、かなり問題ありですね。QLPreviewPanelがアクティブだとキーを押しても何も反応しませんし、表示/非表示のズームアクションもありません。スペースバーでのQuickLook表示も欲しいところです。
次回はそれで!