Reviewed by Trey.
[WebKit-https.git] / WebKit / WebView.subproj / WebHTMLView.m
1 /*
2     WebHTMLView.m
3     Copyright 2002, Apple, Inc. All rights reserved.
4 */
5
6 #import <WebKit/WebHTMLView.h>
7
8 #import <WebKit/DOM.h>
9 #import <WebKit/DOMExtensions.h>
10 #import <WebKit/WebArchive.h>
11 #import <WebKit/WebBridge.h>
12 #import <WebKit/WebClipView.h>
13 #import <WebKit/WebDataProtocol.h>
14 #import <WebKit/WebDataSourcePrivate.h>
15 #import <WebKit/WebDocumentInternal.h>
16 #import <WebKit/WebDOMOperations.h>
17 #import <WebKit/WebEditingDelegate.h>
18 #import <WebKit/WebException.h>
19 #import <WebKit/WebFramePrivate.h>
20 #import <WebKit/WebFrameViewPrivate.h>
21 #import <WebKit/WebHTMLViewInternal.h>
22 #import <WebKit/WebHTMLRepresentationPrivate.h>
23 #import <WebKit/WebImageRenderer.h>
24 #import <WebKit/WebImageRendererFactory.h>
25 #import <WebKit/WebKitLogging.h>
26 #import <WebKit/WebKitNSStringExtras.h>
27 #import <WebKit/WebNetscapePluginEmbeddedView.h>
28 #import <WebKit/WebNSEventExtras.h>
29 #import <WebKit/WebNSImageExtras.h>
30 #import <WebKit/WebNSObjectExtras.h>
31 #import <WebKit/WebNSPasteboardExtras.h>
32 #import <WebKit/WebNSPrintOperationExtras.h>
33 #import <WebKit/WebNSURLExtras.h>
34 #import <WebKit/WebNSViewExtras.h>
35 #import <WebKit/WebPluginController.h>
36 #import <WebKit/WebPreferences.h>
37 #import <WebKit/WebResourcePrivate.h>
38 #import <WebKit/WebStringTruncator.h>
39 #import <WebKit/WebTextRenderer.h>
40 #import <WebKit/WebTextRendererFactory.h>
41 #import <WebKit/WebUIDelegatePrivate.h>
42 #import <WebKit/WebUnicode.h>
43 #import <WebKit/WebViewPrivate.h>
44
45 #import <AppKit/NSAccessibility.h>
46 #import <AppKit/NSGraphicsContextPrivate.h>
47 #import <AppKit/NSResponder_Private.h>
48
49 #import <Foundation/NSFileManager_NSURLExtras.h>
50 #import <Foundation/NSURL_NSURLExtras.h>
51 #import <Foundation/NSURLFileTypeMappings.h>
52
53 #import <CoreGraphics/CGContextGState.h>
54
55 // By imaging to a width a little wider than the available pixels,
56 // thin pages will be scaled down a little, matching the way they
57 // print in IE and Camino. This lets them use fewer sheets than they
58 // would otherwise, which is presumably why other browsers do this.
59 // Wide pages will be scaled down more than this.
60 #define PrintingMinimumShrinkFactor     1.25
61
62 // This number determines how small we are willing to reduce the page content
63 // in order to accommodate the widest line. If the page would have to be
64 // reduced smaller to make the widest line fit, we just clip instead (this
65 // behavior matches MacIE and Mozilla, at least)
66 #define PrintingMaximumShrinkFactor     2.0
67
68 #define AUTOSCROLL_INTERVAL             0.1
69
70 #define DRAG_LABEL_BORDER_X             4.0
71 #define DRAG_LABEL_BORDER_Y             2.0
72 #define DRAG_LABEL_RADIUS               5.0
73 #define DRAG_LABEL_BORDER_Y_OFFSET              2.0
74
75 #define MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP        120.0
76 #define MAX_DRAG_LABEL_WIDTH                    320.0
77
78 #define DRAG_LINK_LABEL_FONT_SIZE   11.0
79 #define DRAG_LINK_URL_FONT_SIZE   10.0
80
81 static BOOL forceRealHitTest = NO;
82
83 @interface WebHTMLView (WebTextSizing) <_web_WebDocumentTextSizing>
84 @end
85
86 @interface WebHTMLView (WebFileInternal)
87 - (BOOL)_imageExistsAtPaths:(NSArray *)paths;
88 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
89 - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText;
90 - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action;
91 @end
92
93 @interface WebHTMLView (WebHTMLViewPrivate)
94 - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize;
95 - (void)_updateTextSizeMultiplier;
96 - (float)_calculatePrintHeight;
97 - (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation;
98 @end
99
100 // Any non-zero value will do, but using somethign recognizable might help us debug some day.
101 #define TRACKING_RECT_TAG 0xBADFACE
102
103 @interface NSView (AppKitSecretsIKnowAbout)
104 - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView;
105 - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect;
106 - (NSRect)_dirtyRect;
107 - (void)_setDrawsOwnDescendants:(BOOL)drawsOwnDescendants;
108 @end
109
110 @interface NSApplication (AppKitSecretsIKnowAbout)
111 - (void)speakString:(NSString *)string;
112 @end
113
114 @interface NSWindow (AppKitSecretsIKnowAbout)
115 - (id)_newFirstResponderAfterResigning;
116 @end
117
118 @interface NSView (WebHTMLViewPrivate)
119 - (void)_web_setPrintingModeRecursive;
120 - (void)_web_clearPrintingModeRecursive;
121 - (void)_web_layoutIfNeededRecursive:(NSRect)rect testDirtyRect:(bool)testDirtyRect;
122 @end
123
124 @interface NSMutableDictionary (WebHTMLViewPrivate)
125 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key;
126 @end
127
128 @interface WebElementOrTextFilter : NSObject <DOMNodeFilter>
129 + (WebElementOrTextFilter *)filter;
130 @end
131
132 static WebElementOrTextFilter *elementOrTextFilterInstance = nil;
133
134 @implementation WebHTMLViewPrivate
135
136 - (void)dealloc
137 {
138     ASSERT(autoscrollTimer == nil);
139     ASSERT(autoscrollTriggerEvent == nil);
140     
141     [mouseDownEvent release];
142     [draggingImageURL release];
143     [pluginController release];
144     [toolTip release];
145     
146     [super dealloc];
147 }
148
149 @end
150
151 @implementation WebHTMLView (WebFileInternal)
152
153 - (BOOL)_imageExistsAtPaths:(NSArray *)paths
154 {
155     NSURLFileTypeMappings *mappings = [NSURLFileTypeMappings sharedMappings];
156     NSArray *imageMIMETypes = [[WebImageRendererFactory sharedFactory] supportedMIMETypes];
157     NSEnumerator *enumerator = [paths objectEnumerator];
158     NSString *path;
159     
160     while ((path = [enumerator nextObject]) != nil) {
161         NSString *MIMEType = [mappings MIMETypeForExtension:[path pathExtension]];
162         if ([imageMIMETypes containsObject:MIMEType]) {
163             return YES;
164         }
165     }
166     
167     return NO;
168 }
169
170 - (DOMDocumentFragment *)_documentFragmentWithPaths:(NSArray *)paths
171 {
172     DOMDocumentFragment *fragment = [[[self _bridge] DOMDocument] createDocumentFragment];
173     NSURLFileTypeMappings *mappings = [NSURLFileTypeMappings sharedMappings];
174     NSArray *imageMIMETypes = [[WebImageRendererFactory sharedFactory] supportedMIMETypes];
175     NSEnumerator *enumerator = [paths objectEnumerator];
176     WebDataSource *dataSource = [self _dataSource];
177     NSString *path;
178     
179     while ((path = [enumerator nextObject]) != nil) {
180         NSString *MIMEType = [mappings MIMETypeForExtension:[path pathExtension]];
181         if ([imageMIMETypes containsObject:MIMEType]) {
182             WebResource *resource = [[WebResource alloc] initWithData:[NSData dataWithContentsOfFile:path]
183                                                                   URL:[NSURL fileURLWithPath:path]
184                                                              MIMEType:MIMEType 
185                                                      textEncodingName:nil
186                                                             frameName:nil];
187             if (resource) {
188                 [fragment appendChild:[dataSource _imageElementWithImageResource:resource]];
189                 [resource release];
190             }
191         }
192     }
193     
194     return [fragment firstChild] != nil ? fragment : nil;
195 }
196
197 - (DOMDocumentFragment *)_documentFragmentFromPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText
198 {
199     NSArray *types = [pasteboard types];
200
201     if ([types containsObject:WebArchivePboardType]) {
202         WebArchive *archive = [[WebArchive alloc] initWithData:[pasteboard dataForType:WebArchivePboardType]];
203         if (archive) {
204             DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithArchive:archive];
205             [archive release];
206             if (fragment) {
207                 return fragment;
208             }
209         }
210     }
211     
212     if ([types containsObject:NSFilenamesPboardType]) {
213         DOMDocumentFragment *fragment = [self _documentFragmentWithPaths:[pasteboard propertyListForType:NSFilenamesPboardType]];
214         if (fragment != nil) {
215             return fragment;
216         }
217     }
218     
219     NSURL *URL;
220     
221     if ([types containsObject:NSHTMLPboardType]) {
222         return [[self _bridge] documentFragmentWithMarkupString:[pasteboard stringForType:NSHTMLPboardType] baseURLString:nil];
223     } else if ([types containsObject:NSTIFFPboardType]) {
224         WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSTIFFPboardType]
225                                                               URL:[NSURL _web_uniqueWebDataURLWithRelativeString:@"/image.tiff"]
226                                                          MIMEType:@"image/tiff" 
227                                                  textEncodingName:nil
228                                                         frameName:nil];
229         DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
230         [resource release];
231         return fragment;
232     } else if ([types containsObject:NSPICTPboardType]) {
233         WebResource *resource = [[WebResource alloc] initWithData:[pasteboard dataForType:NSPICTPboardType]
234                                                               URL:[NSURL _web_uniqueWebDataURLWithRelativeString:@"/image.pict"]
235                                                          MIMEType:@"image/pict" 
236                                                  textEncodingName:nil
237                                                         frameName:nil];
238         DOMDocumentFragment *fragment = [[self _dataSource] _documentFragmentWithImageResource:resource];
239         [resource release];
240         return fragment;
241     } else if ((URL = [pasteboard _web_bestURL])) {
242         NSString *URLString = [URL _web_originalDataAsString];
243         NSString *linkLabel = [pasteboard stringForType:WebURLNamePboardType];
244         linkLabel = [linkLabel length] > 0 ? linkLabel : URLString;
245
246         DOMDocument *document = [[self _bridge] DOMDocument];
247         DOMDocumentFragment *fragment = [document createDocumentFragment];
248         DOMElement *anchorElement = [document createElement:@"a"];
249         [anchorElement setAttribute:@"href" :URLString];
250         [fragment appendChild:anchorElement];
251         [anchorElement appendChild:[document createTextNode:linkLabel]];
252         return fragment;
253     } else if ([types containsObject:NSRTFDPboardType]) {
254         // FIXME: Support RTFD to HTML (or DOM) conversion.
255         ERROR("RTFD to HTML conversion not yet supported.");
256         return [[self _bridge] documentFragmentWithText:[pasteboard stringForType:NSStringPboardType]];
257     } else if ([types containsObject:NSRTFPboardType]) {
258         // FIXME: Support RTF to HTML (or DOM) conversion.
259         ERROR("RTF to HTML conversion not yet supported.");
260         return [[self _bridge] documentFragmentWithText:[pasteboard stringForType:NSStringPboardType]];
261     } else if (allowPlainText && [types containsObject:NSStringPboardType]) {
262         return [[self _bridge] documentFragmentWithText:[pasteboard stringForType:NSStringPboardType]];
263     }
264     
265     return nil;
266 }
267
268 - (void)_pasteWithPasteboard:(NSPasteboard *)pasteboard allowPlainText:(BOOL)allowPlainText
269 {
270     DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:pasteboard allowPlainText:allowPlainText];
271     WebBridge *bridge = [self _bridge];
272     if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[bridge selectedDOMRange] givenAction:WebViewInsertActionPasted]) {
273         [bridge replaceSelectionWithFragment:fragment selectReplacement:NO];
274     }
275 }
276
277 - (BOOL)_shouldInsertFragment:(DOMDocumentFragment *)fragment replacingDOMRange:(DOMRange *)range givenAction:(WebViewInsertAction)action
278 {
279     WebView *webView = [self _webView];
280     DOMNode *child = [fragment firstChild];
281     if ([fragment lastChild] == child && [child isKindOfClass:[DOMCharacterData class]]) {
282         return [[webView _editingDelegateForwarder] webView:webView shouldInsertText:[(DOMCharacterData *)child data] replacingDOMRange:range givenAction:action];
283     } else {
284         return [[webView _editingDelegateForwarder] webView:webView shouldInsertNode:fragment replacingDOMRange:range givenAction:action];
285     }
286 }
287
288 @end
289
290 @implementation WebHTMLView (WebPrivate)
291
292 - (void)_reset
293 {
294     [WebImageRenderer stopAnimationsInView:self];
295 }
296
297 - (WebView *)_webView
298 {
299     // We used to use the view hierarchy exclusively here, but that won't work
300     // right when the first viewDidMoveToSuperview call is done, and this wil.
301     return [[self _frame] webView];
302 }
303
304 - (WebFrame *)_frame
305 {
306     WebFrameView *webFrameView = [self _web_parentWebFrameView];
307     return [webFrameView webFrame];
308 }
309
310 // Required so view can access the part's selection.
311 - (WebBridge *)_bridge
312 {
313     return [[self _frame] _bridge];
314 }
315
316 - (WebDataSource *)_dataSource
317 {
318     return [[self _frame] dataSource];
319 }
320
321 + (void)_postFlagsChangedEvent:(NSEvent *)flagsChangedEvent
322 {
323     NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
324         location:[[flagsChangedEvent window] convertScreenToBase:[NSEvent mouseLocation]]
325         modifierFlags:[flagsChangedEvent modifierFlags]
326         timestamp:[flagsChangedEvent timestamp]
327         windowNumber:[flagsChangedEvent windowNumber]
328         context:[flagsChangedEvent context]
329         eventNumber:0 clickCount:0 pressure:0];
330
331     // Pretend it's a mouse move.
332     [[NSNotificationCenter defaultCenter]
333         postNotificationName:NSMouseMovedNotification object:self
334         userInfo:[NSDictionary dictionaryWithObject:fakeEvent forKey:@"NSEvent"]];
335 }
336
337 - (void)_updateMouseoverWithFakeEvent
338 {
339     NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
340         location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
341         modifierFlags:[[NSApp currentEvent] modifierFlags]
342         timestamp:[NSDate timeIntervalSinceReferenceDate]
343         windowNumber:[[self window] windowNumber]
344         context:[[NSApp currentEvent] context]
345         eventNumber:0 clickCount:0 pressure:0];
346     
347     [self _updateMouseoverWithEvent:fakeEvent];
348 }
349
350 - (void)_frameOrBoundsChanged
351 {
352     if (!NSEqualSizes(_private->lastLayoutSize, [(NSClipView *)[self superview] documentVisibleRect].size)) {
353         [self setNeedsLayout:YES];
354         [self setNeedsDisplay:YES];
355     }
356
357     NSPoint origin = [[self superview] bounds].origin;
358     if (!NSEqualPoints(_private->lastScrollPosition, origin))
359         [[self _bridge] sendScrollEvent];
360     _private->lastScrollPosition = origin;
361
362     SEL selector = @selector(_updateMouseoverWithFakeEvent);
363     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:selector object:nil];
364     [self performSelector:selector withObject:nil afterDelay:0];
365 }
366
367 - (void)_setAsideSubviews
368 {
369     ASSERT(!_private->subviewsSetAside);
370     ASSERT(_private->savedSubviews == nil);
371     _private->savedSubviews = _subviews;
372     _subviews = nil;
373     _private->subviewsSetAside = YES;
374  }
375  
376  - (void)_restoreSubviews
377  {
378     ASSERT(_private->subviewsSetAside);
379     ASSERT(_subviews == nil);
380     _subviews = _private->savedSubviews;
381     _private->savedSubviews = nil;
382     _private->subviewsSetAside = NO;
383 }
384
385 // Don't let AppKit even draw subviews. We take care of that.
386 - (void)_recursiveDisplayRectIfNeededIgnoringOpacity:(NSRect)rect isVisibleRect:(BOOL)isVisibleRect rectIsVisibleRectForView:(NSView *)visibleView topView:(BOOL)topView
387 {
388     // This helps when we print as part of a larger print process.
389     // If the WebHTMLView itself is what we're printing, then we will never have to do this.
390     BOOL wasInPrintingMode = _private->printing;
391     BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
392     if (wasInPrintingMode != isPrinting) {
393         if (isPrinting) {
394             [self _web_setPrintingModeRecursive];
395         } else {
396             [self _web_clearPrintingModeRecursive];
397         }
398     }
399
400     [self _web_layoutIfNeededRecursive: rect testDirtyRect:YES];
401
402     [self _setAsideSubviews];
403     [super _recursiveDisplayRectIfNeededIgnoringOpacity:rect isVisibleRect:isVisibleRect
404         rectIsVisibleRectForView:visibleView topView:topView];
405     [self _restoreSubviews];
406
407     if (wasInPrintingMode != isPrinting) {
408         if (wasInPrintingMode) {
409             [self _web_setPrintingModeRecursive];
410         } else {
411             [self _web_clearPrintingModeRecursive];
412         }
413     }
414 }
415
416 // Don't let AppKit even draw subviews. We take care of that.
417 - (void)_recursiveDisplayAllDirtyWithLockFocus:(BOOL)needsLockFocus visRect:(NSRect)visRect
418 {
419     BOOL needToSetAsideSubviews = !_private->subviewsSetAside;
420
421     BOOL wasInPrintingMode = _private->printing;
422     BOOL isPrinting = ![NSGraphicsContext currentContextDrawingToScreen];
423
424     if (needToSetAsideSubviews) {
425         // This helps when we print as part of a larger print process.
426         // If the WebHTMLView itself is what we're printing, then we will never have to do this.
427         if (wasInPrintingMode != isPrinting) {
428             if (isPrinting) {
429                 [self _web_setPrintingModeRecursive];
430             } else {
431                 [self _web_clearPrintingModeRecursive];
432             }
433         }
434
435         [self _web_layoutIfNeededRecursive: visRect testDirtyRect:NO];
436
437         [self _setAsideSubviews];
438     }
439     
440     [super _recursiveDisplayAllDirtyWithLockFocus:needsLockFocus visRect:visRect];
441     
442     if (needToSetAsideSubviews) {
443         if (wasInPrintingMode != isPrinting) {
444             if (wasInPrintingMode) {
445                 [self _web_setPrintingModeRecursive];
446             } else {
447                 [self _web_clearPrintingModeRecursive];
448             }
449         }
450
451         [self _restoreSubviews];
452     }
453 }
454
455 - (BOOL)_insideAnotherHTMLView
456 {
457     NSView *view = self;
458     while ((view = [view superview])) {
459         if ([view isKindOfClass:[WebHTMLView class]]) {
460             return YES;
461         }
462     }
463     return NO;
464 }
465
466 - (void)scrollPoint:(NSPoint)point
467 {
468     // Since we can't subclass NSTextView to do what we want, we have to second guess it here.
469     // If we get called during the handling of a key down event, we assume the call came from
470     // NSTextView, and ignore it and use our own code to decide how to page up and page down
471     // We are smarter about how far to scroll, and we have "superview scrolling" logic.
472     NSEvent *event = [[self window] currentEvent];
473     if ([event type] == NSKeyDown) {
474         const unichar pageUp = NSPageUpFunctionKey;
475         if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageUp length:1]].length == 1) {
476             [self tryToPerform:@selector(scrollPageUp:) with:nil];
477             return;
478         }
479         const unichar pageDown = NSPageDownFunctionKey;
480         if ([[event characters] rangeOfString:[NSString stringWithCharacters:&pageDown length:1]].length == 1) {
481             [self tryToPerform:@selector(scrollPageDown:) with:nil];
482             return;
483         }
484     }
485     
486     [super scrollPoint:point];
487 }
488
489 - (NSView *)hitTest:(NSPoint)point
490 {
491     // WebHTMLView objects handle all left mouse clicks for objects inside them.
492     // That does not include left mouse clicks with the control key held down.
493     BOOL captureHitsOnSubviews;
494     if (forceRealHitTest) {
495         captureHitsOnSubviews = NO;
496     } else {
497         NSEvent *event = [[self window] currentEvent];
498         captureHitsOnSubviews = [event type] == NSLeftMouseDown && ([event modifierFlags] & NSControlKeyMask) == 0;
499     }
500     if (!captureHitsOnSubviews) {
501         return [super hitTest:point];
502     }
503     if ([[self superview] mouse:point inRect:[self frame]]) {
504         return self;
505     }
506     return nil;
507 }
508
509 static WebHTMLView *lastHitView = nil;
510
511 - (void)_clearLastHitViewIfSelf
512 {
513     if (lastHitView == self) {
514         lastHitView = nil;
515     }
516 }
517
518 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside
519 {
520     ASSERT(_private->trackingRectOwner == nil);
521     _private->trackingRectOwner = owner;
522     _private->trackingRectUserData = data;
523     return TRACKING_RECT_TAG;
524 }
525
526 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect owner:(id)owner userData:(void *)data assumeInside:(BOOL)assumeInside useTrackingNum:(int)tag
527 {
528     ASSERT(tag == TRACKING_RECT_TAG);
529     return [self addTrackingRect:rect owner:owner userData:data assumeInside:assumeInside];
530 }
531
532 - (void)removeTrackingRect:(NSTrackingRectTag)tag
533 {
534     ASSERT(tag == TRACKING_RECT_TAG);
535     if (_private != nil) {
536         ASSERT(_private->trackingRectOwner != nil);
537         _private->trackingRectOwner = nil;
538     }
539 }
540
541 - (void)_sendToolTipMouseExited
542 {
543     // Nothing matters except window, trackingNumber, and userData.
544     NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseExited
545         location:NSMakePoint(0, 0)
546         modifierFlags:0
547         timestamp:0
548         windowNumber:[[self window] windowNumber]
549         context:NULL
550         eventNumber:0
551         trackingNumber:TRACKING_RECT_TAG
552         userData:_private->trackingRectUserData];
553     [_private->trackingRectOwner mouseExited:fakeEvent];
554 }
555
556 - (void)_sendToolTipMouseEntered
557 {
558     // Nothing matters except window, trackingNumber, and userData.
559     NSEvent *fakeEvent = [NSEvent enterExitEventWithType:NSMouseEntered
560         location:NSMakePoint(0, 0)
561         modifierFlags:0
562         timestamp:0
563         windowNumber:[[self window] windowNumber]
564         context:NULL
565         eventNumber:0
566         trackingNumber:TRACKING_RECT_TAG
567         userData:_private->trackingRectUserData];
568     [_private->trackingRectOwner mouseEntered:fakeEvent];
569 }
570
571 - (void)_setToolTip:(NSString *)string
572 {
573     NSString *toolTip = [string length] == 0 ? nil : string;
574     NSString *oldToolTip = _private->toolTip;
575     if ((toolTip == nil || oldToolTip == nil) ? toolTip == oldToolTip : [toolTip isEqualToString:oldToolTip]) {
576         return;
577     }
578     if (oldToolTip) {
579         [self _sendToolTipMouseExited];
580         [oldToolTip release];
581     }
582     _private->toolTip = [toolTip copy];
583     if (toolTip) {
584         [self removeAllToolTips];
585         NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
586         [self addToolTipRect:wideOpenRect owner:self userData:NULL];
587         [self _sendToolTipMouseEntered];
588     }
589 }
590
591 - (NSString *)view:(NSView *)view stringForToolTip:(NSToolTipTag)tag point:(NSPoint)point userData:(void *)data
592 {
593     return [[_private->toolTip copy] autorelease];
594 }
595
596 - (void)_updateMouseoverWithEvent:(NSEvent *)event
597 {
598     WebHTMLView *view = nil;
599     if ([event window] == [self window]) {
600         forceRealHitTest = YES;
601         NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]];
602         forceRealHitTest = NO;
603         while (hitView) {
604             if ([hitView isKindOfClass:[WebHTMLView class]]) {
605                 view = (WebHTMLView *)hitView;
606                 break;
607             }
608             hitView = [hitView superview];
609         }
610     }
611
612     if (lastHitView != view && lastHitView != nil) {
613         // If we are moving out of a view (or frame), let's pretend the mouse moved
614         // all the way out of that view. But we have to account for scrolling, because
615         // khtml doesn't understand our clipping.
616         NSRect visibleRect = [[[[lastHitView _frame] frameView] _scrollView] documentVisibleRect];
617         float yScroll = visibleRect.origin.y;
618         float xScroll = visibleRect.origin.x;
619
620         event = [NSEvent mouseEventWithType:NSMouseMoved
621                          location:NSMakePoint(-1 - xScroll, -1 - yScroll )
622                          modifierFlags:[[NSApp currentEvent] modifierFlags]
623                          timestamp:[NSDate timeIntervalSinceReferenceDate]
624                          windowNumber:[[self window] windowNumber]
625                          context:[[NSApp currentEvent] context]
626                          eventNumber:0 clickCount:0 pressure:0];
627         [[lastHitView _bridge] mouseMoved:event];
628     }
629
630     lastHitView = view;
631     
632     NSDictionary *element;
633     if (view == nil) {
634         element = nil;
635     } else {
636         [[view _bridge] mouseMoved:event];
637
638         NSPoint point = [view convertPoint:[event locationInWindow] fromView:nil];
639         element = [view elementAtPoint:point];
640     }
641
642     // Have the web view send a message to the delegate so it can do status bar display.
643     [[self _webView] _mouseDidMoveOverElement:element modifierFlags:[event modifierFlags]];
644
645     // Set a tool tip; it won't show up right away but will if the user pauses.
646     [self _setToolTip:[element objectForKey:WebCoreElementTitleKey]];
647 }
648
649 + (NSArray *)_insertablePasteboardTypes
650 {
651     static NSArray *types = nil;
652     if (!types) {
653         types = [[NSArray alloc] initWithObjects:WebArchivePboardType, NSHTMLPboardType,
654             NSFilenamesPboardType, NSTIFFPboardType, NSPICTPboardType, NSURLPboardType, 
655             NSRTFDPboardType, NSRTFPboardType, NSStringPboardType, nil];
656     }
657     return types;
658 }
659
660 + (NSArray *)_selectionPasteboardTypes
661 {
662     // FIXME: We should put data for NSHTMLPboardType on the pasteboard but Microsoft Excel doesn't like our format of HTML (3640423).
663     return [NSArray arrayWithObjects:WebArchivePboardType, NSRTFPboardType, NSRTFDPboardType, NSStringPboardType, nil];
664 }
665
666 - (WebArchive *)_selectedArchive
667 {
668     NSArray *nodes;
669     NSString *markupString = [[self _bridge] markupStringFromRange:[[self _bridge] selectedDOMRange] nodes:&nodes];
670     return [[self _dataSource] _archiveWithMarkupString:markupString nodes:nodes];
671 }
672
673 - (NSData *)_selectedRTFData
674 {
675     NSAttributedString *attributedString = [self selectedAttributedString];
676     NSRange range = NSMakeRange(0, [attributedString length]);
677     return [attributedString RTFFromRange:range documentAttributes:nil];
678 }
679
680 - (void)_writeSelectionToPasteboard:(NSPasteboard *)pasteboard
681 {
682     ASSERT([self _haveSelection]);
683     NSArray *types = [[self class] _selectionPasteboardTypes];
684     [pasteboard declareTypes:types owner:nil];
685     [self writeSelectionWithPasteboardTypes:types toPasteboard:pasteboard];
686 }
687
688 - (BOOL)_haveSelection
689 {
690     return [[self _bridge] haveSelection];
691 }
692
693 - (BOOL)_canDelete
694 {
695     return [self _haveSelection] && [[self _bridge] isSelectionEditable];
696 }
697
698 - (BOOL)_canPaste
699 {
700     return [[self _bridge] isSelectionEditable];
701 }
702
703 - (NSImage *)_dragImageForLinkElement:(NSDictionary *)element
704 {
705     NSURL *linkURL = [element objectForKey: WebElementLinkURLKey];
706
707     BOOL drawURLString = YES;
708     BOOL clipURLString = NO, clipLabelString = NO;
709     
710     NSString *label = [element objectForKey: WebElementLinkLabelKey];
711     NSString *urlString = [linkURL _web_userVisibleString];
712     
713     if (!label) {
714         drawURLString = NO;
715         label = urlString;
716     }
717     
718     NSFont *labelFont = [[NSFontManager sharedFontManager] convertFont:[NSFont systemFontOfSize:DRAG_LINK_LABEL_FONT_SIZE]
719                                                    toHaveTrait:NSBoldFontMask];
720     NSFont *urlFont = [NSFont systemFontOfSize: DRAG_LINK_URL_FONT_SIZE];
721     NSSize labelSize;
722     labelSize.width = [label _web_widthWithFont: labelFont];
723     labelSize.height = [labelFont ascender] - [labelFont descender];
724     if (labelSize.width > MAX_DRAG_LABEL_WIDTH){
725         labelSize.width = MAX_DRAG_LABEL_WIDTH;
726         clipLabelString = YES;
727     }
728     
729     NSSize imageSize, urlStringSize;
730     imageSize.width = labelSize.width + DRAG_LABEL_BORDER_X * 2;
731     imageSize.height = labelSize.height + DRAG_LABEL_BORDER_Y * 2;
732     if (drawURLString) {
733         urlStringSize.width = [urlString _web_widthWithFont: urlFont];
734         urlStringSize.height = [urlFont ascender] - [urlFont descender];
735         imageSize.height += urlStringSize.height;
736         if (urlStringSize.width > MAX_DRAG_LABEL_WIDTH) {
737             imageSize.width = MAX(MAX_DRAG_LABEL_WIDTH + DRAG_LABEL_BORDER_X * 2, MIN_DRAG_LABEL_WIDTH_BEFORE_CLIP);
738             clipURLString = YES;
739         } else {
740             imageSize.width = MAX(labelSize.width + DRAG_LABEL_BORDER_X * 2, urlStringSize.width + DRAG_LABEL_BORDER_X * 2);
741         }
742     }
743     NSImage *dragImage = [[[NSImage alloc] initWithSize: imageSize] autorelease];
744     [dragImage lockFocus];
745     
746     [[NSColor colorWithCalibratedRed: 0.7 green: 0.7 blue: 0.7 alpha: 0.8] set];
747     
748     // Drag a rectangle with rounded corners/
749     NSBezierPath *path = [NSBezierPath bezierPath];
750     [path appendBezierPathWithOvalInRect: NSMakeRect(0,0, DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2)];
751     [path appendBezierPathWithOvalInRect: NSMakeRect(0,imageSize.height - DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2)];
752     [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2, imageSize.height - DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2)];
753     [path appendBezierPathWithOvalInRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS * 2,0, DRAG_LABEL_RADIUS * 2, DRAG_LABEL_RADIUS * 2)];
754     
755     [path appendBezierPathWithRect: NSMakeRect(DRAG_LABEL_RADIUS, 0, imageSize.width - DRAG_LABEL_RADIUS * 2, imageSize.height)];
756     [path appendBezierPathWithRect: NSMakeRect(0, DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 10, imageSize.height - 2 * DRAG_LABEL_RADIUS)];
757     [path appendBezierPathWithRect: NSMakeRect(imageSize.width - DRAG_LABEL_RADIUS - 20,DRAG_LABEL_RADIUS, DRAG_LABEL_RADIUS + 20, imageSize.height - 2 * DRAG_LABEL_RADIUS)];
758     [path fill];
759         
760     NSColor *topColor = [NSColor colorWithCalibratedWhite:0.0 alpha:0.75];
761     NSColor *bottomColor = [NSColor colorWithCalibratedWhite:1.0 alpha:0.5];
762     if (drawURLString) {
763         if (clipURLString)
764             urlString = [WebStringTruncator centerTruncateString: urlString toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2) withFont:urlFont];
765
766         [urlString _web_drawDoubledAtPoint:NSMakePoint(DRAG_LABEL_BORDER_X, DRAG_LABEL_BORDER_Y - [urlFont descender]) 
767              withTopColor:topColor bottomColor:bottomColor font:urlFont];
768     }
769
770     if (clipLabelString)
771         label = [WebStringTruncator rightTruncateString: label toWidth:imageSize.width - (DRAG_LABEL_BORDER_X * 2) withFont:labelFont];
772     [label _web_drawDoubledAtPoint:NSMakePoint (DRAG_LABEL_BORDER_X, imageSize.height - DRAG_LABEL_BORDER_Y_OFFSET - [labelFont pointSize])
773              withTopColor:topColor bottomColor:bottomColor font:labelFont];
774     
775     [dragImage unlockFocus];
776     
777     return dragImage;
778 }
779
780 - (BOOL)_startDraggingImage:(NSImage *)wcDragImage at:(NSPoint)wcDragLoc operation:(NSDragOperation)op event:(NSEvent *)mouseDraggedEvent sourceIsDHTML:(BOOL)srcIsDHTML DHTMLWroteData:(BOOL)dhtmlWroteData
781 {
782     NSPoint mouseDownPoint = [self convertPoint:[_private->mouseDownEvent locationInWindow] fromView:nil];
783     NSDictionary *element = [self elementAtPoint:mouseDownPoint];
784
785     NSURL *linkURL = [element objectForKey:WebElementLinkURLKey];
786     NSURL *imageURL = [element objectForKey:WebElementImageURLKey];
787     BOOL isSelected = [[element objectForKey:WebElementIsSelectedKey] boolValue];
788
789     [_private->draggingImageURL release];
790     _private->draggingImageURL = nil;
791
792     NSPoint mouseDraggedPoint = [self convertPoint:[mouseDraggedEvent locationInWindow] fromView:nil];
793     _private->webCoreDragOp = op;     // will be DragNone if WebCore doesn't care
794     NSImage *dragImage = nil;
795     NSPoint dragLoc;
796
797     // We allow WebCore to override the drag image, even if its a link, image or text we're dragging.
798     // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp.
799     // We could verify that ActionDHTML is allowed, although WebCore does claim to respect the action.
800     if (wcDragImage) {
801         dragImage = wcDragImage;
802         // wcDragLoc is the cursor position relative to the lower-left corner of the image.
803         // We add in the Y dimension because we are a flipped view, so adding moves the image down.
804         if (linkURL) {
805             // see HACK below
806             dragLoc = NSMakePoint(mouseDraggedPoint.x - wcDragLoc.x, mouseDraggedPoint.y + wcDragLoc.y);
807         } else {
808             dragLoc = NSMakePoint(mouseDownPoint.x - wcDragLoc.x, mouseDownPoint.y + wcDragLoc.y);
809         }
810         _private->dragOffset = wcDragLoc;
811     }
812     
813     WebView *webView = [self _webView];
814     NSPasteboard *pasteboard = [NSPasteboard pasteboardWithName:NSDragPboard];
815     
816     // note per kwebster, the offset arg below is always ignored in positioning the image
817     if (imageURL && (_private->dragSourceActionMask & WebDragSourceActionImage)) {
818         id source = self;
819         if (!dhtmlWroteData) {
820             _private->draggingImageURL = [imageURL retain];
821             source = [pasteboard _web_declareAndWriteDragImage:[element objectForKey:WebElementImageKey]
822                                                            URL:linkURL ? linkURL : imageURL
823                                                          title:[element objectForKey:WebElementImageAltStringKey]
824                                                        archive:[[element objectForKey:WebElementDOMNodeKey] webArchive]
825                                                         source:self];
826         }
827         [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionImage fromPoint:mouseDownPoint withPasteboard:pasteboard];
828         if (dragImage == nil) {
829             [self _web_dragImage:[element objectForKey:WebElementImageKey]
830                             rect:[[element objectForKey:WebElementImageRectKey] rectValue]
831                            event:_private->mouseDownEvent
832                       pasteboard:pasteboard
833                           source:source
834                           offset:&_private->dragOffset];
835         } else {
836             [self dragImage:dragImage
837                          at:dragLoc
838                      offset:NSZeroSize
839                       event:_private->mouseDownEvent
840                  pasteboard:pasteboard
841                      source:source
842                   slideBack:YES];
843         }
844     } else if (linkURL && (_private->dragSourceActionMask & WebDragSourceActionLink)) {
845         if (!dhtmlWroteData) {
846             NSArray *types = [NSPasteboard _web_writableTypesForURL];
847             [pasteboard declareTypes:types owner:self];
848             [pasteboard _web_writeURL:linkURL andTitle:[element objectForKey:WebElementLinkLabelKey] types:types];            
849         }
850         [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionLink fromPoint:mouseDownPoint withPasteboard:pasteboard];
851         if (dragImage == nil) {
852             dragImage = [self _dragImageForLinkElement:element];
853             NSSize offset = NSMakeSize([dragImage size].width / 2, -DRAG_LABEL_BORDER_Y);
854             dragLoc = NSMakePoint(mouseDraggedPoint.x - offset.width, mouseDraggedPoint.y - offset.height);
855             _private->dragOffset.x = offset.width;
856             _private->dragOffset.y = -offset.height;        // inverted because we are flipped
857         }
858         // HACK:  We should pass the mouseDown event instead of the mouseDragged!  This hack gets rid of
859         // a flash of the image at the mouseDown location when the drag starts.
860         [self dragImage:dragImage
861                      at:dragLoc
862                  offset:NSZeroSize
863                   event:mouseDraggedEvent
864              pasteboard:pasteboard
865                  source:self
866               slideBack:NO];
867     } else if (isSelected && (_private->dragSourceActionMask & WebDragSourceActionSelection)) {
868         if (!dhtmlWroteData) {
869             [self _writeSelectionToPasteboard:pasteboard];
870         }
871         [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionSelection fromPoint:mouseDownPoint withPasteboard:pasteboard];
872         if (dragImage == nil) {
873             dragImage = [[self _bridge] selectionImage];
874             [dragImage _web_dissolveToFraction:WebDragImageAlpha];
875             NSRect visibleSelectionRect = [[self _bridge] visibleSelectionRect];
876             dragLoc = NSMakePoint(NSMinX(visibleSelectionRect), NSMaxY(visibleSelectionRect));
877             _private->dragOffset.x = mouseDownPoint.x - dragLoc.x;
878             _private->dragOffset.y = dragLoc.y - mouseDownPoint.y;        // inverted because we are flipped
879         }
880         [self dragImage:dragImage
881                      at:dragLoc
882                  offset:NSZeroSize
883                   event:_private->mouseDownEvent
884              pasteboard:pasteboard
885                  source:self
886               slideBack:YES];
887     } else {
888         ASSERT(srcIsDHTML);
889         ASSERT(_private->dragSourceActionMask & WebDragSourceActionDHTML);
890         [[webView _UIDelegateForwarder] webView:webView willPerformDragSourceAction:WebDragSourceActionDHTML fromPoint:mouseDownPoint withPasteboard:pasteboard];
891         if (dragImage == nil) {
892             // WebCore should have given us an image, but we'll make one up
893             NSString *imagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_image" ofType:@"tiff"];
894             dragImage = [[[NSImage alloc] initWithContentsOfFile:imagePath] autorelease];
895             NSSize imageSize = [dragImage size];
896             dragLoc = NSMakePoint(mouseDownPoint.x - imageSize.width/2, mouseDownPoint.y + imageSize.height/2);
897             _private->dragOffset.x = imageSize.width/2;
898             _private->dragOffset.y = imageSize.height/2;        // inverted because we are flipped
899         }
900         [self dragImage:dragImage
901                      at:dragLoc
902                  offset:NSZeroSize
903                   event:_private->mouseDownEvent
904              pasteboard:pasteboard
905                  source:self
906               slideBack:YES];
907     }
908     return YES;
909 }
910
911 - (void)_handleAutoscrollForMouseDragged:(NSEvent *)event
912 {
913     [self autoscroll:event];
914     [self _startAutoscrollTimer:event];
915 }
916
917 - (BOOL)_mayStartDragAtEventLocation:(NSPoint)location
918 {
919     NSPoint mouseDownPoint = [self convertPoint:location fromView:nil];
920     NSDictionary *mouseDownElement = [self elementAtPoint:mouseDownPoint];
921
922     if ([mouseDownElement objectForKey: WebElementImageURLKey] != nil && 
923         [[WebPreferences standardPreferences] loadsImagesAutomatically] && 
924         (_private->dragSourceActionMask & WebDragSourceActionImage)) {
925         return YES;
926     }
927     
928     if ([mouseDownElement objectForKey:WebElementLinkURLKey] != nil && 
929         (_private->dragSourceActionMask & WebDragSourceActionLink)) {
930         return YES;
931     }
932     
933     if ([[mouseDownElement objectForKey:WebElementIsSelectedKey] boolValue] &&
934         (_private->dragSourceActionMask & WebDragSourceActionSelection)) {
935         return YES;
936     }
937     
938     return NO;
939 }
940
941 - (WebPluginController *)_pluginController
942 {
943     return _private->pluginController;
944 }
945
946 - (void)_web_setPrintingModeRecursive
947 {
948     [self _setPrinting:YES minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:NO];
949     [super _web_setPrintingModeRecursive];
950 }
951
952 - (void)_web_clearPrintingModeRecursive
953 {
954     [self _setPrinting:NO minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:NO];
955     [super _web_clearPrintingModeRecursive];
956 }
957
958 - (void)_web_layoutIfNeededRecursive:(NSRect)displayRect testDirtyRect:(bool)testDirtyRect
959 {
960     ASSERT(!_private->subviewsSetAside);
961     displayRect = NSIntersectionRect(displayRect, [self bounds]);
962
963     if (!testDirtyRect || [self needsDisplay]) {
964         if (testDirtyRect) {
965             NSRect dirtyRect = [self _dirtyRect];
966             displayRect = NSIntersectionRect(displayRect, dirtyRect);
967         }
968         if (!NSIsEmptyRect(displayRect)) {
969             if ([[self _bridge] needsLayout])
970                 _private->needsLayout = YES;
971             if (_private->needsToApplyStyles || _private->needsLayout)
972                 [self layout];
973         }
974     }
975
976     [super _web_layoutIfNeededRecursive: displayRect testDirtyRect: NO];
977 }
978
979 - (NSRect)_selectionRect
980 {
981     return [[self _bridge] selectionRect];
982 }
983
984 - (void)_startAutoscrollTimer: (NSEvent *)triggerEvent
985 {
986     if (_private->autoscrollTimer == nil) {
987         _private->autoscrollTimer = [[NSTimer scheduledTimerWithTimeInterval:AUTOSCROLL_INTERVAL
988             target:self selector:@selector(_autoscroll) userInfo:nil repeats:YES] retain];
989         _private->autoscrollTriggerEvent = [triggerEvent retain];
990     }
991 }
992
993 - (void)_stopAutoscrollTimer
994 {
995     NSTimer *timer = _private->autoscrollTimer;
996     _private->autoscrollTimer = nil;
997     [_private->autoscrollTriggerEvent release];
998     _private->autoscrollTriggerEvent = nil;
999     [timer invalidate];
1000     [timer release];
1001 }
1002
1003 - (void)_autoscroll
1004 {
1005     int isStillDown;
1006     
1007     // Guarantee that the autoscroll timer is invalidated, even if we don't receive
1008     // a mouse up event.
1009     PSstilldown([_private->autoscrollTriggerEvent eventNumber], &isStillDown);
1010     if (!isStillDown){
1011         [self _stopAutoscrollTimer];
1012         return;
1013     }
1014
1015     NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseDragged
1016         location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
1017         modifierFlags:[[NSApp currentEvent] modifierFlags]
1018         timestamp:[NSDate timeIntervalSinceReferenceDate]
1019         windowNumber:[[self window] windowNumber]
1020         context:[[NSApp currentEvent] context]
1021         eventNumber:0 clickCount:0 pressure:0];
1022     [self mouseDragged:fakeEvent];
1023 }
1024
1025 @end
1026
1027 @implementation NSView (WebHTMLViewPrivate)
1028
1029 - (void)_web_setPrintingModeRecursive
1030 {
1031     [_subviews makeObjectsPerformSelector:@selector(_web_setPrintingModeRecursive)];
1032 }
1033
1034 - (void)_web_clearPrintingModeRecursive
1035 {
1036     [_subviews makeObjectsPerformSelector:@selector(_web_clearPrintingModeRecursive)];
1037 }
1038
1039 - (void)_web_layoutIfNeededRecursive: (NSRect)rect testDirtyRect:(bool)testDirtyRect
1040 {
1041     unsigned index, count;
1042     for (index = 0, count = [_subviews count]; index < count; index++) {
1043         NSView *subview = [_subviews objectAtIndex:index];
1044         NSRect dirtiedSubviewRect = [subview convertRect: rect fromView: self];
1045         [subview _web_layoutIfNeededRecursive: dirtiedSubviewRect testDirtyRect:testDirtyRect];
1046     }
1047 }
1048
1049 @end
1050
1051 @implementation NSMutableDictionary (WebHTMLViewPrivate)
1052
1053 - (void)_web_setObjectIfNotNil:(id)object forKey:(id)key
1054 {
1055     if (object == nil) {
1056         [self removeObjectForKey:key];
1057     } else {
1058         [self setObject:object forKey:key];
1059     }
1060 }
1061
1062 @end
1063
1064 // The following is a workaround for
1065 // <rdar://problem/3429631> window stops getting mouse moved events after first tooltip appears
1066 // The trick is to define a category on NSToolTipPanel that implements setAcceptsMouseMovedEvents:.
1067 // Since the category will be searched before the real class, we'll prevent the flag from being
1068 // set on the tool tip panel.
1069
1070 @interface NSToolTipPanel : NSPanel
1071 @end
1072
1073 @interface NSToolTipPanel (WebHTMLViewPrivate)
1074 @end
1075
1076 @implementation NSToolTipPanel (WebHTMLViewPrivate)
1077
1078 - (void)setAcceptsMouseMovedEvents:(BOOL)flag
1079 {
1080     // Do nothing, preventing the tool tip panel from trying to accept mouse-moved events.
1081 }
1082
1083 @end
1084
1085
1086 @interface WebHTMLView (TextSizing) <_web_WebDocumentTextSizing>
1087 @end
1088
1089 @interface NSArray (WebHTMLView)
1090 - (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object;
1091 @end
1092
1093 @implementation WebHTMLView
1094
1095 + (void)initialize
1096 {
1097     WebKitInitializeUnicode();
1098     [NSApp registerServicesMenuSendTypes:[[self class] _selectionPasteboardTypes] returnTypes:nil];
1099 }
1100
1101 - (id)initWithFrame:(NSRect)frame
1102 {
1103     [super initWithFrame:frame];
1104     
1105     // Make all drawing go through us instead of subviews.
1106     // The bulk of the code to handle this is in WebHTMLViewPrivate.m.
1107     if (NSAppKitVersionNumber >= 711) {
1108         [self _setDrawsOwnDescendants:YES];
1109     }
1110     
1111     _private = [[WebHTMLViewPrivate alloc] init];
1112
1113     _private->pluginController = [[WebPluginController alloc] initWithHTMLView:self];
1114     _private->needsLayout = YES;
1115
1116     return self;
1117 }
1118
1119 - (void)dealloc
1120 {
1121     [self _clearLastHitViewIfSelf];
1122     [self _reset];
1123     [[NSNotificationCenter defaultCenter] removeObserver:self];
1124     [_private->pluginController destroyAllPlugins];
1125     [_private release];
1126     _private = nil;
1127     [super dealloc];
1128 }
1129
1130 - (void)finalize
1131 {
1132     [self _clearLastHitViewIfSelf];
1133     [self _reset];
1134     [[NSNotificationCenter defaultCenter] removeObserver:self];
1135     [_private->pluginController destroyAllPlugins];
1136     _private = nil;
1137     [super finalize];
1138 }
1139
1140 - (IBAction)takeFindStringFromSelection:(id)sender
1141 {
1142     if (![self _haveSelection]) {
1143         NSBeep();
1144         return;
1145     }
1146
1147     [NSPasteboard _web_setFindPasteboardString:[self selectedString] withOwner:self];
1148 }
1149
1150 - (NSArray *)pasteboardTypesForSelection
1151 {
1152     return [[self class] _selectionPasteboardTypes];
1153 }
1154
1155 - (void)writeSelectionWithPasteboardTypes:(NSArray *)types toPasteboard:(NSPasteboard *)pasteboard
1156 {
1157     // Put HTML on the pasteboard.
1158     if ([types containsObject:WebArchivePboardType]) {
1159         WebArchive *archive = [self _selectedArchive];
1160         [pasteboard setData:[archive data] forType:WebArchivePboardType];
1161     }
1162     
1163     // Put attributed string on the pasteboard (RTF format).
1164     NSData *RTFData = nil;
1165     if ([types containsObject:NSRTFPboardType]) {
1166         RTFData = [self _selectedRTFData];
1167         [pasteboard setData:RTFData forType:NSRTFPboardType];
1168     }
1169     if ([types containsObject:NSRTFDPboardType]) {
1170         if (!RTFData) {
1171             RTFData = [self _selectedRTFData];
1172         }
1173         [pasteboard setData:RTFData forType:NSRTFDPboardType];
1174     }
1175     
1176     // Put plain string on the pasteboard.
1177     if ([types containsObject:NSStringPboardType]) {
1178         // Map &nbsp; to a plain old space because this is better for source code, other browsers do it,
1179         // and because HTML forces you to do this any time you want two spaces in a row.
1180         NSMutableString *s = [[self selectedString] mutableCopy];
1181         const unichar NonBreakingSpaceCharacter = 0xA0;
1182         NSString *NonBreakingSpaceString = [NSString stringWithCharacters:&NonBreakingSpaceCharacter length:1];
1183         [s replaceOccurrencesOfString:NonBreakingSpaceString withString:@" " options:0 range:NSMakeRange(0, [s length])];
1184         [pasteboard setString:s forType:NSStringPboardType];
1185         [s release];
1186     }
1187 }
1188
1189 - (BOOL)writeSelectionToPasteboard:(NSPasteboard *)pasteboard types:(NSArray *)types
1190 {
1191     [self _writeSelectionToPasteboard:pasteboard];
1192     return YES;
1193 }
1194
1195 - (void)selectAll:(id)sender
1196 {
1197     [self selectAll];
1198 }
1199
1200 - (void)jumpToSelection: sender
1201 {
1202     [[self _bridge] jumpToSelection];
1203 }
1204
1205 - (BOOL)validateUserInterfaceItem:(id <NSValidatedUserInterfaceItem>)item 
1206 {
1207     SEL action = [item action];
1208     WebBridge *bridge = [self _bridge];
1209
1210     if (action == @selector(cut:)) {
1211         return [bridge mayDHTMLCut] || [self _canDelete];
1212     } else if (action == @selector(copy:)) {
1213         return [bridge mayDHTMLCopy] || [self _haveSelection];
1214     } else if (action == @selector(delete:)) {
1215         return [self _canDelete];
1216     } else if (action == @selector(paste:)) {
1217         return [bridge mayDHTMLPaste] || [self _canPaste];
1218     } else if (action == @selector(takeFindStringFromSelection:)) {
1219         return [self _haveSelection];
1220     } else if (action == @selector(jumpToSelection:)) {
1221         return [self _haveSelection];
1222     }
1223     
1224     return YES;
1225 }
1226
1227 - (id)validRequestorForSendType:(NSString *)sendType returnType:(NSString *)returnType
1228 {
1229     if (sendType && ([[[self class] _selectionPasteboardTypes] containsObject:sendType]) && [self _haveSelection]){
1230         return self;
1231     }
1232
1233     return [super validRequestorForSendType:sendType returnType:returnType];
1234 }
1235
1236 - (BOOL)acceptsFirstResponder
1237 {
1238     // Don't accept first responder when we first click on this view.
1239     // We have to pass the event down through WebCore first to be sure we don't hit a subview.
1240     // Do accept first responder at any other time, for example from keyboard events,
1241     // or from calls back from WebCore once we begin mouse-down event handling.
1242     NSEvent *event = [NSApp currentEvent];
1243     if ([event type] == NSLeftMouseDown && event != _private->mouseDownEvent && 
1244         NSPointInRect([event locationInWindow], [self convertRect:[self visibleRect] toView:nil])) {
1245         return NO;
1246     }
1247     return YES;
1248 }
1249
1250 - (void)updateTextBackgroundColor
1251 {
1252     NSWindow *window = [self window];
1253     BOOL shouldUseInactiveTextBackgroundColor = !([window isKeyWindow] && [window firstResponder] == self) ||
1254         _private->resigningFirstResponder;
1255     WebBridge *bridge = [self _bridge];
1256     if ([bridge usesInactiveTextBackgroundColor] != shouldUseInactiveTextBackgroundColor) {
1257         [bridge setUsesInactiveTextBackgroundColor:shouldUseInactiveTextBackgroundColor];
1258         [self setNeedsDisplayInRect:[bridge visibleSelectionRect]];
1259     }
1260 }
1261
1262 - (BOOL)maintainsInactiveSelection
1263 {
1264     // This method helps to determing whether the view should maintain
1265     // an inactive selection when the view is not first responder.
1266     // Traditionally, these views have not maintained such selections,
1267     // clearing them when the view was not first responder. However,
1268     // to fix bugs like this one:
1269     // <rdar://problem/3672088>: "Editable WebViews should maintain a selection even 
1270     //                            when they're not firstResponder"
1271     // it was decided to add a switch to act more like an NSTextView.
1272     // For now, however, the view only acts in this way when the
1273     // web view is set to be editable. This will maintain traditional
1274     // behavior for WebKit clients dating back to before this change,
1275     // and will likely be a decent switch for the long term, since
1276     // clients to ste the web view to be editable probably want it
1277     // to act like a "regular" Cocoa view in terms of its selection
1278     // behavior.
1279     if (![[self _webView] isEditable])
1280         return NO;
1281         
1282     id nextResponder = [[self window] _newFirstResponderAfterResigning];
1283     return !nextResponder || ![nextResponder isKindOfClass:[NSView class]] || ![nextResponder isDescendantOf:[self _webView]];
1284 }
1285
1286 - (void)addMouseMovedObserver
1287 {
1288     if ([[self window] isKeyWindow] && ![self _insideAnotherHTMLView]) {
1289         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mouseMovedNotification:)
1290             name:NSMouseMovedNotification object:nil];
1291         [self _frameOrBoundsChanged];
1292     }
1293 }
1294
1295 - (void)removeMouseMovedObserver
1296 {
1297     [[self _webView] _mouseDidMoveOverElement:nil modifierFlags:0];
1298     [[NSNotificationCenter defaultCenter] removeObserver:self
1299         name:NSMouseMovedNotification object:nil];
1300 }
1301
1302 - (void)updateShowsFirstResponder
1303 {
1304     [[self _bridge] setShowsFirstResponder:[[self window] isKeyWindow]];
1305 }
1306
1307 - (void)addSuperviewObservers
1308 {
1309     // We watch the bounds of our superview, so that we can do a layout when the size
1310     // of the superview changes. This is different from other scrollable things that don't
1311     // need this kind of thing because their layout doesn't change.
1312     
1313     // We need to pay attention to both height and width because our "layout" has to change
1314     // to extend the background the full height of the space and because some elements have
1315     // sizes that are based on the total size of the view.
1316     
1317     NSView *superview = [self superview];
1318     if (superview && [self window]) {
1319         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) 
1320             name:NSViewFrameDidChangeNotification object:superview];
1321         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_frameOrBoundsChanged) 
1322             name:NSViewBoundsDidChangeNotification object:superview];
1323     }
1324 }
1325
1326 - (void)removeSuperviewObservers
1327 {
1328     NSView *superview = [self superview];
1329     if (superview && [self window]) {
1330         [[NSNotificationCenter defaultCenter] removeObserver:self
1331             name:NSViewFrameDidChangeNotification object:superview];
1332         [[NSNotificationCenter defaultCenter] removeObserver:self
1333             name:NSViewBoundsDidChangeNotification object:superview];
1334     }
1335 }
1336
1337 - (void)addWindowObservers
1338 {
1339     NSWindow *window = [self window];
1340     if (window) {
1341         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidBecomeKey:)
1342             name:NSWindowDidBecomeKeyNotification object:window];
1343         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResignKey:)
1344             name:NSWindowDidResignKeyNotification object:window];
1345         [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowWillClose:)
1346             name:NSWindowWillCloseNotification object:window];
1347     }
1348 }
1349
1350 - (void)removeWindowObservers
1351 {
1352     NSWindow *window = [self window];
1353     if (window) {
1354         [[NSNotificationCenter defaultCenter] removeObserver:self
1355             name:NSWindowDidBecomeKeyNotification object:window];
1356         [[NSNotificationCenter defaultCenter] removeObserver:self
1357             name:NSWindowDidResignKeyNotification object:window];
1358         [[NSNotificationCenter defaultCenter] removeObserver:self
1359             name:NSWindowWillCloseNotification object:window];
1360     }
1361 }
1362
1363 - (void)viewWillMoveToSuperview:(NSView *)newSuperview
1364 {
1365     [self removeSuperviewObservers];
1366 }
1367
1368 - (void)viewDidMoveToSuperview
1369 {
1370     // Do this here in case the text size multiplier changed when a non-HTML
1371     // view was installed.
1372     if ([self superview] != nil) {
1373         [self _updateTextSizeMultiplier];
1374         [self addSuperviewObservers];
1375     }
1376 }
1377
1378 - (void)viewWillMoveToWindow:(NSWindow *)window
1379 {
1380     // Don't do anything if we aren't initialized.  This happens
1381     // when decoding a WebView.  When WebViews are decoded their subviews
1382     // are created by initWithCoder: and so won't be normally
1383     // initialized.  The stub views are discarded by WebView.
1384     if (_private){
1385         // FIXME: Some of these calls may not work because this view may be already removed from it's superview.
1386         [self removeMouseMovedObserver];
1387         [self removeWindowObservers];
1388         [self removeSuperviewObservers];
1389         [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_updateMouseoverWithFakeEvent) object:nil];
1390     
1391         [[self _pluginController] stopAllPlugins];
1392     }
1393 }
1394
1395 - (void)viewDidMoveToWindow
1396 {
1397     // Don't do anything if we aren't initialized.  This happens
1398     // when decoding a WebView.  When WebViews are decoded their subviews
1399     // are created by initWithCoder: and so won't be normally
1400     // initialized.  The stub views are discarded by WebView.
1401     if (_private) {
1402         [self _stopAutoscrollTimer];
1403         if ([self window]) {
1404             _private->lastScrollPosition = [[self superview] bounds].origin;
1405             [self addWindowObservers];
1406             [self addSuperviewObservers];
1407             [self addMouseMovedObserver];
1408             [self updateTextBackgroundColor];
1409     
1410             [[self _pluginController] startAllPlugins];
1411     
1412             _private->lastScrollPosition = NSZeroPoint;
1413             
1414             _private->inWindow = YES;
1415         } else {
1416             // Reset when we are moved out of a window after being moved into one.
1417             // Without this check, we reset ourselves before we even start.
1418             // This is only needed because viewDidMoveToWindow is called even when
1419             // the window is not changing (bug in AppKit).
1420             if (_private->inWindow) {
1421                 [self _reset];
1422                 _private->inWindow = NO;
1423             }
1424         }
1425     }
1426 }
1427
1428 - (void)viewWillMoveToHostWindow:(NSWindow *)hostWindow
1429 {
1430     [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewWillMoveToHostWindow:) withObject:hostWindow];
1431 }
1432
1433 - (void)viewDidMoveToHostWindow
1434 {
1435     [[self subviews] _web_makePluginViewsPerformSelector:@selector(viewDidMoveToHostWindow) withObject:nil];
1436 }
1437
1438
1439 - (void)addSubview:(NSView *)view
1440 {
1441     [super addSubview:view];
1442
1443     if ([[view class] respondsToSelector:@selector(plugInViewWithArguments:)] || [view respondsToSelector:@selector(pluginInitialize)]) {
1444         [[self _pluginController] addPlugin:view];
1445     }
1446 }
1447
1448 - (void)reapplyStyles
1449 {
1450     if (!_private->needsToApplyStyles) {
1451         return;
1452     }
1453     
1454 #ifdef _KWQ_TIMING        
1455     double start = CFAbsoluteTimeGetCurrent();
1456 #endif
1457
1458     [[self _bridge] reapplyStylesForDeviceType:
1459         _private->printing ? WebCoreDevicePrinter : WebCoreDeviceScreen];
1460     
1461 #ifdef _KWQ_TIMING        
1462     double thisTime = CFAbsoluteTimeGetCurrent() - start;
1463     LOG(Timing, "%s apply style seconds = %f", [self URL], thisTime);
1464 #endif
1465
1466     _private->needsToApplyStyles = NO;
1467 }
1468
1469 // Do a layout, but set up a new fixed width for the purposes of doing printing layout.
1470 // minPageWidth==0 implies a non-printing layout
1471 - (void)layoutToMinimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustingViewSize:(BOOL)adjustViewSize
1472 {
1473     [self reapplyStyles];
1474     
1475     // Ensure that we will receive mouse move events.  Is this the best place to put this?
1476     [[self window] setAcceptsMouseMovedEvents: YES];
1477     [[self window] _setShouldPostEventNotifications: YES];
1478
1479     if (!_private->needsLayout) {
1480         return;
1481     }
1482
1483 #ifdef _KWQ_TIMING        
1484     double start = CFAbsoluteTimeGetCurrent();
1485 #endif
1486
1487     LOG(View, "%@ doing layout", self);
1488
1489     if (minPageWidth > 0.0) {
1490         [[self _bridge] forceLayoutWithMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize];
1491     } else {
1492         [[self _bridge] forceLayoutAdjustingViewSize:adjustViewSize];
1493     }
1494     _private->needsLayout = NO;
1495     
1496     if (!_private->printing) {
1497         // get size of the containing dynamic scrollview, so
1498         // appearance and disappearance of scrollbars will not show up
1499         // as a size change
1500         NSSize newLayoutFrameSize = [[[self superview] superview] frame].size;
1501         if (_private->laidOutAtLeastOnce && !NSEqualSizes(_private->lastLayoutFrameSize, newLayoutFrameSize)) {
1502             [[self _bridge] sendResizeEvent];
1503         }
1504         _private->laidOutAtLeastOnce = YES;
1505         _private->lastLayoutSize = [(NSClipView *)[self superview] documentVisibleRect].size;
1506         _private->lastLayoutFrameSize = newLayoutFrameSize;
1507     }
1508
1509 #ifdef _KWQ_TIMING        
1510     double thisTime = CFAbsoluteTimeGetCurrent() - start;
1511     LOG(Timing, "%s layout seconds = %f", [self URL], thisTime);
1512 #endif
1513 }
1514
1515 - (void)layout
1516 {
1517     [self layoutToMinimumPageWidth:0.0 maximumPageWidth:0.0 adjustingViewSize:NO];
1518 }
1519
1520 - (NSMenu *)menuForEvent:(NSEvent *)event
1521 {
1522     if ([[self _bridge] sendContextMenuEvent:event]) {
1523         return nil;
1524     }
1525     NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
1526     NSDictionary *element = [self elementAtPoint:point];
1527     return [[self _webView] _menuForElement:element];
1528 }
1529
1530 // Search from the end of the currently selected location, or from the beginning of the
1531 // document if nothing is selected.
1532 - (BOOL)searchFor:(NSString *)string direction:(BOOL)forward caseSensitive:(BOOL)caseFlag wrap:(BOOL)wrapFlag;
1533 {
1534     return [[self _bridge] searchFor:string direction:forward caseSensitive:caseFlag wrap:wrapFlag];
1535 }
1536
1537 - (NSString *)string
1538 {
1539     return [[self attributedString] string];
1540 }
1541
1542 - (NSAttributedString *)attributedString
1543 {
1544     WebBridge *b = [self _bridge];
1545     return [b attributedStringFrom:[b DOMDocument]
1546                        startOffset:0
1547                                 to:nil
1548                          endOffset:0];
1549 }
1550
1551 - (NSString *)selectedString
1552 {
1553     return [[self _bridge] selectedString];
1554 }
1555
1556 // Get an attributed string that represents the current selection.
1557 - (NSAttributedString *)selectedAttributedString
1558 {
1559     return [[self _bridge] selectedAttributedString];
1560 }
1561
1562 - (void)selectAll
1563 {
1564     [[self _bridge] selectAll];
1565 }
1566
1567 // Remove the selection.
1568 - (void)deselectAll
1569 {
1570     [[self _bridge] deselectAll];
1571 }
1572
1573 - (void)deselectText
1574 {
1575     [[self _bridge] deselectText];
1576 }
1577
1578 - (BOOL)isOpaque
1579 {
1580     return [[self _webView] drawsBackground];
1581 }
1582
1583 - (void)setNeedsDisplay:(BOOL)flag
1584 {
1585     LOG(View, "%@ flag = %d", self, (int)flag);
1586     [super setNeedsDisplay: flag];
1587 }
1588
1589 - (void)setNeedsLayout: (BOOL)flag
1590 {
1591     LOG(View, "%@ flag = %d", self, (int)flag);
1592     _private->needsLayout = flag;
1593 }
1594
1595
1596 - (void)setNeedsToApplyStyles: (BOOL)flag
1597 {
1598     LOG(View, "%@ flag = %d", self, (int)flag);
1599     _private->needsToApplyStyles = flag;
1600 }
1601
1602 - (void)drawRect:(NSRect)rect
1603 {
1604     LOG(View, "%@ drawing", self);
1605     
1606     BOOL subviewsWereSetAside = _private->subviewsSetAside;
1607     if (subviewsWereSetAside) {
1608         [self _restoreSubviews];
1609     }
1610
1611 #ifdef _KWQ_TIMING
1612     double start = CFAbsoluteTimeGetCurrent();
1613 #endif
1614
1615     [NSGraphicsContext saveGraphicsState];
1616     NSRectClip(rect);
1617         
1618     ASSERT([[self superview] isKindOfClass:[WebClipView class]]);
1619
1620     [(WebClipView *)[self superview] setAdditionalClip:rect];
1621
1622     NS_DURING {
1623         WebTextRendererFactory *textRendererFactoryIfCoalescing = nil;
1624         if ([WebTextRenderer shouldBufferTextDrawing] && [NSView focusView]) {
1625             textRendererFactoryIfCoalescing = [WebTextRendererFactory sharedFactory];
1626             [textRendererFactoryIfCoalescing startCoalesceTextDrawing];
1627         }
1628
1629         if (![[self _webView] drawsBackground]) {
1630             [[NSColor clearColor] set];
1631             NSRectFill (rect);
1632         }
1633         
1634         //double start = CFAbsoluteTimeGetCurrent();
1635         [[self _bridge] drawRect:rect];
1636         //LOG(Timing, "draw time %e", CFAbsoluteTimeGetCurrent() - start);
1637
1638         if (textRendererFactoryIfCoalescing != nil) {
1639             [textRendererFactoryIfCoalescing endCoalesceTextDrawing];
1640         }
1641
1642         [(WebClipView *)[self superview] resetAdditionalClip];
1643
1644         [NSGraphicsContext restoreGraphicsState];
1645     } NS_HANDLER {
1646         [(WebClipView *)[self superview] resetAdditionalClip];
1647         [NSGraphicsContext restoreGraphicsState];
1648         ERROR("Exception caught while drawing: %@", localException);
1649         [localException raise];
1650     } NS_ENDHANDLER
1651
1652 #ifdef DEBUG_LAYOUT
1653     NSRect vframe = [self frame];
1654     [[NSColor blackColor] set];
1655     NSBezierPath *path;
1656     path = [NSBezierPath bezierPath];
1657     [path setLineWidth:(float)0.1];
1658     [path moveToPoint:NSMakePoint(0, 0)];
1659     [path lineToPoint:NSMakePoint(vframe.size.width, vframe.size.height)];
1660     [path closePath];
1661     [path stroke];
1662     path = [NSBezierPath bezierPath];
1663     [path setLineWidth:(float)0.1];
1664     [path moveToPoint:NSMakePoint(0, vframe.size.height)];
1665     [path lineToPoint:NSMakePoint(vframe.size.width, 0)];
1666     [path closePath];
1667     [path stroke];
1668 #endif
1669
1670 #ifdef _KWQ_TIMING
1671     double thisTime = CFAbsoluteTimeGetCurrent() - start;
1672     LOG(Timing, "%s draw seconds = %f", widget->part()->baseURL().URL().latin1(), thisTime);
1673 #endif
1674
1675     if (subviewsWereSetAside) {
1676         [self _setAsideSubviews];
1677     }
1678 }
1679
1680 // Turn off the additional clip while computing our visibleRect.
1681 - (NSRect)visibleRect
1682 {
1683     if (!([[self superview] isKindOfClass:[WebClipView class]]))
1684         return [super visibleRect];
1685         
1686     WebClipView *clipView = (WebClipView *)[self superview];
1687
1688     BOOL hasAdditionalClip = [clipView hasAdditionalClip];
1689     if (!hasAdditionalClip) {
1690         return [super visibleRect];
1691     }
1692     
1693     NSRect additionalClip = [clipView additionalClip];
1694     [clipView resetAdditionalClip];
1695     NSRect visibleRect = [super visibleRect];
1696     [clipView setAdditionalClip:additionalClip];
1697     return visibleRect;
1698 }
1699
1700 - (BOOL)isFlipped 
1701 {
1702     return YES;
1703 }
1704
1705 - (void)windowDidBecomeKey:(NSNotification *)notification
1706 {
1707     ASSERT([notification object] == [self window]);
1708     [self addMouseMovedObserver];
1709     [self updateTextBackgroundColor];
1710     [self updateShowsFirstResponder];
1711 }
1712
1713 - (void)windowDidResignKey: (NSNotification *)notification
1714 {
1715     ASSERT([notification object] == [self window]);
1716     [self removeMouseMovedObserver];
1717     [self updateTextBackgroundColor];
1718     [self updateShowsFirstResponder];
1719 }
1720
1721 - (void)windowWillClose:(NSNotification *)notification
1722 {
1723     [[self _pluginController] destroyAllPlugins];
1724 }
1725
1726 - (BOOL)acceptsFirstMouse:(NSEvent *)event
1727 {
1728     // We hack AK's hitTest method to catch all events at the topmost WebHTMLView.  However, for
1729     // the purposes of this method we want to really query the deepest view, so we forward to it.
1730     forceRealHitTest = YES;
1731     NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]];
1732     forceRealHitTest = NO;
1733     
1734     if ([hitView isKindOfClass:[self class]]) {
1735         WebHTMLView *hitHTMLView = (WebHTMLView *)hitView;
1736         [[hitHTMLView _bridge] setActivationEventNumber:[event eventNumber]];
1737         return [[hitHTMLView _bridge] eventMayStartDrag:event];
1738     } else {
1739         return [hitView acceptsFirstMouse:event];
1740     }
1741 }
1742
1743 - (BOOL)shouldDelayWindowOrderingForEvent:(NSEvent *)event
1744 {
1745     // We hack AK's hitTest method to catch all events at the topmost WebHTMLView.  However, for
1746     // the purposes of this method we want to really query the deepest view, so we forward to it.
1747     forceRealHitTest = YES;
1748     NSView *hitView = [[[self window] contentView] hitTest:[event locationInWindow]];
1749     forceRealHitTest = NO;
1750     
1751     if ([hitView isKindOfClass:[self class]]) {
1752         WebHTMLView *hitHTMLView = (WebHTMLView *)hitView;
1753         return [[hitHTMLView _bridge] eventMayStartDrag:event];
1754     } else {
1755         return [hitView shouldDelayWindowOrderingForEvent:event];
1756     }
1757 }
1758
1759 - (void)mouseDown:(NSEvent *)event
1760 {    
1761     // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here.
1762     // We don't want to pass them along to KHTML a second time.
1763     if ([event modifierFlags] & NSControlKeyMask) {
1764         return;
1765     }
1766     
1767     _private->ignoringMouseDraggedEvents = NO;
1768     
1769     // Record the mouse down position so we can determine drag hysteresis.
1770     [_private->mouseDownEvent release];
1771     _private->mouseDownEvent = [event retain];
1772
1773     // Don't do any mouseover while the mouse is down.
1774     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_updateMouseoverWithFakeEvent) object:nil];
1775
1776     // Let KHTML get a chance to deal with the event. This will call back to us
1777     // to start the autoscroll timer if appropriate.
1778     [[self _bridge] mouseDown:event];
1779 }
1780
1781 - (void)dragImage:(NSImage *)dragImage
1782                at:(NSPoint)at
1783            offset:(NSSize)offset
1784             event:(NSEvent *)event
1785        pasteboard:(NSPasteboard *)pasteboard
1786            source:(id)source
1787         slideBack:(BOOL)slideBack
1788 {   
1789     [self _stopAutoscrollTimer];
1790     
1791     _private->initiatedDrag = YES;
1792     [[self _webView] _setInitiatedDrag:YES];
1793     
1794     // Retain this view during the drag because it may be released before the drag ends.
1795     [self retain];
1796
1797     [super dragImage:dragImage at:at offset:offset event:event pasteboard:pasteboard source:source slideBack:slideBack];
1798 }
1799
1800 - (void)mouseDragged:(NSEvent *)event
1801 {
1802     if (!_private->ignoringMouseDraggedEvents) {
1803         [[self _bridge] mouseDragged:event];
1804     }
1805 }
1806
1807 - (NSDragOperation)draggingSourceOperationMaskForLocal:(BOOL)isLocal
1808 {
1809     if (_private->webCoreDragOp == NSDragOperationNone) {
1810         return (NSDragOperationGeneric | NSDragOperationCopy);
1811     } else {
1812         return _private->webCoreDragOp;
1813     }
1814 }
1815
1816 - (void)draggedImage:(NSImage *)image movedTo:(NSPoint)screenLoc
1817 {
1818     NSPoint windowImageLoc = [[self window] convertScreenToBase:screenLoc];
1819     NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + _private->dragOffset.x, windowImageLoc.y + _private->dragOffset.y);
1820     [[self _bridge] dragSourceMovedTo:windowMouseLoc];
1821 }
1822
1823 - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
1824 {
1825     NSPoint windowImageLoc = [[self window] convertScreenToBase:aPoint];
1826     NSPoint windowMouseLoc = NSMakePoint(windowImageLoc.x + _private->dragOffset.x, windowImageLoc.y + _private->dragOffset.y);
1827     [[self _bridge] dragSourceEndedAt:windowMouseLoc operation:operation];
1828
1829     _private->initiatedDrag = NO;
1830     [[self _webView] _setInitiatedDrag:NO];
1831     
1832     // Prevent queued mouseDragged events from coming after the drag and fake mouseUp event.
1833     _private->ignoringMouseDraggedEvents = YES;
1834     
1835     // Once the dragging machinery kicks in, we no longer get mouse drags or the up event.
1836     // khtml expects to get balanced down/up's, so we must fake up a mouseup.
1837     NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSLeftMouseUp
1838                                             location:windowMouseLoc
1839                                        modifierFlags:[[NSApp currentEvent] modifierFlags]
1840                                            timestamp:[NSDate timeIntervalSinceReferenceDate]
1841                                         windowNumber:[[self window] windowNumber]
1842                                              context:[[NSApp currentEvent] context]
1843                                          eventNumber:0 clickCount:0 pressure:0];
1844     [self mouseUp:fakeEvent]; // This will also update the mouseover state.
1845     
1846     // Balance the previous retain from when the drag started.
1847     [self release];
1848 }
1849
1850 - (NSArray *)namesOfPromisedFilesDroppedAtDestination:(NSURL *)dropDestination
1851 {
1852     ASSERT(_private->draggingImageURL);
1853     
1854     NSFileWrapper *wrapper = [[self _dataSource] _fileWrapperForURL:_private->draggingImageURL];
1855     ASSERT(wrapper);    
1856     
1857     // FIXME: Report an error if we fail to create a file.
1858     NSString *path = [[dropDestination path] stringByAppendingPathComponent:[wrapper preferredFilename]];
1859     path = [[NSFileManager defaultManager] _web_pathWithUniqueFilenameForPath:path];
1860     if (![wrapper writeToFile:path atomically:NO updateFilenames:YES]) {
1861         ERROR("Failed to create image file via -[NSFileWrapper writeToFile:atomically:updateFilenames:]");
1862     }
1863     
1864     return [NSArray arrayWithObject:[path lastPathComponent]];
1865 }
1866
1867 - (BOOL)_canProcessDragWithDraggingInfo:(id <NSDraggingInfo>)draggingInfo
1868 {
1869     NSPasteboard *pasteboard = [draggingInfo draggingPasteboard];
1870     NSMutableSet *types = [NSMutableSet setWithArray:[pasteboard types]];
1871     [types intersectSet:[NSSet setWithArray:[WebHTMLView _insertablePasteboardTypes]]];
1872     if ([types count] == 0) {
1873         return NO;
1874     } else if ([types count] == 1 && 
1875                [types containsObject:NSFilenamesPboardType] && 
1876                ![self _imageExistsAtPaths:[pasteboard propertyListForType:NSFilenamesPboardType]]) {
1877         return NO;
1878     }
1879     
1880     NSPoint point = [self convertPoint:[draggingInfo draggingLocation] fromView:nil];
1881     NSDictionary *element = [self elementAtPoint:point];
1882     if ([[self _webView] isEditable] || [[element objectForKey:WebElementDOMNodeKey] isContentEditable]) {
1883         if (_private->initiatedDrag && [[element objectForKey:WebElementIsSelectedKey] boolValue]) {
1884             // Can't drag onto the selection being dragged.
1885             return NO;
1886         }
1887         return YES;
1888     }
1889     
1890     return NO;
1891 }
1892
1893 - (NSDragOperation)draggingUpdatedWithDraggingInfo:(id <NSDraggingInfo>)draggingInfo actionMask:(unsigned int)actionMask
1894 {
1895     NSDragOperation operation = NSDragOperationNone;
1896     
1897     if (actionMask & WebDragDestinationActionDHTML) {
1898         operation = [[self _bridge] dragOperationForDraggingInfo:draggingInfo];
1899     }
1900     _private->webCoreHandlingDrag = (operation != NSDragOperationNone);
1901     
1902     if ((actionMask & WebDragDestinationActionEdit) &&
1903         !_private->webCoreHandlingDrag
1904         && [self _canProcessDragWithDraggingInfo:draggingInfo])
1905     {
1906         WebView *webView = [self _webView];
1907         [webView moveDragCaretToPoint:[webView convertPoint:[draggingInfo draggingLocation] fromView:nil]];
1908         operation = (_private->initiatedDrag && [[self _bridge] isSelectionEditable]) ? NSDragOperationMove : NSDragOperationCopy;
1909     } else {
1910         [[self _webView] removeDragCaret];
1911     }
1912     
1913     return operation;
1914 }
1915
1916 - (void)draggingCancelledWithDraggingInfo:(id <NSDraggingInfo>)draggingInfo
1917 {
1918     [[self _bridge] dragExitedWithDraggingInfo:draggingInfo];
1919     [[self _webView] removeDragCaret];
1920 }
1921
1922 - (BOOL)concludeDragForDraggingInfo:(id <NSDraggingInfo>)draggingInfo actionMask:(unsigned int)actionMask
1923 {
1924     WebView *webView = [self _webView];
1925     WebBridge *bridge = [self _bridge];
1926     if (_private->webCoreHandlingDrag) {
1927         ASSERT(actionMask & WebDragDestinationActionDHTML);
1928         [[webView _UIDelegateForwarder] webView:webView willPerformDragDestinationAction:WebDragDestinationActionDHTML forDraggingInfo:draggingInfo];
1929         [bridge concludeDragForDraggingInfo:draggingInfo];
1930         return YES;
1931     } else if (actionMask & WebDragDestinationActionEdit) {
1932         BOOL didInsert = NO;
1933         if ([self _canProcessDragWithDraggingInfo:draggingInfo]) {
1934             DOMDocumentFragment *fragment = [self _documentFragmentFromPasteboard:[draggingInfo draggingPasteboard] allowPlainText:YES];
1935             if (fragment && [self _shouldInsertFragment:fragment replacingDOMRange:[bridge dragCaretDOMRange] givenAction:WebViewInsertActionDropped]) {
1936                 [[webView _UIDelegateForwarder] webView:webView willPerformDragDestinationAction:WebDragDestinationActionEdit forDraggingInfo:draggingInfo];
1937                 if (_private->initiatedDrag && [bridge isSelectionEditable]) {
1938                     [bridge moveSelectionToDragCaret:fragment];
1939                 } else {
1940                     [bridge setSelectionToDragCaret];
1941                     [bridge replaceSelectionWithFragment:fragment selectReplacement:YES];
1942                 }
1943                 didInsert = YES;
1944             }
1945         }
1946         [webView removeDragCaret];
1947         return didInsert;
1948     }
1949     return NO;
1950 }
1951
1952 - (NSDictionary *)elementAtPoint:(NSPoint)point
1953 {
1954     NSDictionary *elementInfoWC = [[self _bridge] elementAtPoint:point];
1955     NSMutableDictionary *elementInfo = [elementInfoWC mutableCopy];
1956     
1957     // Convert URL strings to NSURLs
1958     [elementInfo _web_setObjectIfNotNil:[NSURL _web_URLWithDataAsString:[elementInfoWC objectForKey:WebElementLinkURLKey]] forKey:WebElementLinkURLKey];
1959     [elementInfo _web_setObjectIfNotNil:[NSURL _web_URLWithDataAsString:[elementInfoWC objectForKey:WebElementImageURLKey]] forKey:WebElementImageURLKey];
1960     
1961     WebFrameView *webFrameView = [self _web_parentWebFrameView];
1962     ASSERT(webFrameView);
1963     WebFrame *webFrame = [webFrameView webFrame];
1964     
1965     if (webFrame) {
1966         NSString *frameName = [elementInfoWC objectForKey:WebElementLinkTargetFrameKey];
1967         if ([frameName length] == 0) {
1968             [elementInfo setObject:webFrame forKey:WebElementLinkTargetFrameKey];
1969         } else {
1970             WebFrame *wf = [webFrame findFrameNamed:frameName];
1971             if (wf != nil)
1972                 [elementInfo setObject:wf forKey:WebElementLinkTargetFrameKey];
1973             else
1974                 [elementInfo removeObjectForKey:WebElementLinkTargetFrameKey];
1975         }
1976         
1977         [elementInfo setObject:webFrame forKey:WebElementFrameKey];
1978     }
1979     
1980     return [elementInfo autorelease];
1981 }
1982
1983 - (void)mouseUp:(NSEvent *)event
1984 {
1985     [self _stopAutoscrollTimer];
1986     [[self _bridge] mouseUp:event];
1987     [self _updateMouseoverWithFakeEvent];
1988 }
1989
1990 - (void)mouseMovedNotification:(NSNotification *)notification
1991 {
1992     [self _updateMouseoverWithEvent:[[notification userInfo] objectForKey:@"NSEvent"]];
1993 }
1994
1995 - (BOOL)supportsTextEncoding
1996 {
1997     return YES;
1998 }
1999
2000 - (NSView *)nextKeyView
2001 {
2002     if (_private && _private->nextKeyViewAccessShouldMoveFocus && ![[self _bridge] inNextKeyViewOutsideWebFrameViews]) {
2003         _private->nextKeyViewAccessShouldMoveFocus = NO;
2004         return [[self _bridge] nextKeyView];
2005     }
2006     
2007     return [super nextKeyView];
2008 }
2009
2010 - (NSView *)previousKeyView
2011 {
2012     if (_private && _private->nextKeyViewAccessShouldMoveFocus) {
2013         _private->nextKeyViewAccessShouldMoveFocus = NO;
2014         return [[self _bridge] previousKeyView];
2015     }
2016         
2017     return [super previousKeyView];
2018 }
2019
2020 - (NSView *)nextValidKeyView
2021 {
2022     if (![self isHiddenOrHasHiddenAncestor]) {
2023         _private->nextKeyViewAccessShouldMoveFocus = YES;
2024     }
2025     NSView *view = [super nextValidKeyView];
2026     _private->nextKeyViewAccessShouldMoveFocus = NO;
2027     return view;
2028 }
2029
2030 - (NSView *)previousValidKeyView
2031 {
2032     if (![self isHiddenOrHasHiddenAncestor]) {
2033         _private->nextKeyViewAccessShouldMoveFocus = YES;
2034     }
2035     NSView *view = [super previousValidKeyView];
2036     _private->nextKeyViewAccessShouldMoveFocus = NO;
2037     return view;
2038 }
2039
2040 - (BOOL)becomeFirstResponder
2041 {
2042     NSView *view = nil;
2043     if (![[self _webView] _isPerformingProgrammaticFocus]) {
2044         switch ([[self window] keyViewSelectionDirection]) {
2045         case NSDirectSelection:
2046             break;
2047         case NSSelectingNext:
2048             view = [[self _bridge] nextKeyViewInsideWebFrameViews];
2049             break;
2050         case NSSelectingPrevious:
2051             view = [[self _bridge] previousKeyViewInsideWebFrameViews];
2052             break;
2053         }
2054     }
2055     if (view) {
2056         [[self window] makeFirstResponder:view];
2057     }
2058     [self updateTextBackgroundColor];
2059     return YES;
2060 }
2061
2062 // This approach could be relaxed when dealing with 3228554.
2063 // Some alteration to the selection behavior was done to deal with 3672088.
2064 - (BOOL)resignFirstResponder
2065 {
2066     BOOL resign = [super resignFirstResponder];
2067     if (resign) {
2068         _private->resigningFirstResponder = YES;
2069         if (![self maintainsInactiveSelection]) { 
2070             if ([[self _webView] _isPerformingProgrammaticFocus]) {
2071                 [self deselectText];
2072             }
2073             else {
2074                 [self deselectAll];
2075             }
2076         }
2077         [self updateTextBackgroundColor];
2078         _private->resigningFirstResponder = NO;
2079     }
2080     return resign;
2081 }
2082
2083 //------------------------------------------------------------------------------------
2084 // WebDocumentView protocol
2085 //------------------------------------------------------------------------------------
2086 - (void)setDataSource:(WebDataSource *)dataSource 
2087 {
2088 }
2089
2090 - (void)dataSourceUpdated:(WebDataSource *)dataSource
2091 {
2092 }
2093
2094 // Does setNeedsDisplay:NO as a side effect when printing is ending.
2095 // pageWidth != 0 implies we will relayout to a new width
2096 - (void)_setPrinting:(BOOL)printing minimumPageWidth:(float)minPageWidth maximumPageWidth:(float)maxPageWidth adjustViewSize:(BOOL)adjustViewSize
2097 {
2098     WebFrame *frame = [self _frame];
2099     NSArray *subframes = [frame childFrames];
2100     unsigned n = [subframes count];
2101     unsigned i;
2102     for (i = 0; i != n; ++i) {
2103         WebFrame *subframe = [subframes objectAtIndex:i];
2104         WebFrameView *frameView = [subframe frameView];
2105         if ([[subframe dataSource] _isDocumentHTML]) {
2106             [(WebHTMLView *)[frameView documentView] _setPrinting:printing minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:adjustViewSize];
2107         }
2108     }
2109
2110     if (printing != _private->printing) {
2111         [_private->pageRects release];
2112         _private->pageRects = nil;
2113         _private->printing = printing;
2114         [self setNeedsToApplyStyles:YES];
2115         [self setNeedsLayout:YES];
2116         [self layoutToMinimumPageWidth:minPageWidth maximumPageWidth:maxPageWidth adjustingViewSize:adjustViewSize];
2117         if (printing) {
2118             [[self _webView] _adjustPrintingMarginsForHeaderAndFooter];
2119         } else {
2120             // Can't do this when starting printing or nested printing won't work, see 3491427.
2121             [self setNeedsDisplay:NO];
2122         }
2123     }
2124 }
2125
2126 // This is needed for the case where the webview is embedded in the view that's being printed.
2127 // It shouldn't be called when the webview is being printed directly.
2128 - (void)adjustPageHeightNew:(float *)newBottom top:(float)oldTop bottom:(float)oldBottom limit:(float)bottomLimit
2129 {
2130     // This helps when we print as part of a larger print process.
2131     // If the WebHTMLView itself is what we're printing, then we will never have to do this.
2132     BOOL wasInPrintingMode = _private->printing;
2133     if (!wasInPrintingMode) {
2134         [self _setPrinting:YES minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:NO];
2135     }
2136     
2137     [[self _bridge] adjustPageHeightNew:newBottom top:oldTop bottom:oldBottom limit:bottomLimit];
2138     
2139     if (!wasInPrintingMode) {
2140         [self _setPrinting:NO minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:NO];
2141     }
2142 }
2143
2144 - (float)_availablePaperWidthForPrintOperation:(NSPrintOperation *)printOperation
2145 {
2146     NSPrintInfo *printInfo = [printOperation printInfo];
2147     return [printInfo paperSize].width - [printInfo leftMargin] - [printInfo rightMargin];
2148 }
2149
2150 - (float)_scaleFactorForPrintOperation:(NSPrintOperation *)printOperation
2151 {
2152     float viewWidth = NSWidth([self bounds]);
2153     if (viewWidth < 1) {
2154         ERROR("%@ has no width when printing", self);
2155         return 1.0;
2156     }
2157
2158     float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
2159     float maxShrinkToFitScaleFactor = 1/PrintingMaximumShrinkFactor;
2160     float shrinkToFitScaleFactor = [self _availablePaperWidthForPrintOperation:printOperation]/viewWidth;
2161     return userScaleFactor * MAX(maxShrinkToFitScaleFactor, shrinkToFitScaleFactor);
2162 }
2163
2164 // FIXME 3491344: This is a secret AppKit-internal method that we need to override in order
2165 // to get our shrink-to-fit to work with a custom pagination scheme. We can do this better
2166 // if AppKit makes it SPI/API.
2167 - (float)_provideTotalScaleFactorForPrintOperation:(NSPrintOperation *)printOperation 
2168 {
2169     return [self _scaleFactorForPrintOperation:printOperation];
2170 }
2171
2172 - (void)setPageWidthForPrinting:(float)pageWidth
2173 {
2174     [self _setPrinting:NO minimumPageWidth:0. maximumPageWidth:0. adjustViewSize:NO];
2175     [self _setPrinting:YES minimumPageWidth:pageWidth maximumPageWidth:pageWidth adjustViewSize:YES];
2176 }
2177
2178
2179 // Return the number of pages available for printing
2180 - (BOOL)knowsPageRange:(NSRangePointer)range {
2181     // Must do this explicit display here, because otherwise the view might redisplay while the print
2182     // sheet was up, using printer fonts (and looking different).
2183     [self displayIfNeeded];
2184     [[self window] setAutodisplay:NO];
2185     
2186     // If we are a frameset just print with the layout we have onscreen, otherwise relayout
2187     // according to the paper size
2188     float minLayoutWidth = 0.0;
2189     float maxLayoutWidth = 0.0;
2190     if (![[self _bridge] isFrameSet]) {
2191         float paperWidth = [self _availablePaperWidthForPrintOperation:[NSPrintOperation currentOperation]];
2192         minLayoutWidth = paperWidth*PrintingMinimumShrinkFactor;
2193         maxLayoutWidth = paperWidth*PrintingMaximumShrinkFactor;
2194     }
2195     [self _setPrinting:YES minimumPageWidth:minLayoutWidth maximumPageWidth:maxLayoutWidth adjustViewSize:YES]; // will relayout
2196     
2197     // There is a theoretical chance that someone could do some drawing between here and endDocument,
2198     // if something caused setNeedsDisplay after this point. If so, it's not a big tragedy, because
2199     // you'd simply see the printer fonts on screen. As of this writing, this does not happen with Safari.
2200
2201     range->location = 1;
2202     NSPrintOperation *printOperation = [NSPrintOperation currentOperation];
2203     float totalScaleFactor = [self _scaleFactorForPrintOperation:printOperation];
2204     float userScaleFactor = [printOperation _web_pageSetupScaleFactor];
2205     [_private->pageRects release];
2206     NSArray *newPageRects = [[self _bridge] computePageRectsWithPrintWidthScaleFactor:userScaleFactor
2207                                                                           printHeight:[self _calculatePrintHeight]/totalScaleFactor];
2208     // AppKit gets all messed up if you give it a zero-length page count (see 3576334), so if we
2209     // hit that case we'll pass along a degenerate 1 pixel square to print. This will print
2210     // a blank page (with correct-looking header and footer if that option is on), which matches
2211     // the behavior of IE and Camino at least.
2212     if ([newPageRects count] == 0) {
2213         newPageRects = [NSArray arrayWithObject:[NSValue valueWithRect: NSMakeRect(0, 0, 1, 1)]];
2214     }
2215     _private->pageRects = [newPageRects retain];
2216     
2217     range->length = [_private->pageRects count];
2218     
2219     return YES;
2220 }
2221
2222 // Return the drawing rectangle for a particular page number
2223 - (NSRect)rectForPage:(int)page {
2224     return [[_private->pageRects objectAtIndex: (page-1)] rectValue];
2225 }
2226
2227 // Calculate the vertical size of the view that fits on a single page
2228 - (float)_calculatePrintHeight {
2229     // Obtain the print info object for the current operation
2230     NSPrintInfo *pi = [[NSPrintOperation currentOperation] printInfo];
2231     
2232     // Calculate the page height in points
2233     NSSize paperSize = [pi paperSize];
2234     return paperSize.height - [pi topMargin] - [pi bottomMargin];
2235 }
2236
2237 - (void)drawPageBorderWithSize:(NSSize)borderSize
2238 {
2239     ASSERT(NSEqualSizes(borderSize, [[[NSPrintOperation currentOperation] printInfo] paperSize]));    
2240     [[self _webView] _drawHeaderAndFooter];
2241 }
2242
2243 - (void)endDocument
2244 {
2245     [super endDocument];
2246     // Note sadly at this point [NSGraphicsContext currentContextDrawingToScreen] is still NO 
2247     [self _setPrinting:NO minimumPageWidth:0.0 maximumPageWidth:0.0 adjustViewSize:YES];
2248     [[self window] setAutodisplay:YES];
2249 }
2250
2251 - (void)_updateTextSizeMultiplier
2252 {
2253     [[self _bridge] setTextSizeMultiplier:[[self _webView] textSizeMultiplier]];    
2254 }
2255
2256 - (BOOL)performKeyEquivalent:(NSEvent *)event
2257 {
2258     // Pass command-key combos through WebCore so Command-return on an active link is treated
2259     // as a modified activation rather than ignored. Note that this means web pages have a crack 
2260     // at intercepting command-modified keypresses now. If this turns out to cause havoc we
2261     // can restrict this to only send the event through WebCore if it contains the Return or
2262     // Enter key.
2263     if ([[self _bridge] interceptKeyEvent:event toView:self]) {
2264         return YES;
2265     }
2266
2267     return [super performKeyEquivalent:event];
2268 }
2269
2270 - (void)keyDown:(NSEvent *)event
2271 {
2272     BOOL intercepted = [[self _bridge] interceptKeyEvent:event toView:self];
2273     if (!intercepted || [event _web_isTabKeyEvent]) {
2274         [super keyDown:event];
2275     }
2276 }
2277
2278 - (void)keyUp:(NSEvent *)event
2279 {
2280     if (![[self _bridge] interceptKeyEvent:event toView:self]) {
2281         [super keyUp:event];
2282     }
2283 }
2284
2285 - (id)accessibilityAttributeValue:(NSString*)attributeName
2286 {
2287     if ([attributeName isEqualToString: NSAccessibilityChildrenAttribute]) {
2288         id accTree = [[self _bridge] accessibilityTree];
2289         if (accTree)
2290             return [NSArray arrayWithObject: accTree];
2291         return nil;
2292     }
2293     return [super accessibilityAttributeValue:attributeName];
2294 }
2295
2296 - (id)accessibilityHitTest:(NSPoint)point
2297 {
2298     id accTree = [[self _bridge] accessibilityTree];
2299     if (accTree) {
2300         NSPoint windowCoord = [[self window] convertScreenToBase: point];
2301         return [accTree accessibilityHitTest: [self convertPoint:windowCoord fromView:nil]];
2302     }
2303     else
2304         return self;
2305 }
2306
2307 - (void)centerSelectionInVisibleArea:(id)sender
2308 {
2309     // FIXME: Does this do the right thing when the selection is not a caret?
2310     [[self _bridge] ensureCaretVisible];
2311 }
2312
2313 - (void)_alterCurrentSelection:(WebSelectionAlteration)alteration direction:(WebSelectionDirection)direction granularity:(WebSelectionGranularity)granularity
2314 {
2315     WebBridge *bridge = [self _bridge];
2316     DOMRange *proposedRange = [bridge rangeByAlteringCurrentSelection:alteration direction:direction granularity:granularity];
2317     WebView *webView = [self _webView];
2318     if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[bridge selectedDOMRange] toDOMRange:proposedRange affinity:[bridge selectionAffinity] stillSelecting:NO]) {
2319         [bridge alterCurrentSelection:alteration direction:direction granularity:granularity];
2320     }
2321 }
2322
2323 - (void)moveBackward:(id)sender
2324 {
2325     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectByCharacter];
2326 }
2327
2328 - (void)moveBackwardAndModifySelection:(id)sender
2329 {
2330     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectByCharacter];
2331 }
2332
2333 - (void)moveDown:(id)sender
2334 {
2335     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectByLine];
2336 }
2337
2338 - (void)moveDownAndModifySelection:(id)sender
2339 {
2340     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectByLine];
2341 }
2342
2343 - (void)moveForward:(id)sender
2344 {
2345     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectByCharacter];
2346 }
2347
2348 - (void)moveForwardAndModifySelection:(id)sender
2349 {
2350     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectByCharacter];
2351 }
2352
2353 - (void)moveLeft:(id)sender
2354 {
2355     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectLeft granularity:WebSelectByCharacter];
2356 }
2357
2358 - (void)moveLeftAndModifySelection:(id)sender
2359 {
2360     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectLeft granularity:WebSelectByCharacter];
2361 }
2362
2363 - (void)moveRight:(id)sender
2364 {
2365     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectRight granularity:WebSelectByCharacter];
2366 }
2367
2368 - (void)moveRightAndModifySelection:(id)sender
2369 {
2370     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectRight granularity:WebSelectByCharacter];
2371 }
2372
2373 - (void)moveToBeginningOfDocument:(id)sender
2374 {
2375     ERROR("unimplemented");
2376 }
2377
2378 - (void)moveToBeginningOfLine:(id)sender
2379 {
2380     ERROR("unimplemented");
2381 }
2382
2383 - (void)moveToBeginningOfParagraph:(id)sender
2384 {
2385     ERROR("unimplemented");
2386 }
2387
2388 - (void)moveToEndOfDocument:(id)sender
2389 {
2390     ERROR("unimplemented");
2391 }
2392
2393 - (void)moveToEndOfLine:(id)sender
2394 {
2395     ERROR("unimplemented");
2396 }
2397
2398 - (void)moveToEndOfParagraph:(id)sender
2399 {
2400     ERROR("unimplemented");
2401 }
2402
2403 - (void)moveUp:(id)sender
2404 {
2405     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectByLine];
2406 }
2407
2408 - (void)moveUpAndModifySelection:(id)sender
2409 {
2410     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectByLine];
2411 }
2412
2413 - (void)moveWordBackward:(id)sender
2414 {
2415     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectBackward granularity:WebSelectByWord];
2416 }
2417
2418 - (void)moveWordBackwardAndModifySelection:(id)sender
2419 {
2420     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectBackward granularity:WebSelectByWord];
2421 }
2422
2423 - (void)moveWordForward:(id)sender
2424 {
2425     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectForward granularity:WebSelectByWord];
2426 }
2427
2428 - (void)moveWordForwardAndModifySelection:(id)sender
2429 {
2430     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectForward granularity:WebSelectByWord];
2431 }
2432
2433 - (void)moveWordLeft:(id)sender
2434 {
2435     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectLeft granularity:WebSelectByWord];
2436 }
2437
2438 - (void)moveWordLeftAndModifySelection:(id)sender
2439 {
2440     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectLeft granularity:WebSelectByWord];
2441 }
2442
2443 - (void)moveWordRight:(id)sender
2444 {
2445     [self _alterCurrentSelection:WebSelectByMoving direction:WebSelectRight granularity:WebSelectByWord];
2446 }
2447
2448 - (void)moveWordRightAndModifySelection:(id)sender
2449 {
2450     [self _alterCurrentSelection:WebSelectByExtending direction:WebSelectRight granularity:WebSelectByWord];
2451 }
2452
2453 - (void)pageDown:(id)sender
2454 {
2455     ERROR("unimplemented");
2456 }
2457
2458 - (void)pageUp:(id)sender
2459 {
2460     ERROR("unimplemented");
2461 }
2462
2463 - (void)_expandSelectionToGranularity:(WebSelectionGranularity)granularity
2464 {
2465     WebBridge *bridge = [self _bridge];
2466     DOMRange *range = [bridge selectedDOMRangeWithGranularity:granularity];
2467     if (range && ![range collapsed]) {
2468         WebView *webView = [self _webView];
2469         if ([[webView _editingDelegateForwarder] webView:webView shouldChangeSelectedDOMRange:[bridge selectedDOMRange] toDOMRange:range affinity:[bridge selectionAffinity] stillSelecting:NO]) {
2470             [bridge setSelectedDOMRange:range affinity:[bridge selectionAffinity]];
2471         }
2472     }
2473 }
2474
2475 - (void)selectParagraph:(id)sender
2476 {
2477     [self _expandSelectionToGranularity:WebSelectByParagraph];
2478 }
2479
2480 - (void)selectLine:(id)sender
2481 {
2482     [self _expandSelectionToGranularity:WebSelectByLine];
2483 }
2484
2485 - (void)selectWord:(id)sender
2486 {
2487     [self _expandSelectionToGranularity:WebSelectByWord];
2488 }
2489
2490 - (void)copy:(id)sender
2491 {
2492     if ([[self _bridge] tryDHTMLCopy]) {
2493         return;     // DHTML did the whole operation
2494     }
2495
2496     if (![self _haveSelection]) {
2497         NSBeep();
2498         return;
2499     }
2500     [self _writeSelectionToPasteboard:[NSPasteboard generalPasteboard]];
2501 }
2502
2503 - (void)cut:(id)sender
2504 {
2505     if ([[self _bridge] tryDHTMLCut]) {
2506         return;     // DHTML did the whole operation
2507     }
2508
2509     if (![self _haveSelection]) {
2510         NSBeep();
2511         return;
2512     }
2513     [self copy:sender];
2514     [[self _bridge] deleteSelection];
2515 }
2516
2517 - (void)delete:(id)sender
2518 {
2519     if (![self _haveSelection]) {
2520         NSBeep();
2521         return;
2522     }
2523     [[self _bridge] deleteSelection];
2524 }
2525
2526 - (void)paste:(id)sender
2527 {
2528     if ([[self _bridge] tryDHTMLPaste]) {
2529         return;     // DHTML did the whole operation
2530     }
2531
2532     [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:YES];
2533 }
2534
2535 - (void)copyFont:(id)sender
2536 {
2537     ERROR("unimplemented");
2538 }
2539
2540 - (void)pasteFont:(id)sender
2541 {
2542     ERROR("unimplemented");
2543 }
2544
2545 - (void)pasteAsPlainText:(id)sender
2546 {
2547     NSString *text = [[NSPasteboard generalPasteboard] stringForType:NSStringPboardType];
2548     WebView *webView = [self _webView];
2549     WebBridge *bridge = [self _bridge];
2550     if ([[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:[bridge selectedDOMRange] givenAction:WebViewInsertActionPasted]) {
2551         [bridge replaceSelectionWithText:text selectReplacement:NO];
2552     }
2553 }
2554
2555 - (void)pasteAsRichText:(id)sender
2556 {
2557     [self _pasteWithPasteboard:[NSPasteboard generalPasteboard] allowPlainText:NO];
2558 }
2559
2560 - (DOMCSSStyleDeclaration *)_fontManagerOperationAsStyle
2561 {
2562     WebBridge *bridge = [self _bridge];
2563     DOMCSSStyleDeclaration *style = [[bridge DOMDocument] createCSSStyleDeclaration];
2564
2565     NSFontManager *fm = [NSFontManager sharedFontManager];
2566
2567     NSFont *a = [fm convertFont:[fm fontWithFamily:@"Helvetica" traits:0 weight:5 size:10]];
2568     NSFont *b = [fm convertFont:[fm fontWithFamily:@"Times" traits:(NSBoldFontMask | NSItalicFontMask) weight:10 size:12]];
2569     
2570     NSString *fa = [a familyName];
2571     NSString *fb = [b familyName];
2572     if ([fa isEqualToString:fb]) {
2573         [style setFontFamily:fa];
2574     }
2575
2576     int sa = [a pointSize];
2577     int sb = [b pointSize];
2578     if (sa == sb) {
2579         [style setFontSize:[NSString stringWithFormat:@"%dpx", sa]];
2580     }
2581
2582     int wa = [fm weightOfFont:a];
2583     int wb = [fm weightOfFont:b];
2584     if (wa == wb) {
2585         if (wa >= 9) {
2586             [style setFontWeight:@"bold"];
2587         } else {
2588             [style setFontWeight:@"normal"];
2589         }
2590     }
2591
2592     BOOL ia = ([fm traitsOfFont:a] & NSItalicFontMask) != 0;
2593     BOOL ib = ([fm traitsOfFont:b] & NSItalicFontMask) != 0;
2594     if (ia == ib) {
2595         if (ia) {
2596             [style setFontStyle:@"italic"];
2597         } else {
2598             [style setFontStyle:@"normal"];
2599         }
2600     }
2601
2602     return style;
2603 }
2604
2605 - (void)changeFont:(id)sender
2606 {
2607     DOMCSSStyleDeclaration *style = [self _fontManagerOperationAsStyle];
2608     WebView *webView = [self _webView];
2609     WebBridge *bridge = [self _bridge];
2610     if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:[bridge selectedDOMRange]]) {
2611         [bridge applyStyle:style];
2612     }
2613 }
2614
2615 - (void)changeAttributes:(id)sender
2616 {
2617     ERROR("unimplemented");
2618 }
2619
2620 - (DOMCSSStyleDeclaration *)_colorPanelColorAsStyleUsingSelector:(SEL)selector
2621 {
2622     WebBridge *bridge = [self _bridge];
2623     DOMCSSStyleDeclaration *style = [[bridge DOMDocument] createCSSStyleDeclaration];
2624     NSColor *color = [[NSColorPanel sharedColorPanel] color];
2625     NSString *colorAsString = [NSString stringWithFormat:@"rgb(%.0f,%.0f,%.0f)", [color redComponent]*255, [color greenComponent]*255, [color blueComponent]*255];
2626     ASSERT([style respondsToSelector:selector]);
2627     [style performSelector:selector withObject:colorAsString];
2628     
2629     return style;
2630 }
2631
2632 - (void)_changeCSSColorUsingSelector:(SEL)selector inRange:(DOMRange *)range
2633 {
2634     DOMCSSStyleDeclaration *style = [self _colorPanelColorAsStyleUsingSelector:selector];
2635     WebView *webView = [self _webView];
2636     if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:range]) {
2637         [[self _bridge] applyStyle:style];
2638     }
2639 }
2640
2641 - (DOMRange *)_entireDOMRange
2642 {
2643     DOMDocument *document = [[self _bridge] DOMDocument];
2644     DOMRange *range = [document createRange];
2645     [range selectNode:[document documentElement]];
2646     return range;
2647 }
2648
2649 - (void)changeDocumentBackgroundColor:(id)sender
2650 {
2651     // Mimicking NSTextView, this method sets the background color for the
2652     // entire document. There is no NSTextView API for setting the background
2653     // color on the selected range only. Note that this method is currently
2654     // never called from the UI (see comment in changeColor:).
2655     // FIXME: this actually has no effect when called, probably due to 3654850. _entireDOMRange seems
2656     // to do the right thing because it works in startSpeaking:, and I know setBackgroundColor: does the
2657     // right thing because I tested it with [[self _bridge] selectedDOMRange].
2658     // FIXME: This won't actually apply the style to the entire range here, because it ends up calling
2659     // [bridge applyStyle:], which operates on the current selection. To make this work right, we'll
2660     // need to save off the selection, temporarily set it to the entire range, make the change, then
2661     // restore the old selection.
2662     [self _changeCSSColorUsingSelector:@selector(setBackgroundColor:) inRange:[self _entireDOMRange]];
2663 }
2664
2665 - (void)changeColor:(id)sender
2666 {
2667     // FIXME: in NSTextView, this method calls changeDocumentBackgroundColor: when a
2668     // private call has earlier been made by [NSFontFontEffectsBox changeColor:], see 3674493. 
2669     // AppKit will have to be revised to allow this to work with anything that isn't an 
2670     // NSTextView. However, this might not be required for Tiger, since the background-color 
2671     // changing box in the font panel doesn't work in Mail (3674481), though it does in TextEdit.
2672     [self _changeCSSColorUsingSelector:@selector(setColor:) inRange:[[self _bridge] selectedDOMRange]];
2673 }
2674
2675 - (void)_alignSelectionUsingCSSValue:(NSString *)CSSAlignmentValue
2676 {
2677     // FIXME 3675191: This doesn't work yet. Maybe it's blocked by 3654850, or maybe something other than
2678     // just applyStyle: needs to be called for block-level attributes like this.
2679     WebBridge *bridge = [self _bridge];
2680     DOMCSSStyleDeclaration *style = [[bridge DOMDocument] createCSSStyleDeclaration];
2681     [style setTextAlign:CSSAlignmentValue];
2682     WebView *webView = [self _webView];
2683     if ([[webView _editingDelegateForwarder] webView:webView shouldApplyStyle:style toElementsInDOMRange:[bridge selectedDOMRange]]) {
2684         [[self _bridge] applyStyle:style];
2685     }
2686 }
2687
2688 - (void)alignCenter:(id)sender
2689 {
2690     [self _alignSelectionUsingCSSValue:@"center"];
2691 }
2692
2693 - (void)alignJustified:(id)sender
2694 {
2695     [self _alignSelectionUsingCSSValue:@"justify"];
2696 }
2697
2698 - (void)alignLeft:(id)sender
2699 {
2700     [self _alignSelectionUsingCSSValue:@"left"];
2701 }
2702
2703 - (void)alignRight:(id)sender
2704 {
2705     [self _alignSelectionUsingCSSValue:@"right"];
2706 }
2707
2708 - (void)indent:(id)sender
2709 {
2710     ERROR("unimplemented");
2711 }
2712
2713 - (void)insertTab:(id)sender
2714 {
2715     WebView *webView = [self _webView];
2716     WebBridge *bridge = [self _bridge];
2717     if ([[webView _editingDelegateForwarder] webView:webView shouldInsertText:@"\t" replacingDOMRange:[bridge selectedDOMRange] givenAction:WebViewInsertActionPasted]) {
2718         [bridge insertText:@"\t"];
2719     }
2720 }
2721
2722 - (void)insertBacktab:(id)sender
2723 {
2724     // Doing nothing matches normal NSTextView behavior. If we ever use WebView for a field-editor-type purpose
2725     // we might add code here.
2726 }
2727
2728 - (void)insertNewline:(id)sender
2729 {
2730     // Perhaps we should make this delegate call sensitive to the real DOM operation we actually do.
2731     WebView *webView = [self _webView];
2732     WebBridge *bridge = [self _bridge];
2733     if ([[webView _editingDelegateForwarder] webView:webView shouldInsertText:@"\n" replacingDOMRange:[bridge selectedDOMRange] givenAction:WebViewInsertActionTyped]) {
2734         [bridge insertNewline];
2735     }
2736 }
2737
2738 - (void)insertParagraphSeparator:(id)sender
2739 {
2740     // FIXME: Should this do something different?
2741     [self insertNewline:sender];
2742 }
2743
2744 - (void)changeCaseOfLetter:(id)sender
2745 {
2746     ERROR("unimplemented");
2747 }
2748
2749 - (void)_changeWordCaseWithSelector:(SEL)selector
2750 {
2751     WebView *webView = [self _webView];
2752     WebBridge *bridge = [self _bridge];
2753     [self selectWord:nil];
2754     NSString *word = [[bridge selectedString] performSelector:selector];
2755     // FIXME: Does this need a different action context other than "typed"?
2756     if ([[webView _editingDelegateForwarder] webView:webView shouldInsertText:word replacingDOMRange:[bridge selectedDOMRange] givenAction:WebViewInsertActionTyped]) {
2757         [bridge replaceSelectionWithText:word selectReplacement:NO];
2758     }
2759 }
2760
2761 - (void)uppercaseWord:(id)sender
2762 {
2763     [self _changeWordCaseWithSelector:@selector(uppercaseString)];
2764 }
2765
2766 - (void)lowercaseWord:(id)sender
2767 {
2768     [self _changeWordCaseWithSelector:@selector(lowercaseString)];
2769 }
2770
2771 - (void)capitalizeWord:(id)sender
2772 {
2773     [self _changeWordCaseWithSelector:@selector(capitalizedString)];
2774 }
2775
2776 - (void)deleteForward:(id)sender
2777 {
2778     ERROR("unimplemented");
2779 }
2780
2781 - (void)deleteBackward:(id)sender
2782 {
2783     WebBridge *bridge = [self _bridge];
2784     if ([bridge isSelectionEditable]) {
2785         WebView *webView = [self _webView];
2786         if ([[webView _editingDelegateForwarder] webView:webView shouldDeleteDOMRange:[bridge selectedDOMRange]]) {
2787             [bridge deleteKeyPressed];
2788         }
2789     }
2790 }
2791
2792 - (void)deleteBackwardByDecomposingPreviousCharacter:(id)sender
2793 {
2794     ERROR("unimplemented");
2795 }
2796
2797 - (void)deleteWordForward:(id)sender
2798 {
2799     [self moveWordForwardAndModifySelection:sender];
2800     [self delete:sender];
2801 }
2802
2803 - (void)deleteWordBackward:(id)sender
2804 {
2805     [self moveWordBackwardAndModifySelection:sender];
2806     [self delete:sender];
2807 }
2808
2809 - (void)deleteToBeginningOfLine:(id)sender
2810 {
2811     [self moveToBeginningOfLine:sender];
2812     [self delete:sender];
2813 }
2814
2815 - (void)deleteToEndOfLine:(id)sender
2816 {
2817     [self moveToEndOfLine:sender];
2818     [self delete:sender];
2819 }
2820
2821 - (void)deleteToBeginningOfParagraph:(id)sender
2822 {
2823     [self moveToBeginningOfParagraph:sender];
2824     [self delete:sender];
2825 }
2826
2827 - (void)deleteToEndOfParagraph:(id)sender
2828 {
2829     [self moveToEndOfParagraph:sender];
2830     [self delete:sender];
2831 }
2832
2833 - (void)complete:(id)sender
2834 {
2835     ERROR("unimplemented");
2836 }
2837
2838 - (void)checkSpelling:(id)sender
2839 {
2840 #if 0
2841     NSTextStorage *text = _getTextStorage(self);
2842     NSTextViewSharedData *sharedData = _getSharedData(self);
2843     if (text && ([text length] > 0) && [self isSelectable]) {
2844         NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
2845         NSRange selCharRange = [self selectedRange];
2846         NSRange misspellCharRange = {0, 0}, grammarCharRange = {0, 0};
2847         unsigned i, count;
2848         NSArray *grammarRanges = nil, *grammarDescriptions = nil;
2849
2850         if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
2851
2852         misspellCharRange = [checker checkSpellingOfString:[text string] startingAt:NSMaxRange(selCharRange) language:nil wrap:YES inSpellDocumentWithTag:[self spellCheckerDocumentTag] wordCount:NULL];
2853 #if GRAMMAR_CHECKING
2854         grammarCharRange = [checker checkGrammarOfString:[text string] startingAt:NSMaxRange(selCharRange) language:nil wrap:YES inSpellDocumentWithTag:[self spellCheckerDocumentTag] ranges:&grammarRanges descriptions:&grammarDescriptions reconnectOnError:YES];
2855 #endif
2856
2857         if (misspellCharRange.length > 0 && (grammarCharRange.length == 0 || misspellCharRange.location <= grammarCharRange.location)) {
2858             // select the text and drive the panel
2859             [self setSelectedRange:misspellCharRange affinity:NSSelectionAffinityUpstream stillSelecting:NO];
2860             if ([self isEditable]) {
2861                 [self _addSpellingAttributeForRange:misspellCharRange];
2862                 sharedData->_excludedSpellingCharRange = misspellCharRange;
2863             }
2864             [self scrollRangeToVisible:misspellCharRange];
2865             [checker updateSpellingPanelWithMisspelledWord:[[text string] substringWithRange:misspellCharRange]];
2866         } else if (grammarCharRange.length > 0) {
2867             [self setSelectedRange:grammarCharRange affinity:NSSelectionAffinityUpstream stillSelecting:NO];
2868             count = [grammarRanges count];
2869             if ([self isEditable]) {
2870                 for (i = 0; i < count; i++) {
2871                     NSRange range = [grammarRanges rangeAtIndex:i];
2872                     range.location += grammarCharRange.location;
2873                     [self _addSpellingAttributeForRange:range];
2874                     [_getLayoutManager(self) addTemporaryAttributes:[NSDictionary dictionaryWithObjectsAndKeys:[grammarDescriptions objectAtIndex:i], NSToolTipAttributeName, nil] forCharacterRange:range];
2875                 }
2876             }
2877             [self scrollRangeToVisible:grammarCharRange];
2878         } else {
2879             // Cause the beep to indicate there are no more misspellings.
2880             [checker updateSpellingPanelWithMisspelledWord:@""];
2881         }
2882     }
2883 #endif
2884 }
2885
2886 - (void)showGuessPanel:(id)sender
2887 {
2888 #if 0
2889     NSTextStorage *text = _getTextStorage(self);
2890     NSTextViewSharedData *sharedData = _getSharedData(self);
2891     NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
2892     if (text && ([text length] > 0) && [self isSelectable]) {
2893         NSRange selCharRange = [self selectedRange];
2894         NSRange misspellCharRange = {0, 0};
2895         if (checker && ![checker windowIsSpellingPanel:[self window]]) {
2896             misspellCharRange = [checker checkSpellingOfString:[text string] startingAt:((selCharRange.location > 0) ? (selCharRange.location - 1) : 0) language:nil wrap:YES inSpellDocumentWithTag:[self spellCheckerDocumentTag] wordCount:NULL];
2897             if (misspellCharRange.length) {
2898                 // select the text and drive the panel
2899                 [self setSelectedRange:misspellCharRange affinity:NSSelectionAffinityUpstream stillSelecting:NO];
2900                 if ([self isEditable]) {
2901                     [self _addSpellingAttributeForRange:misspellCharRange];
2902                     sharedData->_excludedSpellingCharRange = misspellCharRange;
2903                 }
2904                 [self scrollRangeToVisible:misspellCharRange];
2905                 [checker updateSpellingPanelWithMisspelledWord:[[text string] substringWithRange:misspellCharRange]];
2906             }
2907         }
2908     }
2909     if (checker) {
2910         [[checker spellingPanel] orderFront:sender];
2911     }
2912 #endif
2913 }
2914
2915 #if 0
2916
2917 - (void)_changeSpellingToWord:(NSString *)newWord {
2918     NSRange charRange = [self rangeForUserTextChange];
2919     if ([self isEditable] && charRange.location != NSNotFound) {
2920         NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
2921         if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
2922
2923         // Don't correct to empty string.
2924         if ([newWord isEqualToString:@""]) return;
2925
2926         if ([self shouldChangeTextInRange:charRange replacementString:newWord]) {
2927             [self replaceCharactersInRange:charRange withString:newWord];
2928             charRange.length = [newWord length];
2929             [self setSelectedRange:charRange affinity:NSSelectionAffinityUpstream stillSelecting:NO];
2930             [self didChangeText];
2931         }
2932     }
2933 }
2934
2935 - (void)_changeSpellingFromMenu:(id)sender {
2936     [self _changeSpellingToWord:[sender title]];
2937 }
2938
2939 - (void)changeSpelling:(id)sender {
2940     [self _changeSpellingToWord:[[sender selectedCell] stringValue]];
2941 }
2942
2943 - (void)ignoreSpelling:(id)sender {
2944     NSRange charRange = [self rangeForUserTextChange];
2945     if ([self isEditable]) {
2946         NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
2947         NSString *stringToIgnore;
2948         unsigned int length;
2949
2950         if (!checker) return;
2951
2952         stringToIgnore = [sender stringValue];
2953         length = [stringToIgnore length];
2954         if (stringToIgnore && length > 0) {
2955             [checker ignoreWord:stringToIgnore inSpellDocumentWithTag:[self spellCheckerDocumentTag]];
2956             if (length == charRange.length && [stringToIgnore isEqualToString:[[_getTextStorage(self) string] substringWithRange:charRange]]) {
2957                 [self _removeSpellingAttributeForRange:charRange];
2958             }
2959         }
2960     }
2961 }
2962
2963 - (void)_ignoreSpellingFromMenu:(id)sender {
2964     NSRange charRange = [self rangeForUserTextChange];
2965     if ([self isEditable] && charRange.location != NSNotFound && charRange.length > 0) {
2966         NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
2967         if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
2968         [self _removeSpellingAttributeForRange:charRange];
2969         [checker ignoreWord:[[_getTextStorage(self) string] substringWithRange:charRange] inSpellDocumentWithTag:[self spellCheckerDocumentTag]];
2970     }
2971 }
2972
2973 - (void)_learnSpellingFromMenu:(id)sender {
2974     NSRange charRange = [self rangeForUserTextChange];
2975     if ([self isEditable] && charRange.location != NSNotFound && charRange.length > 0) {
2976         NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
2977         if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
2978         [self _removeSpellingAttributeForRange:charRange];
2979         [checker learnWord:[[_getTextStorage(self) string] substringWithRange:charRange]];
2980     }
2981 }
2982
2983 - (void)_forgetSpellingFromMenu:(id)sender {
2984     NSRange charRange = [self rangeForUserTextChange];
2985     if ([self isEditable] && charRange.location != NSNotFound && charRange.length > 0) {
2986         NSSpellChecker *checker = [NSSpellChecker sharedSpellChecker];
2987         if (!checker || [checker windowIsSpellingPanel:[self window]]) return;
2988         [checker forgetWord:[[_getTextStorage(self) string] substringWithRange:charRange]];
2989     }
2990 }
2991
2992 - (void)_openLinkFromMenu:(id)sender {
2993     NSTextStorage *text = _getTextStorage(self);
2994     NSRange charRange = [self selectedRange];
2995     if (charRange.location != NSNotFound && charRange.length > 0) {
2996         id link = [text attribute:NSLinkAttributeName atIndex:charRange.location effectiveRange:NULL];
2997         if (link) {
2998             [self clickedOnLink:link atIndex:charRange.location];
2999         } else {
3000             NSString *string = [[text string] substringWithRange:charRange];
3001             link = [NSURL URLWithString:string];
3002             if (link) [[NSWorkspace sharedWorkspace] openURL:link];
3003         }
3004     }
3005 }
3006
3007 #endif
3008
3009 - (void)performFindPanelAction:(id)sender
3010 {
3011     ERROR("unimplemented");
3012 }
3013
3014 - (void)startSpeaking:(id)sender
3015 {
3016     WebBridge *bridge = [self _bridge];
3017     DOMRange *range = [bridge selectedDOMRange];
3018     if (!range || [range collapsed]) {
3019         range = [self _entireDOMRange];
3020     }
3021     [NSApp speakString:[bridge stringForRange:range]];
3022 }
3023
3024 - (void)stopSpeaking:(id)sender
3025 {
3026     [NSApp stopSpeaking:sender];
3027 }
3028
3029 - (void)insertText:(NSString *)text
3030 {
3031     WebBridge *bridge = [self _bridge];
3032     if ([bridge isSelectionEditable]) {
3033         WebView *webView = [self _webView];
3034         if ([[webView _editingDelegateForwarder] webView:webView shouldInsertText:text replacingDOMRange:[bridge selectedDOMRange] givenAction:WebViewInsertActionTyped]) {
3035             [bridge insertText:text];
3036         }
3037     }
3038 }
3039
3040 @end
3041
3042 @implementation WebHTMLView (WebTextSizing)
3043
3044 - (void)_web_textSizeMultiplierChanged
3045 {
3046     [self _updateTextSizeMultiplier];
3047 }
3048
3049 @end
3050
3051 @implementation NSArray (WebHTMLView)
3052
3053 - (void)_web_makePluginViewsPerformSelector:(SEL)selector withObject:(id)object
3054 {
3055     NSEnumerator *enumerator = [self objectEnumerator];
3056     WebNetscapePluginEmbeddedView *view;
3057     while ((view = [enumerator nextObject]) != nil) {
3058         if ([view isKindOfClass:[WebNetscapePluginEmbeddedView class]]) {
3059             [view performSelector:selector withObject:object];
3060         }
3061     }
3062 }
3063
3064 @end
3065
3066 @implementation WebElementOrTextFilter
3067
3068 + (WebElementOrTextFilter *)filter 
3069 {
3070     if (!elementOrTextFilterInstance)
3071         elementOrTextFilterInstance = [[WebElementOrTextFilter alloc] init];
3072     return elementOrTextFilterInstance;
3073 }
3074
3075 - (short)acceptNode:(DOMNode *)n
3076 {
3077     return ([n isKindOfClass:[DOMElement class]] || [n isKindOfClass:[DOMText class]]) ? DOM_FILTER_ACCEPT : DOM_FILTER_SKIP;
3078 }
3079
3080 @end
3081
3082 @implementation WebHTMLView (WebInternal)
3083
3084 - (void)_updateFontPanel
3085 {
3086     // FIXME: NSTextView bails out if becoming or resigning first responder, for which it has ivar flags. Not
3087     // sure if we need to do something similar.
3088     
3089     WebBridge *bridge = [self _bridge];
3090
3091     if (![bridge isSelectionEditable]) {
3092         return;
3093     }
3094     
3095     NSWindow *window = [self window];
3096     // FIXME: is this first-responder check correct? What happens if a subframe is editable and is first responder?
3097     if ([NSApp keyWindow] != window || [window firstResponder] != self) {
3098         return;
3099     }
3100     
3101     BOOL onlyOneFontInSelection = YES;
3102     NSFont *font = nil;
3103     
3104     if (![bridge haveSelection]) {
3105         font = [bridge fontForCurrentPosition];
3106     } 
3107     else {
3108         DOMRange *selection = [bridge selectedDOMRange];
3109         DOMNode *startContainer = [selection startContainer];
3110         DOMNode *endContainer = [selection endContainer];
3111         
3112         ASSERT(startContainer);
3113         ASSERT(endContainer);
3114         ASSERT([[WebElementOrTextFilter filter] acceptNode:startContainer] == DOM_FILTER_ACCEPT);
3115         ASSERT([[WebElementOrTextFilter filter] acceptNode:endContainer] == DOM_FILTER_ACCEPT);
3116         
3117         font = [bridge renderedFontForNode:startContainer];
3118         
3119         if (startContainer != endContainer) {
3120             DOMDocument *document = [bridge DOMDocument];
3121             DOMTreeWalker *treeWalker = [document createTreeWalker:document :DOM_SHOW_ALL :[WebElementOrTextFilter filter] :NO];
3122             DOMNode *node = startContainer;
3123             [treeWalker setCurrentNode:node];
3124             while (node) {
3125                 NSFont *otherFont = [bridge renderedFontForNode:node];
3126                 if (![font isEqual:otherFont]) {
3127                     onlyOneFontInSelection = NO;
3128                     break;
3129                 }
3130                 if (node == endContainer)
3131                     break;
3132                 node = [treeWalker nextNode];
3133             }
3134         }
3135     }
3136     
3137     // FIXME: for now, return a bogus font that distinguishes the empty selection from the non-empty
3138     // selection. We should be able to remove this once the rest of this code works properly.
3139     if (font == nil) {
3140         if (![bridge haveSelection]) {
3141             font = [NSFont toolTipsFontOfSize:17];
3142         } else {
3143             font = [NSFont menuFontOfSize:23];
3144         }
3145     }
3146     ASSERT(font != nil);
3147         
3148     NSFontManager *fm = [NSFontManager sharedFontManager];
3149     [fm setSelectedFont:font isMultiple:!onlyOneFontInSelection];
3150     // FIXME: we don't keep track of selected attributes, or set them on the font panel. This
3151     // appears to have no effect on the UI. E.g., underlined text in Mail or TextEdit is
3152     // not reflected in the font panel. Maybe someday this will change.
3153 }
3154
3155 - (unsigned int)_delegateDragSourceActionMask
3156 {
3157     WebView *webView = [self _webView];
3158     NSPoint point = [webView convertPoint:[_private->mouseDownEvent locationInWindow] fromView:nil];
3159     _private->dragSourceActionMask = [[webView _UIDelegateForwarder] webView:webView dragSourceActionMaskForPoint:point];
3160     return _private->dragSourceActionMask;
3161 }
3162
3163 @end