2 * Copyright (C) 2005 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #ifndef OMIT_TIGER_FEATURES
31 #import <WebKit/WebAssertions.h>
32 #import <WebKit/WebDataSource.h>
33 #import <WebKit/WebDocumentInternal.h>
34 #import <WebKit/WebFrame.h>
35 #import <WebKit/WebLocalizableStrings.h>
36 #import <WebKit/WebNSPasteboardExtras.h>
37 #import <WebKit/WebPDFView.h>
38 #import <WebKit/WebUIDelegate.h>
39 #import <WebKit/WebView.h>
40 #import <WebKit/WebViewPrivate.h>
42 #import <WebKitSystemInterface.h>
43 #import <Quartz/Quartz.h>
45 // QuartzPrivate.h doesn't include the PDFKit private headers, so we can't get at PDFViewPriv.h. (3957971)
46 // Even if that was fixed, we'd have to tweak compile options to include QuartzPrivate.h. (3957839)
48 @interface PDFDocument (PDFKitSecretsIKnow)
49 - (NSPrintOperation *)getPrintOperationForPrintInfo:(NSPrintInfo *)printInfo autoRotate:(BOOL)doRotate;
52 NSString *_NSPathForSystemFramework(NSString *framework);
54 @implementation WebPDFView
56 + (NSBundle *)PDFKitBundle
58 static NSBundle *PDFKitBundle = nil;
59 if (PDFKitBundle == nil) {
60 NSString *PDFKitPath = [_NSPathForSystemFramework(@"Quartz.framework") stringByAppendingString:@"/Frameworks/PDFKit.framework"];
61 if (PDFKitPath == nil) {
62 ERROR("Couldn't find PDFKit.framework");
65 PDFKitBundle = [NSBundle bundleWithPath:PDFKitPath];
66 if (![PDFKitBundle load]) {
67 ERROR("Couldn't load PDFKit.framework");
75 static Class PDFViewClass = nil;
76 if (PDFViewClass == nil) {
77 PDFViewClass = [[WebPDFView PDFKitBundle] classNamed:@"PDFView"];
78 if (PDFViewClass == nil) {
79 ERROR("Couldn't find PDFView class in PDFKit.framework");
85 - (id)initWithFrame:(NSRect)frame
87 self = [super initWithFrame:frame];
89 [self setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
90 PDFSubview = [[[[self class] PDFViewClass] alloc] initWithFrame:frame];
91 [PDFSubview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
92 [self addSubview:PDFSubview];
100 [PDFSubview release];
105 - (PDFView *)PDFSubview
110 #define TEMP_PREFIX "/tmp/XXXXXX-"
111 #define OBJC_TEMP_PREFIX @"/tmp/XXXXXX-"
113 static void applicationInfoForMIMEType(NSString *type, NSString **name, NSImage **image)
117 OSStatus error = LSCopyApplicationForMIMEType((CFStringRef)type, kLSRolesAll, (CFURLRef *)&appURL);
122 NSString *appPath = [appURL path];
125 *image = [[NSWorkspace sharedWorkspace] iconForFile:appPath];
126 [*image setSize:NSMakeSize(16.f,16.f)];
128 NSString *appName = [[NSFileManager defaultManager] displayNameAtPath:appPath];
134 // Generate path once.
138 NSString *filename = [[dataSource response] suggestedFilename];
139 NSFileManager *manager = [NSFileManager defaultManager];
141 path = [@"/tmp/" stringByAppendingPathComponent: filename];
142 if ([manager fileExistsAtPath:path]) {
143 path = [OBJC_TEMP_PREFIX stringByAppendingString:filename];
144 char *cpath = (char *)[path fileSystemRepresentation];
146 int fd = mkstemps (cpath, strlen(cpath) - strlen(TEMP_PREFIX) + 1);
148 // Couldn't create a temporary file! Should never happen. Do
149 // we need an alert here?
154 path = [manager stringWithFileSystemRepresentation:cpath length:strlen(cpath)];
163 - (NSView *)hitTest:(NSPoint)point
165 // Override hitTest so we can override menuForEvent.
166 NSEvent *event = [NSApp currentEvent];
167 NSEventType type = [event type];
168 if (type == NSRightMouseDown || (type == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask))) {
171 return [super hitTest:point];
174 - (NSDictionary *)elementAtPoint:(NSPoint)point
176 WebFrame *frame = [dataSource webFrame];
179 // FIXME 4158121: should determine whether the point is over a selection, and if so set WebElementIsSelectedKey
180 // as in WebTextView.m. Would need to convert coordinates, and make sure that the code that checks
181 // WebElementIsSelectedKey would work with PDF documents.
182 return [NSDictionary dictionaryWithObjectsAndKeys:
183 frame, WebElementFrameKey, nil];
186 - (NSMutableArray *)_menuItemsFromPDFKitForEvent:(NSEvent *)theEvent
188 NSMutableArray *copiedItems = [NSMutableArray array];
189 NSDictionary *actionsToTags = [[NSDictionary alloc] initWithObjectsAndKeys:
190 [NSNumber numberWithInt:WebMenuItemPDFActualSize], NSStringFromSelector(@selector(_setActualSize:)),
191 [NSNumber numberWithInt:WebMenuItemPDFZoomIn], NSStringFromSelector(@selector(zoomIn:)),
192 [NSNumber numberWithInt:WebMenuItemPDFZoomOut], NSStringFromSelector(@selector(zoomOut:)),
193 [NSNumber numberWithInt:WebMenuItemPDFAutoSize], NSStringFromSelector(@selector(_setAutoSize:)),
194 [NSNumber numberWithInt:WebMenuItemPDFSinglePage], NSStringFromSelector(@selector(_setSinglePage:)),
195 [NSNumber numberWithInt:WebMenuItemPDFFacingPages], NSStringFromSelector(@selector(_setDoublePage:)),
196 [NSNumber numberWithInt:WebMenuItemPDFContinuous], NSStringFromSelector(@selector(_toggleContinuous:)),
197 [NSNumber numberWithInt:WebMenuItemPDFNextPage], NSStringFromSelector(@selector(goToNextPage:)),
198 [NSNumber numberWithInt:WebMenuItemPDFPreviousPage], NSStringFromSelector(@selector(goToPreviousPage:)),
201 NSEnumerator *e = [[[PDFSubview menuForEvent:theEvent] itemArray] objectEnumerator];
203 while ((item = [e nextObject]) != nil) {
204 // Copy items since a menu item can be in only one menu at a time, and we don't
205 // want to modify the original menu supplied by PDFKit.
206 NSMenuItem *itemCopy = [item copy];
207 [copiedItems addObject:itemCopy];
209 if ([itemCopy isSeparatorItem]) {
212 NSString *actionString = NSStringFromSelector([itemCopy action]);
213 NSNumber *tagNumber = [actionsToTags objectForKey:actionString];
216 if (tagNumber != nil) {
217 tag = [tagNumber intValue];
219 tag = WebMenuItemTagOther;
220 ERROR("no WebKit menu item tag found for PDF context menu item action \"%@\", using WebMenuItemTagOther", actionString);
222 if ([itemCopy tag] == 0) {
223 [itemCopy setTag:tag];
225 ERROR("PDF context menu item %@ came with tag %d, so no WebKit tag was applied. This could mean that the item doesn't appear in clients such as Safari.", [itemCopy title], [itemCopy tag]);
229 [actionsToTags release];
234 - (BOOL)_anyPDFTagsFoundInMenu:(NSMenu *)menu
236 NSEnumerator *e = [[menu itemArray] objectEnumerator];
238 while ((item = [e nextObject]) != nil) {
239 switch ([item tag]) {
240 case WebMenuItemTagOpenWithDefaultApplication:
241 case WebMenuItemPDFActualSize:
242 case WebMenuItemPDFZoomIn:
243 case WebMenuItemPDFZoomOut:
244 case WebMenuItemPDFAutoSize:
245 case WebMenuItemPDFSinglePage:
246 case WebMenuItemPDFFacingPages:
247 case WebMenuItemPDFContinuous:
248 case WebMenuItemPDFNextPage:
249 case WebMenuItemPDFPreviousPage:
256 - (NSMenu *)menuForEvent:(NSEvent *)theEvent
258 // Start with the menu items supplied by PDFKit, with WebKit tags applied
259 NSMutableArray *items = [self _menuItemsFromPDFKitForEvent:theEvent];
261 // Add in an "Open with <default PDF viewer>" item
262 NSString *appName = nil;
263 NSImage *appIcon = nil;
265 applicationInfoForMIMEType([[dataSource response] MIMEType], &appName, &appIcon);
267 appName = UI_STRING("Finder", "Default application name for Open With context menu");
269 NSString *title = [NSString stringWithFormat:UI_STRING("Open with %@", "context menu item for PDF"), appName];
270 NSMenuItem *item = [[NSMenuItem alloc] initWithTitle:title action:@selector(openWithFinder:) keyEquivalent:@""];
271 [item setTag:WebMenuItemTagOpenWithDefaultApplication];
273 [item setImage:appIcon];
274 [items insertObject:item atIndex:0];
277 [items insertObject:[NSMenuItem separatorItem] atIndex:1];
279 // pass the items off to the WebKit context menu mechanism
280 WebView *webView = [[dataSource webFrame] webView];
282 // Currently clicks anywhere in the PDF view are treated the same, so we just pass NSZeroPoint;
283 // we implement elementAtPoint: here just to be slightly forward-looking.
284 NSMenu *menu = [webView _menuForElement:[self elementAtPoint:NSZeroPoint] defaultItems:items];
286 // The delegate has now had the opportunity to add items to the standard PDF-related items, or to
287 // remove or modify some of the PDF-related items. In 10.4, the PDF context menu did not go through
288 // the standard WebKit delegate path, and so the standard PDF-related items always appeared. For
289 // clients that create their own context menu by hand-picking specific items from the default list, such as
290 // Safari, none of the PDF-related items will appear until the client is rewritten to explicitly
291 // include these items. So for backwards compatibility we're going to include the entire set of PDF-related
292 // items if the executable was linked in 10.4 or earlier and the menu returned from the delegate mechanism
293 // contains none of the PDF-related items.
294 if (WKExecutableLinkedInTigerOrEarlier()) {
295 if (![self _anyPDFTagsFoundInMenu:menu]) {
296 [menu addItem:[NSMenuItem separatorItem]];
297 NSEnumerator *e = [items objectEnumerator];
298 NSMenuItem *menuItem;
299 while ((menuItem = [e nextObject]) != nil) {
300 // copy menuItem since a given menuItem can be in only one menu at a time, and we don't
301 // want to mess with the menu returned from PDFKit.
302 [menu addItem:[menuItem copy]];
310 - (void)_updateScalingToReflectTextSize
312 WebView *view = [[dataSource webFrame] webView];
314 // The scale factor and text size multiplier conveniently use the same units, so we can just
315 // treat the values as interchangeable.
317 [PDFSubview setScaleFactor:[view textSizeMultiplier]];
321 - (void)setDataSource:(WebDataSource *)ds
324 [self setFrame:[[self superview] frame]];
325 [self _updateScalingToReflectTextSize];
328 - (void)dataSourceUpdated:(WebDataSource *)dataSource
332 - (void)setNeedsLayout:(BOOL)flag
340 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
344 - (void)viewDidMoveToHostWindow
348 - (void)openWithFinder:(id)sender
350 NSString *opath = [self path];
354 [[dataSource data] writeToFile:opath atomically:YES];
358 if (![[NSWorkspace sharedWorkspace] openFile:opath]) {
359 // NSWorkspace couldn't open file. Do we need an alert
360 // here? We ignore the error elsewhere.
365 - (void)_web_textSizeMultiplierChanged
367 [self _updateScalingToReflectTextSize];
370 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag;
374 options |= NSBackwardsSearch;
377 options |= NSCaseInsensitiveSearch;
379 PDFDocument *document = [PDFSubview document];
380 PDFSelection *selection = [document findString:string fromSelection:[PDFSubview currentSelection] withOptions:options];
381 if (selection == nil && wrapFlag) {
382 selection = [document findString:string fromSelection:nil withOptions:options];
384 if (selection != nil) {
385 [PDFSubview setCurrentSelection:selection];
386 [PDFSubview scrollSelectionToVisible:nil];
392 - (void)takeFindStringFromSelection:(id)sender
394 [NSPasteboard _web_setFindPasteboardString:[[PDFSubview currentSelection] string] withOwner:self];
397 - (void)jumpToSelection:(id)sender
399 [PDFSubview scrollSelectionToVisible:nil];
402 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item
404 SEL action = [item action];
405 if (action == @selector(takeFindStringFromSelection:) || action == @selector(jumpToSelection:)) {
406 return [PDFSubview currentSelection] != nil;
411 - (BOOL)canPrintHeadersAndFooters
416 - (NSPrintOperation *)printOperationWithPrintInfo:(NSPrintInfo *)printInfo
418 return [[PDFSubview document] getPrintOperationForPrintInfo:printInfo autoRotate:YES];
423 #endif // OMIT_TIGER_FEATURES