1/*
2 * Copyright (C) 2005, 2006 Apple Computer, Inc.  All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#import <WebKit/WebNSViewExtras.h>
30
31#import <WebKit/DOMExtensions.h>
32#import <WebKit/WebDataSource.h>
33#import <WebKit/WebFramePrivate.h>
34#import <WebKit/WebFrameViewInternal.h>
35#import <WebKit/WebNSImageExtras.h>
36#import <WebKit/WebNSPasteboardExtras.h>
37#import <WebKit/WebNSURLExtras.h>
38#import <WebKit/WebView.h>
39
40#define WebDragStartHysteresisX                 5.0f
41#define WebDragStartHysteresisY                 5.0f
42#define WebMaxDragImageSize                     NSMakeSize(400.0f, 400.0f)
43#define WebMaxOriginalImageArea                 (1500.0f * 1500.0f)
44#define WebDragIconRightInset                   7.0f
45#define WebDragIconBottomInset                  3.0f
46
47@implementation NSView (WebExtras)
48
49- (NSView *)_web_superviewOfClass:(Class)class
50{
51    NSView *view = [self superview];
52    while (view  && ![view isKindOfClass:class])
53        view = [view superview];
54    return view;
55}
56
57- (WebFrameView *)_web_parentWebFrameView
58{
59    return (WebFrameView *)[self _web_superviewOfClass:[WebFrameView class]];
60}
61
62// FIXME: Mail is the only client of _webView, remove this method once no versions of Mail need it.
63- (WebView *)_webView
64{
65    return (WebView *)[self _web_superviewOfClass:[WebView class]];
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
148        return NSDragOperationCopy;
149    }
150
151    return NSDragOperationNone;
152}
153
154- (void)_web_DragImageForElement:(DOMElement *)element
155                         rect:(NSRect)rect
156                        event:(NSEvent *)event
157                   pasteboard:(NSPasteboard *)pasteboard
158                       source:(id)source
159                       offset:(NSPoint *)dragImageOffset
160{
161    NSPoint mouseDownPoint = [self convertPoint:[event locationInWindow] fromView:nil];
162    NSImage *dragImage;
163    NSPoint origin;
164
165    NSImage *image = [element image];
166    if (image != nil && [image size].height * [image size].width <= WebMaxOriginalImageArea) {
167        NSSize originalSize = rect.size;
168        origin = rect.origin;
169
170        dragImage = [[image copy] autorelease];
171        [dragImage setScalesWhenResized:YES];
172        [dragImage setSize:originalSize];
173
174        [dragImage _web_scaleToMaxSize:WebMaxDragImageSize];
175        NSSize newSize = [dragImage size];
176
177        [dragImage _web_dissolveToFraction:WebDragImageAlpha];
178
179        // Properly orient the drag image and orient it differently if it's smaller than the original
180        origin.x = mouseDownPoint.x - (((mouseDownPoint.x - origin.x) / originalSize.width) * newSize.width);
181        origin.y = origin.y + originalSize.height;
182        origin.y = mouseDownPoint.y - (((mouseDownPoint.y - origin.y) / originalSize.height) * newSize.height);
183    } else {
184        // FIXME: This has been broken for a while.
185        // There's no way to get the MIME type for the image from a DOM element.
186        // The old code used WKGetPreferredExtensionForMIMEType([image MIMEType]);
187        NSString *extension = @"";
188        dragImage = [[NSWorkspace sharedWorkspace] iconForFileType:extension];
189        NSSize offset = NSMakeSize([dragImage size].width - WebDragIconRightInset, -WebDragIconBottomInset);
190        origin = NSMakePoint(mouseDownPoint.x - offset.width, mouseDownPoint.y - offset.height);
191    }
192
193    // This is the offset from the lower left corner of the image to the mouse location.  Because we
194    // are a flipped view the calculation of Y is inverted.
195    if (dragImageOffset) {
196        dragImageOffset->x = mouseDownPoint.x - origin.x;
197        dragImageOffset->y = origin.y - mouseDownPoint.y;
198    }
199
200    // Per kwebster, offset arg is ignored
201    [self dragImage:dragImage at:origin offset:NSZeroSize event:event pasteboard:pasteboard source:source slideBack:YES];
202}
203
204- (BOOL)_web_firstResponderIsSelfOrDescendantView
205{
206    NSResponder *responder = [[self window] firstResponder];
207    return (responder &&
208           (responder == self ||
209           ([responder isKindOfClass:[NSView class]] && [(NSView *)responder isDescendantOf:self])));
210}
211
212- (NSRect)_web_convertRect:(NSRect)aRect toView:(NSView *)aView
213{
214    // Converting to this view's window; let -convertRect:toView: handle it
215    if (aView == nil)
216        return [self convertRect:aRect toView:nil];
217
218    // This view must be in a window.  Do whatever weird thing -convertRect:toView: does in this situation.
219    NSWindow *thisWindow = [self window];
220    if (!thisWindow)
221        return [self convertRect:aRect toView:aView];
222
223    // The other view must be in a window, too.
224    NSWindow *otherWindow = [aView window];
225    if (!otherWindow)
226        return [self convertRect:aRect toView:aView];
227
228    // Convert to this window's coordinates
229    NSRect convertedRect = [self convertRect:aRect toView:nil];
230
231    // Convert to screen coordinates
232    convertedRect.origin = [thisWindow convertBaseToScreen:convertedRect.origin];
233
234    // Convert to other window's coordinates
235    convertedRect.origin = [otherWindow convertScreenToBase:convertedRect.origin];
236
237    // Convert to other view's coordinates
238    convertedRect = [aView convertRect:convertedRect fromView:nil];
239
240    return convertedRect;
241}
242
243@end
244