Fixed: <rdar://problem/3937663> repro assertion failure and crash dragging image...
[WebKit-https.git] / WebKit / Misc.subproj / WebNSViewExtras.m
1 /*
2         WebNSViewExtras.m
3         Copyright (c) 2002, Apple, Inc. All rights reserved.
4 */
5
6 #import <WebKit/WebNSViewExtras.h>
7
8 #import <WebKit/WebDataSource.h>
9 #import <WebKit/WebFramePrivate.h>
10 #import <WebKit/WebFrameViewInternal.h>
11 #import <WebKit/WebImageRenderer.h>
12 #import <WebKit/WebNSImageExtras.h>
13 #import <WebKit/WebNSPasteboardExtras.h>
14 #import <WebKit/WebNSURLExtras.h>
15
16 #import <Foundation/NSString_NSURLExtras.h>
17 #import <Foundation/NSURL_NSURLExtras.h>
18 #import <Foundation/NSURLFileTypeMappings.h>
19
20 #define WebDragStartHysteresisX                 5.0
21 #define WebDragStartHysteresisY                 5.0
22 #define WebMaxDragImageSize                     NSMakeSize(400, 400)
23 #define WebMaxOriginalImageArea                 (1500 * 1500)
24 #define WebDragIconRightInset                   7.0
25 #define WebDragIconBottomInset                  3.0
26
27 #ifdef DEBUG_VIEWS
28 @interface NSObject (Foo)
29 - (void*)_renderFramePart;
30 - (id)_frameForView: (id)aView;
31 @end
32 #endif
33
34 @implementation NSView (WebExtras)
35
36 - (NSView *)_web_superviewOfClass:(Class)class stoppingAtClass:(Class)limitClass
37 {
38     NSView *view = self;
39     while ((view = [view superview]) != nil) {
40         if ([view isKindOfClass:class]) {
41             return view;
42         } else if (limitClass && [view isKindOfClass:limitClass]) {
43             break;
44         }
45     }
46
47     return nil;
48 }
49
50 - (NSView *)_web_superviewOfClass:(Class)class
51 {
52     return [self _web_superviewOfClass:class stoppingAtClass:nil];
53 }
54
55 - (WebFrameView *)_web_parentWebFrameView
56 {
57     WebFrameView *view = (WebFrameView *)[[[self superview] superview] superview];
58     if ([view isKindOfClass: [WebFrameView class]])
59         return view;
60     return nil;
61 }
62
63 - (WebView *)_web_parentWebView
64 {
65     return [[self _web_parentWebFrameView] _webView];
66 }
67
68 /* Determine whether a mouse down should turn into a drag; started as copy of NSTableView code */
69 - (BOOL)_web_dragShouldBeginFromMouseDown:(NSEvent *)mouseDownEvent
70                            withExpiration:(NSDate *)expiration
71                               xHysteresis:(float)xHysteresis
72                               yHysteresis:(float)yHysteresis
73 {
74     NSEvent *nextEvent, *firstEvent, *dragEvent, *mouseUp;
75     BOOL dragIt;
76
77     if ([mouseDownEvent type] != NSLeftMouseDown) {
78         return NO;
79     }
80
81     nextEvent = nil;
82     firstEvent = nil;
83     dragEvent = nil;
84     mouseUp = nil;
85     dragIt = NO;
86
87     while ((nextEvent = [[self window] nextEventMatchingMask:(NSLeftMouseUpMask | NSLeftMouseDraggedMask)
88                                                    untilDate:expiration
89                                                       inMode:NSEventTrackingRunLoopMode
90                                                      dequeue:YES]) != nil) {
91         if (firstEvent == nil) {
92             firstEvent = nextEvent;
93         }
94
95         if ([nextEvent type] == NSLeftMouseDragged) {
96             float deltax = ABS([nextEvent locationInWindow].x - [mouseDownEvent locationInWindow].x);
97             float deltay = ABS([nextEvent locationInWindow].y - [mouseDownEvent locationInWindow].y);
98             dragEvent = nextEvent;
99
100             if (deltax >= xHysteresis) {
101                 dragIt = YES;
102                 break;
103             }
104
105             if (deltay >= yHysteresis) {
106                 dragIt = YES;
107                 break;
108             }
109         } else if ([nextEvent type] == NSLeftMouseUp) {
110             mouseUp = nextEvent;
111             break;
112         }
113     }
114
115     // Since we've been dequeuing the events (If we don't, we'll never see the mouse up...),
116     // we need to push some of the events back on.  It makes sense to put the first and last
117     // drag events and the mouse up if there was one.
118     if (mouseUp != nil) {
119         [NSApp postEvent:mouseUp atStart:YES];
120     }
121     if (dragEvent != nil) {
122         [NSApp postEvent:dragEvent atStart:YES];
123     }
124     if (firstEvent != mouseUp && firstEvent != dragEvent) {
125         [NSApp postEvent:firstEvent atStart:YES];
126     }
127
128     return dragIt;
129 }
130
131 - (BOOL)_web_dragShouldBeginFromMouseDown:(NSEvent *)mouseDownEvent
132                            withExpiration:(NSDate *)expiration
133 {
134     return [self _web_dragShouldBeginFromMouseDown:mouseDownEvent
135                                     withExpiration:expiration
136                                        xHysteresis:WebDragStartHysteresisX
137                                        yHysteresis:WebDragStartHysteresisY];
138 }
139
140
141 - (NSDragOperation)_web_dragOperationForDraggingInfo:(id <NSDraggingInfo>)sender
142 {
143     if (![NSApp modalWindow] && 
144         ![[self window] attachedSheet] &&
145         [sender draggingSource] != self &&
146         [[sender draggingPasteboard] _web_bestURL]) {
147         return NSDragOperationCopy;
148     } else {
149         return NSDragOperationNone;
150     }
151 }
152
153 #ifdef DEBUG_VIEWS
154 - (void)_web_printViewHierarchy: (int)level
155 {
156     NSArray *subviews;
157     int _level = level, i;
158     NSRect f;
159     NSView *subview;
160     void *rfp = 0;
161     
162     subviews = [self subviews];
163     _level = level;
164     while (_level-- > 0)
165         printf (" ");
166     f = [self frame];
167     
168     if ([self respondsToSelector: @selector(_webView)]){
169         id aWebView = [self _webView];
170         id aFrame = [aWebView _frameForView: self];
171         rfp = [aFrame _renderFramePart];
172     }
173     
174     printf ("%s renderFramePart %p (%f,%f) w %f, h %f\n", [[[self class] className] cString], rfp, f.origin.x, f.origin.y, f.size.width, f.size.height);
175     for (i = 0; i < (int)[subviews count]; i++){
176         subview = [subviews objectAtIndex: i];
177         [subview _web_printViewHierarchy: level + 1];
178     }
179 }
180 #endif
181
182 - (void)_web_dragImage:(WebImageRenderer *)wir
183                   rect:(NSRect)rect
184                  event:(NSEvent *)event
185             pasteboard:(NSPasteboard *)pasteboard 
186                 source:(id)source
187                 offset:(NSPoint *)dragImageOffset
188 {
189     NSPoint mouseDownPoint = [self convertPoint:[event locationInWindow] fromView:nil];
190     NSImage *dragImage;
191     NSPoint origin;
192     NSImage *image;
193     
194     image = [wir image];
195     if (image != nil && [image size].height * [image size].width <= WebMaxOriginalImageArea) {
196         NSSize originalSize = rect.size;
197         origin = rect.origin;
198         
199         dragImage = [[image copy] autorelease];
200         [dragImage setScalesWhenResized:YES];
201         [dragImage setSize:originalSize];
202         
203         [dragImage _web_scaleToMaxSize:WebMaxDragImageSize];
204         NSSize newSize = [dragImage size];
205         
206         [dragImage _web_dissolveToFraction:WebDragImageAlpha];
207         
208         // Properly orient the drag image and orient it differently if it's smaller than the original
209         origin.x = mouseDownPoint.x - (((mouseDownPoint.x - origin.x) / originalSize.width) * newSize.width);
210         origin.y = origin.y + originalSize.height;
211         origin.y = mouseDownPoint.y - (((mouseDownPoint.y - origin.y) / originalSize.height) * newSize.height);
212     } else {
213         NSString *extension = [[NSURLFileTypeMappings sharedMappings] preferredExtensionForMIMEType:[wir MIMEType]];
214         if (extension == nil) {
215             extension = @"";
216         }
217         dragImage = [[NSWorkspace sharedWorkspace] iconForFileType:extension];
218         NSSize offset = NSMakeSize([dragImage size].width - WebDragIconRightInset, -WebDragIconBottomInset);
219         origin = NSMakePoint(mouseDownPoint.x - offset.width, mouseDownPoint.y - offset.height);
220     }
221
222     // This is the offset from the lower left corner of the image to the mouse location.  Because we
223     // are a flipped view the calculation of Y is inverted.
224     if (dragImageOffset) {
225         dragImageOffset->x = mouseDownPoint.x - origin.x;
226         dragImageOffset->y = origin.y - mouseDownPoint.y;
227     }
228     
229     // Per kwebster, offset arg is ignored
230     [self dragImage:dragImage at:origin offset:NSZeroSize event:event pasteboard:pasteboard source:source slideBack:YES];
231 }
232
233 - (BOOL)_web_firstResponderIsSelfOrDescendantView
234 {
235     NSResponder *responder = [[self window] firstResponder];
236     return (responder && 
237            (responder == self || 
238            ([responder isKindOfClass:[NSView class]] && [(NSView *)responder isDescendantOf:self])));
239 }
240
241 - (BOOL)_web_firstResponderCausesFocusDisplay
242 {
243     return [self _web_firstResponderIsSelfOrDescendantView] || [[self window] firstResponder] == [self _web_parentWebFrameView];
244 }
245
246 @end
247
248 @implementation NSView (WebDocumentViewExtras)
249
250 - (WebView *)_webView
251 {
252     // We used to use the view hierarchy exclusively here, but that won't work
253     // right when the first viewDidMoveToSuperview call is done, and this wil.
254     return [[self _frame] webView];
255 }
256
257 - (WebFrame *)_frame
258 {
259     WebFrameView *webFrameView = [self _web_parentWebFrameView];
260     return [webFrameView webFrame];
261 }
262
263 // Required so view can access the part's selection.
264 - (WebBridge *)_bridge
265 {
266     return [[self _frame] _bridge];
267 }
268
269 - (WebDataSource *)_dataSource
270 {
271     return [[self _frame] dataSource];
272 }
273
274 - (NSURL *)_webViewURL
275 {
276     return [[[[[self superview] _frame] dataSource] request] URL];
277 }
278 @end