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:を追加しました。
これをツールバーのボタンのアクションにします。


MainWindow.xib


では実装部。
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 )previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index
は、指定されたインデックスのアイテムを返します。まあ、見たままですね。
このアイテムは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表示も欲しいところです。


次回はそれで!


つづき Controllerと たわむれる12 - masakihの日記