PDFViewController.mm   [plain text]


/*
 * Copyright (C) 2010, 2011 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "config.h"
#import "PDFViewController.h"

#import "DataReference.h"
#import "WKAPICast.h"
#import "WKViewPrivate.h"
#import "WebData.h"
#import "WebEventFactory.h"
#import "WebPageGroup.h"
#import "WebPageProxy.h"
#import "WebPreferences.h"
#import <PDFKit/PDFKit.h>
#import <WebCore/LocalizedStrings.h>
#import <objc/runtime.h>
#import <wtf/text/CString.h>
#import <wtf/text/WTFString.h>

// Redeclarations of PDFKit notifications. We can't use the API since we use a weak link to the framework.
#define _webkit_PDFViewDisplayModeChangedNotification @"PDFViewDisplayModeChanged"
#define _webkit_PDFViewScaleChangedNotification @"PDFViewScaleChanged"
#define _webkit_PDFViewPageChangedNotification @"PDFViewChangedPage"

using namespace WebKit;

@class PDFDocument;
@class PDFView;

@interface PDFDocument (PDFDocumentDetails)
- (NSPrintOperation *)getPrintOperationForPrintInfo:(NSPrintInfo *)printInfo autoRotate:(BOOL)doRotate;
@end

extern "C" NSString *_NSPathForSystemFramework(NSString *framework);

// MARK: C UTILITY FUNCTIONS

static void _applicationInfoForMIMEType(NSString *type, NSString **name, NSImage **image)
{
    ASSERT(name);
    ASSERT(image);
    
    CFURLRef appURL = 0;

    OSStatus error = LSCopyApplicationForMIMEType((CFStringRef)type, kLSRolesAll, &appURL);
    if (error != noErr)
        return;

    NSString *appPath = [(NSURL *)appURL path];
    if (appURL)
        CFRelease(appURL);

    *image = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
    [*image setSize:NSMakeSize(16, 16)];

    *name = [[NSFileManager defaultManager] displayNameAtPath:appPath];
}

// FIXME 4182876: We can eliminate this function in favor if -isEqual: if [PDFSelection isEqual:] is overridden
// to compare contents.
static BOOL _PDFSelectionsAreEqual(PDFSelection *selectionA, PDFSelection *selectionB)
{
    NSArray *aPages = [selectionA pages];
    NSArray *bPages = [selectionB pages];

    if (![aPages isEqual:bPages])
        return NO;

    NSUInteger count = [aPages count];
    for (NSUInteger i = 0; i < count; ++i) {
        NSRect aBounds = [selectionA boundsForPage:[aPages objectAtIndex:i]];
        NSRect bBounds = [selectionB boundsForPage:[bPages objectAtIndex:i]];
        if (!NSEqualRects(aBounds, bBounds))
            return NO;
    }

    return YES;
}

@interface WKPDFView : NSView
{
    PDFViewController* _pdfViewController;

    RetainPtr<NSView> _pdfPreviewView;
    PDFView *_pdfView;
    BOOL _ignoreScaleAndDisplayModeAndPageNotifications;
    BOOL _willUpdatePreferencesSoon;
}

- (id)initWithFrame:(NSRect)frame PDFViewController:(PDFViewController*)pdfViewController;
- (void)invalidate;
- (PDFView *)pdfView;
- (void)setDocument:(PDFDocument *)pdfDocument;

- (BOOL)forwardScrollWheelEvent:(NSEvent *)wheelEvent;
- (void)_applyPDFPreferences;
- (PDFSelection *)_nextMatchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag fromSelection:(PDFSelection *)initialSelection startInSelection:(BOOL)startInSelection;
@end

@implementation WKPDFView

- (id)initWithFrame:(NSRect)frame PDFViewController:(PDFViewController*)pdfViewController
{
    if ((self = [super initWithFrame:frame])) {
        _pdfViewController = pdfViewController;

        [self setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];

        Class previewViewClass = PDFViewController::pdfPreviewViewClass();
        ASSERT(previewViewClass);

        _pdfPreviewView.adoptNS([[previewViewClass alloc] initWithFrame:frame]);
        [_pdfPreviewView.get() setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
        [self addSubview:_pdfPreviewView.get()];

        _pdfView = [_pdfPreviewView.get() performSelector:@selector(pdfView)];
        [_pdfView setDelegate:self];
    }

    return self;
}

- (void)invalidate
{
    _pdfViewController = 0;
}

- (PDFView *)pdfView
{
    return _pdfView;
}

- (void)setDocument:(PDFDocument *)pdfDocument
{
    _ignoreScaleAndDisplayModeAndPageNotifications = YES;
    [_pdfView setDocument:pdfDocument];
    [self _applyPDFPreferences];
    _ignoreScaleAndDisplayModeAndPageNotifications = NO;
}

- (void)_applyPDFPreferences
{
    if (!_pdfViewController)
        return;

    WebPreferences *preferences = _pdfViewController->page()->pageGroup()->preferences();

    CGFloat scaleFactor = preferences->pdfScaleFactor();
    if (!scaleFactor)
        [_pdfView setAutoScales:YES];
    else {
        [_pdfView setAutoScales:NO];
        [_pdfView setScaleFactor:scaleFactor];
    }
    [_pdfView setDisplayMode:preferences->pdfDisplayMode()];
}

- (void)_updatePreferences:(id)ignored
{
    _willUpdatePreferencesSoon = NO;

    if (!_pdfViewController)
        return;

    WebPreferences* preferences = _pdfViewController->page()->pageGroup()->preferences();

    CGFloat scaleFactor = [_pdfView autoScales] ? 0 : [_pdfView scaleFactor];
    preferences->setPDFScaleFactor(scaleFactor);
    preferences->setPDFDisplayMode([_pdfView displayMode]);
}

- (void)_updatePreferencesSoon
{   
    if (_willUpdatePreferencesSoon)
        return;

    [self performSelector:@selector(_updatePreferences:) withObject:nil afterDelay:0];
    _willUpdatePreferencesSoon = YES;
}

- (void)_scaleOrDisplayModeOrPageChanged:(NSNotification *)notification
{
    ASSERT_ARG(notification, [notification object] == _pdfView);
    if (!_ignoreScaleAndDisplayModeAndPageNotifications)
        [self _updatePreferencesSoon];
}

- (void)_openWithFinder:(id)sender
{
    _pdfViewController->openPDFInFinder();
}

- (PDFSelection *)_nextMatchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag fromSelection:(PDFSelection *)initialSelection startInSelection:(BOOL)startInSelection
{
    if (![string length])
        return nil;

    int options = 0;
    if (!forward)
        options |= NSBackwardsSearch;

    if (!caseFlag)
        options |= NSCaseInsensitiveSearch;

    PDFDocument *document = [_pdfView document];

    PDFSelection *selectionForInitialSearch = [initialSelection copy];
    if (startInSelection) {
        // Initially we want to include the selected text in the search.  So we must modify the starting search 
        // selection to fit PDFDocument's search requirements: selection must have a length >= 1, begin before 
        // the current selection (if searching forwards) or after (if searching backwards).
        int initialSelectionLength = [[initialSelection string] length];
        if (forward) {
            [selectionForInitialSearch extendSelectionAtStart:1];
            [selectionForInitialSearch extendSelectionAtEnd:-initialSelectionLength];
        } else {
            [selectionForInitialSearch extendSelectionAtEnd:1];
            [selectionForInitialSearch extendSelectionAtStart:-initialSelectionLength];
        }
    }
    PDFSelection *foundSelection = [document findString:string fromSelection:selectionForInitialSearch withOptions:options];
    [selectionForInitialSearch release];

    // If we first searched in the selection, and we found the selection, search again from just past the selection
    if (startInSelection && _PDFSelectionsAreEqual(foundSelection, initialSelection))
        foundSelection = [document findString:string fromSelection:initialSelection withOptions:options];

    if (!foundSelection && wrapFlag)
        foundSelection = [document findString:string fromSelection:nil withOptions:options];

    return foundSelection;
}

- (NSUInteger)_countMatches:(NSString *)string caseSensitive:(BOOL)caseFlag
{
    if (![string length])
        return 0;

    int options = caseFlag ? 0 : NSCaseInsensitiveSearch;

    return [[[_pdfView document] findString:string withOptions:options] count];
}

// MARK: NSView overrides

- (void)viewDidMoveToWindow
{
    if (![self window])
        return;

    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(_scaleOrDisplayModeOrPageChanged:) name:_webkit_PDFViewScaleChangedNotification object:_pdfView];
    [notificationCenter addObserver:self selector:@selector(_scaleOrDisplayModeOrPageChanged:) name:_webkit_PDFViewDisplayModeChangedNotification object:_pdfView];
    [notificationCenter addObserver:self selector:@selector(_scaleOrDisplayModeOrPageChanged:) name:_webkit_PDFViewPageChangedNotification object:_pdfView];
}

- (void)viewWillMoveToWindow:(NSWindow *)newWindow
{
    if (![self window])
        return;

    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self name:_webkit_PDFViewScaleChangedNotification object:_pdfView];
    [notificationCenter removeObserver:self name:_webkit_PDFViewDisplayModeChangedNotification object:_pdfView];
    [notificationCenter removeObserver:self name:_webkit_PDFViewPageChangedNotification object:_pdfView];
}

- (NSView *)hitTest:(NSPoint)point
{
    // Override hitTest so we can override menuForEvent.
    NSEvent *event = [NSApp currentEvent];
    NSEventType type = [event type];
    if (type == NSRightMouseDown || (type == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask)))
        return self;

    return [super hitTest:point];
}

static void insertOpenWithDefaultPDFMenuItem(NSMenu *menu, NSUInteger index)
{
    // Add in an "Open with <default PDF viewer>" item
    NSString *appName = nil;
    NSImage *appIcon = nil;
    
    _applicationInfoForMIMEType(@"application/pdf", &appName, &appIcon);
    if (!appName)
        appName = WEB_UI_STRING("Finder", "Default application name for Open With context menu");
    
    // To match the PDFKit style, we'll add Open with Preview even when there's no document yet to view, and
    // disable it using validateUserInterfaceItem.
    NSString *title = [NSString stringWithFormat:WEB_UI_STRING("Open with %@", "context menu item for PDF"), appName];
    
    NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(_openWithFinder:) keyEquivalent:@""];
    if (appIcon)
        [item setImage:appIcon];
    [menu insertItem:item atIndex:index];
    [item release];
}

- (NSMenu *)menuForEvent:(NSEvent *)theEvent
{
    NSMenu *menu = [[NSMenu alloc] initWithTitle:@""];

    bool insertedOpenWithItem = false;
    
    NSEnumerator *menuItemEnumerator = [[[_pdfView menuForEvent:theEvent] itemArray] objectEnumerator];
    while (NSMenuItem *item = [menuItemEnumerator nextObject]) {
        NSMenuItem *itemCopy = [item copy];
        [menu addItem:itemCopy];
        [itemCopy release];

        if (insertedOpenWithItem)
            continue;
        
        // If a "Copy" item is present, place the "Open With" item just after it, with an intervening separator.
        if ([item action] != @selector(copy:))
            continue;
        
        [menu addItem:[NSMenuItem separatorItem]];
        insertOpenWithDefaultPDFMenuItem(menu, [menu numberOfItems]);
        insertedOpenWithItem = true;
    }
    
    if (!insertedOpenWithItem) {
        // No "Copy" item was found; place the "Open With" item at the top of the menu, with a trailing separator.
        insertOpenWithDefaultPDFMenuItem(menu, 0);
        [menu insertItem:[NSMenuItem separatorItem] atIndex:1];
    }

    return [menu autorelease];
}

// MARK: NSUserInterfaceValidations PROTOCOL IMPLEMENTATION

- (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
{
    SEL action = [item action];
    if (action == @selector(_openWithFinder:))
        return [_pdfView document] != nil;
    return YES;
}

// MARK: PDFView delegate methods

- (void)PDFViewWillClickOnLink:(PDFView *)sender withURL:(NSURL *)URL
{
    _pdfViewController->linkClicked([URL absoluteString]);
}

- (void)PDFViewOpenPDFInNativeApplication:(PDFView *)sender
{
    _pdfViewController->openPDFInFinder();
}

- (void)PDFViewSavePDFToDownloadFolder:(PDFView *)sender
{
    _pdfViewController->savePDFToDownloadsFolder();
}

- (void)PDFViewPerformPrint:(PDFView *)sender
{
    _pdfViewController->print();
}

- (BOOL)forwardScrollWheelEvent:(NSEvent *)wheelEvent
{
    return _pdfViewController->forwardScrollWheelEvent(wheelEvent);
}

@end

namespace WebKit {

PassOwnPtr<PDFViewController> PDFViewController::create(WKView *wkView)
{
    return adoptPtr(new PDFViewController(wkView));
}

PDFViewController::PDFViewController(WKView *wkView)
    : m_wkView(wkView)
    , m_wkPDFView(AdoptNS, [[WKPDFView alloc] initWithFrame:[m_wkView bounds] PDFViewController:this])
    , m_pdfView([m_wkPDFView.get() pdfView])
    , m_hasWrittenPDFToDisk(false)
{
    [m_wkView addSubview:m_wkPDFView.get()];
}

PDFViewController::~PDFViewController()
{
    [m_wkPDFView.get() removeFromSuperview];
    [m_wkPDFView.get() invalidate];
    m_wkPDFView = nullptr;
}

WebPageProxy* PDFViewController::page() const
{
    return toImpl([m_wkView pageRef]);
}

NSView* PDFViewController::pdfView() const
{ 
    return m_wkPDFView.get(); 
}
    
static RetainPtr<CFDataRef> convertPostScriptDataSourceToPDF(const CoreIPC::DataReference& dataReference)
{
    // Convert PostScript to PDF using Quartz 2D API
    // http://developer.apple.com/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_ps_convert/chapter_16_section_1.html
    
    CGPSConverterCallbacks callbacks = { 0, 0, 0, 0, 0, 0, 0, 0 };    
    RetainPtr<CGPSConverterRef> converter(AdoptCF, CGPSConverterCreate(0, &callbacks, 0));
    ASSERT(converter);

    RetainPtr<NSData> nsData(AdoptNS, [[NSData alloc] initWithBytesNoCopy:const_cast<uint8_t*>(dataReference.data()) length:dataReference.size() freeWhenDone:NO]);   

    RetainPtr<CGDataProviderRef> provider(AdoptCF, CGDataProviderCreateWithCFData((CFDataRef)nsData.get()));
    ASSERT(provider);

    RetainPtr<CFMutableDataRef> result(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
    ASSERT(result);
    
    RetainPtr<CGDataConsumerRef> consumer(AdoptCF, CGDataConsumerCreateWithCFData(result.get()));
    ASSERT(consumer);
    
    CGPSConverterConvert(converter.get(), provider.get(), consumer.get(), 0);

    if (!result)
        return 0;

    return result;
}

void PDFViewController::setPDFDocumentData(const String& mimeType, const String& suggestedFilename, const CoreIPC::DataReference& dataReference)
{
    if (equalIgnoringCase(mimeType, "application/postscript")) {
        m_pdfData = convertPostScriptDataSourceToPDF(dataReference);
        if (!m_pdfData)
            return;
        m_suggestedFilename = String(suggestedFilename + ".pdf");
    } else {
        // Make sure to copy the data.
        m_pdfData.adoptCF(CFDataCreate(0, dataReference.data(), dataReference.size()));
        m_suggestedFilename = suggestedFilename;
    }

    RetainPtr<PDFDocument> pdfDocument(AdoptNS, [[pdfDocumentClass() alloc] initWithData:(NSData *)m_pdfData.get()]);
    [m_wkPDFView.get() setDocument:pdfDocument.get()];
}

double PDFViewController::zoomFactor() const
{
    return [m_pdfView scaleFactor];
}

void PDFViewController::setZoomFactor(double zoomFactor)
{
    [m_pdfView setScaleFactor:zoomFactor];
}

Class PDFViewController::pdfDocumentClass()
{
    static Class pdfDocumentClass = [pdfKitBundle() classNamed:@"PDFDocument"];
    
    return pdfDocumentClass;
}

Class PDFViewController::pdfPreviewViewClass()
{
    static Class pdfPreviewViewClass = [pdfKitBundle() classNamed:@"PDFPreviewView"];
    
    return pdfPreviewViewClass;
}

bool PDFViewController::forwardScrollWheelEvent(NSEvent *wheelEvent)
{
    CGFloat deltaX = [wheelEvent deltaX];
    if ((deltaX > 0 && !page()->canGoBack()) || (deltaX < 0 && !page()->canGoForward()))
        return false;

    [m_wkView scrollWheel:wheelEvent];
    return true;
}

#ifndef BUILDING_ON_SNOW_LEOPARD
static IMP oldPDFViewScrollView_scrollWheel;

static WKPDFView *findEnclosingWKPDFView(NSView *view)
{
    for (NSView *superview = [view superview]; superview; superview = [superview superview]) {
        if ([superview isKindOfClass:[WKPDFView class]])
            return static_cast<WKPDFView *>(superview);
    }

    return nil;
}

static void PDFViewScrollView_scrollWheel(NSScrollView* self, SEL _cmd, NSEvent *wheelEvent)
{
    CGFloat deltaX = [wheelEvent deltaX];
    CGFloat deltaY = [wheelEvent deltaY];

    NSSize contentsSize = [[self documentView] bounds].size;
    NSRect visibleRect = [self documentVisibleRect];

    // We only want to forward the wheel events if the horizontal delta is non-zero,
    // and only if we're pinned to either the left or right side.
    // We also never want to forward momentum scroll events.
    if ([wheelEvent momentumPhase] == NSEventPhaseNone && deltaX && fabsf(deltaY) < fabsf(deltaX)
        && ((deltaX > 0 && visibleRect.origin.x <= 0) || (deltaX < 0 && contentsSize.width <= NSMaxX(visibleRect)))) {
    
        if (WKPDFView *pdfView = findEnclosingWKPDFView(self)) {
            if ([pdfView forwardScrollWheelEvent:wheelEvent])
                return;
        }
    }

    oldPDFViewScrollView_scrollWheel(self, _cmd, wheelEvent);
}
#endif

NSBundle* PDFViewController::pdfKitBundle()
{
    static NSBundle *pdfKitBundle;
    if (pdfKitBundle)
        return pdfKitBundle;

    NSString *pdfKitPath = [_NSPathForSystemFramework(@"Quartz.framework") stringByAppendingString:@"/Frameworks/PDFKit.framework"];
    if (!pdfKitPath) {
        LOG_ERROR("Couldn't find PDFKit.framework");
        return nil;
    }

    pdfKitBundle = [NSBundle bundleWithPath:pdfKitPath];
    if (![pdfKitBundle load])
        LOG_ERROR("Couldn't load PDFKit.framework");

#ifndef BUILDING_ON_SNOW_LEOPARD
    if (Class pdfViewScrollViewClass = [pdfKitBundle classNamed:@"PDFViewScrollView"]) {
        if (Method scrollWheel = class_getInstanceMethod(pdfViewScrollViewClass, @selector(scrollWheel:)))
            oldPDFViewScrollView_scrollWheel = method_setImplementation(scrollWheel, reinterpret_cast<IMP>(PDFViewScrollView_scrollWheel));
    }
#endif

    return pdfKitBundle;
}

NSPrintOperation *PDFViewController::makePrintOperation(NSPrintInfo *printInfo)
{
    return [[m_pdfView document] getPrintOperationForPrintInfo:printInfo autoRotate:YES];
}

void PDFViewController::openPDFInFinder()
{
    // We don't want to open the PDF until we have a document to write. (see 4892525).
    if (![m_pdfView document]) {
        NSBeep();
        return;
    }

    NSString *path = pathToPDFOnDisk();
    if (!path)
        return;

    if (!m_hasWrittenPDFToDisk) {
        // Create a PDF file with the minimal permissions (only accessible to the current user, see 4145714).
        RetainPtr<NSNumber> permissions(AdoptNS, [[NSNumber alloc] initWithInt:S_IRUSR]);
        RetainPtr<NSDictionary> fileAttributes(AdoptNS, [[NSDictionary alloc] initWithObjectsAndKeys:permissions.get(), NSFilePosixPermissions, nil]);

        if (![[NSFileManager defaultManager] createFileAtPath:path contents:(NSData *)m_pdfData.get() attributes:fileAttributes.get()])
            return;

        m_hasWrittenPDFToDisk = true;
    }

    [[NSWorkspace sharedWorkspace] openFile:path];
}

static void releaseCFData(unsigned char*, const void* data)
{
    ASSERT(CFGetTypeID(data) == CFDataGetTypeID());

    // Balanced by CFRetain in savePDFToDownloadsFolder.
    CFRelease(data);
}

void PDFViewController::savePDFToDownloadsFolder()
{
    // We don't want to write the file until we have a document to write. (see 5267607).
    if (![m_pdfView document]) {
        NSBeep();
        return;
    }

    ASSERT(m_pdfData);

    // Balanced by CFRelease in releaseCFData.
    CFRetain(m_pdfData.get());

    RefPtr<WebData> data = WebData::createWithoutCopying(CFDataGetBytePtr(m_pdfData.get()), CFDataGetLength(m_pdfData.get()), releaseCFData, m_pdfData.get());

    page()->saveDataToFileInDownloadsFolder(m_suggestedFilename.get(), page()->mainFrame()->mimeType(), page()->mainFrame()->url(), data.get());
}

static NSString *temporaryPDFDirectoryPath()
{
    static NSString *temporaryPDFDirectoryPath;

    if (!temporaryPDFDirectoryPath) {
        NSString *temporaryDirectoryTemplate = [NSTemporaryDirectory() stringByAppendingPathComponent:@"WebKitPDFs-XXXXXX"];
        CString templateRepresentation = [temporaryDirectoryTemplate fileSystemRepresentation];

        if (mkdtemp(templateRepresentation.mutableData()))
            temporaryPDFDirectoryPath = [[[NSFileManager defaultManager] stringWithFileSystemRepresentation:templateRepresentation.data() length:templateRepresentation.length()] copy];
    }

    return temporaryPDFDirectoryPath;
}

NSString *PDFViewController::pathToPDFOnDisk()
{
    if (m_pathToPDFOnDisk)
        return m_pathToPDFOnDisk.get();

    NSString *pdfDirectoryPath = temporaryPDFDirectoryPath();
    if (!pdfDirectoryPath)
        return nil;

    NSString *path = [pdfDirectoryPath stringByAppendingPathComponent:m_suggestedFilename.get()];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:path]) {
        NSString *pathTemplatePrefix = [pdfDirectoryPath stringByAppendingPathComponent:@"XXXXXX-"];
        NSString *pathTemplate = [pathTemplatePrefix stringByAppendingString:m_suggestedFilename.get()];
        CString pathTemplateRepresentation = [pathTemplate fileSystemRepresentation];

        int fd = mkstemps(pathTemplateRepresentation.mutableData(), pathTemplateRepresentation.length() - strlen([pathTemplatePrefix fileSystemRepresentation]) + 1);
        if (fd < 0)
            return nil;

        close(fd);
        path = [fileManager stringWithFileSystemRepresentation:pathTemplateRepresentation.data() length:pathTemplateRepresentation.length()];
    }

    m_pathToPDFOnDisk.adoptNS([path copy]);
    return path;
}

void PDFViewController::linkClicked(const String& url)
{
    NSEvent* nsEvent = [NSApp currentEvent];
    WebMouseEvent event;
    switch ([nsEvent type]) {
    case NSLeftMouseUp:
    case NSRightMouseUp:
    case NSOtherMouseUp:
        event = WebEventFactory::createWebMouseEvent(nsEvent, m_pdfView);
    default:
        // For non mouse-clicks or for keyboard events, pass an empty WebMouseEvent
        // through.  The event is only used by the WebFrameLoaderClient to determine
        // the modifier keys and which mouse button is down.  These queries will be
        // valid with an empty event.
        break;
    }
    
    page()->linkClicked(url, event);
}

void PDFViewController::print()
{
    page()->printMainFrame();
}

void PDFViewController::findString(const String& string, FindOptions options, unsigned maxMatchCount)
{
    BOOL forward = !(options & FindOptionsBackwards);
    BOOL caseFlag = !(options & FindOptionsCaseInsensitive);
    BOOL wrapFlag = options & FindOptionsWrapAround;

    PDFSelection *selection = [m_wkPDFView.get() _nextMatchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag fromSelection:[m_pdfView currentSelection] startInSelection:NO];
    if (!selection) {
        page()->didFailToFindString(string);
        return;
    }

    NSUInteger matchCount;
    if (!maxMatchCount) {
        // If the max was zero, any result means we exceeded the max. We can skip computing the actual count.
        matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
    } else {
        matchCount = [m_wkPDFView.get() _countMatches:string caseSensitive:caseFlag];
        if (matchCount > maxMatchCount)
            matchCount = static_cast<unsigned>(kWKMoreThanMaximumMatchCount);
    }

    [m_pdfView setCurrentSelection:selection];
    [m_pdfView scrollSelectionToVisible:nil];
    page()->didFindString(string, matchCount);
}

void PDFViewController::countStringMatches(const String& string, FindOptions options, unsigned maxMatchCount)
{
    BOOL caseFlag = !(options & FindOptionsCaseInsensitive);

    NSUInteger matchCount = [m_wkPDFView.get() _countMatches:string caseSensitive:caseFlag];
    if (matchCount > maxMatchCount)
        matchCount = maxMatchCount;
    page()->didCountStringMatches(string, matchCount);
}

} // namespace WebKit