Controllerと たわむれる12
前回のつづき
QLPreviewPanelのDelegate第一弾
第一弾です。長くなります。
QLPreviewPanelのDelegateにはControllersXXXViewController達を使うのですが、ほとんど同じことをやります。こういうときはクラス全部にコピペせずに共通するスーパークラスをでっち上げるのが正解です。元々のスーパークラスがすべてNSViewControllerですからNSViewControllerのサブクラスを共通のスーパークラスに仕立てます。
では、適当にControllersXXXViewControllerの宣言部または実装部のクラス名を選択します。
で、コンテクストメニューまたは「編集」メニューから「リファクタリング...」を選択します。
「スーパークラスを作成」を選択し、スーパークラス名を「ControllersViewController」にします。
多少警告が出ますがとりあえず無視して「プレビュー」「適用」。
#importプリプロセッサの位置が変ですので直しておきます。
他のControllersXXXViewControllerのスーパークラスもControllersViewControllerに変更します。ControllersViewController.hをインクルードするのを忘れずに。
で、ControllersViewController。
ControllersViewController.h
#import <Cocoa/Cocoa.h> #import <Quartz/Quartz.h> @interface ControllersViewController : NSViewController <QLPreviewPanelDelegate> - (NSView *)previewTragetView; @end
これもインクルードファイルがおかしいので直しておきます。
それとQLPreviewPanelDelegateプロトコルへの準拠を宣言します。
共通するメソッドは -previewTragetView だけ宣言しておきます。このメソッドは実際にPreviewされるアイテムが表示されているビューを返すメソッドです。当然サブクラスでオーバーライドされる事が前提です。
ControllersViewController.mにいく前に、NSViewControllerの話。
NSViewControllerはNSResponderを親に持ってますが、通常はレスポンダーチェーンに含まれません。今回はそれだと困るのでレスポンダーチェーンに割り込むようにします。
ControllersViewController.m
#import "ControllersViewController.h" @implementation ControllersViewController - (void)dealloc { [[self view] removeObserver:self forKeyPath:@"nextResponder"]; [super dealloc]; } - (void)loadView { [super loadView]; [[self view] addObserver:self forKeyPath:@"nextResponder" options:0 context:NULL]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if(![keyPath isEqualToString:@"nextResponder"]) { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; return; } id nextResponder = [object nextResponder]; if([self isEqual:nextResponder]) return; [self setNextResponder:nextResponder]; [object setNextResponder:self]; if([object window]) { [[object window] makeFirstResponder:[self previewTragetView]]; } } - (void)keyDown:(NSEvent *)theEvent { if([theEvent isARepeat]) return [super keyDown:theEvent]; #define kSPACE_KEY 49 unsigned short code = [theEvent keyCode]; switch(code) { case kSPACE_KEY: [NSApp sendAction:@selector(togglePreviewPanel:) to:nil from:nil]; return; } [super keyDown:theEvent]; } - (NSView *)previewTragetView { return nil; } - (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event { if([event type] != NSKeyDown) return NO; NSView *previewTragetView = [self previewTragetView]; if(!previewTragetView) return NO; [previewTragetView keyDown:event]; return YES; } @end
ちょっと長いです。順番に。
まず、最初の3つのメソッドはレスポンダーチェーンに割り込むためのメソッドです。
- 自身のviewのnextResponderをKVOで監視する
- 自身のviewのnextResponderが変更されたらviewと新しいnextResponderの間に自身を割り込ませる
という方法を採っています。
既にnextResponderが自身であればもちろん割り込みません。ここでさらに割り込むと無限ループになりますので注意してください。
あと、ついでに、previewTragetViewをfirstResponderにしてます。
レスポンダーチェーンに割り込んだので-keyDown:メソッドが呼ばれるようになります。これを使ってスペースバーでのプレビューの開始を行います。
直接ControllersWindowControllerにメソッドを投げずにAction-Targetを使用してFirstResponderに委譲しています。これで、クラス依存が無くなりますし、もしターゲットが変わってもこのメソッドを変更する必要は無くなります。
-previewTragetViewメソッドはデフォルトではnilを返します。
QLPreviewPanelの最初のDelegateメソッドは -previewPanel:handleEvent:です。
このメソッドでQLPreviewPanelが処理しなかったイベントを処理する機会が与えられます。
処理出来なかった場合はNOを処理出来た場合はYESを返します。NOを返した場合はビープ音が発せられます。
今回はkeyDownイベントのみを全部ごっそりpreviewTragetViewに処理させます。previewTragetViewが処理出来なければおそらくpreviewTragetViewがビープ音を発するでしょう。
次回は第2弾。