1/*
2 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
3 * Copyright (C) 2006 David Smith (catfish.man@gmail.com)
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30#import "WebViewInternal.h"
31
32#import "WebFrameInternal.h"
33#import "WebHTMLView.h"
34#import "WebTextCompletionController.h"
35#import "WebViewData.h"
36#import <WebCore/Frame.h>
37
38using namespace WebCore;
39
40@class NSTextInputContext;
41
42@interface NSResponder (WebNSResponderDetails)
43- (NSTextInputContext *)inputContext;
44@end
45
46#if !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD)
47@interface NSObject (NSTextInputContextDetails)
48- (BOOL)wantsToHandleMouseEvents;
49- (BOOL)handleMouseEvent:(NSEvent *)event;
50@end
51#endif
52
53@implementation WebView (WebViewEventHandling)
54
55static WebView *lastMouseoverView;
56
57- (void)_closingEventHandling
58{
59    if (lastMouseoverView == self)
60        lastMouseoverView = nil;
61}
62
63- (void)_setMouseDownEvent:(NSEvent *)event
64{
65    ASSERT(!event || [event type] == NSLeftMouseDown || [event type] == NSRightMouseDown || [event type] == NSOtherMouseDown);
66
67    if (event == _private->mouseDownEvent)
68        return;
69
70    [event retain];
71    [_private->mouseDownEvent release];
72    _private->mouseDownEvent = event;
73}
74
75- (void)mouseDown:(NSEvent *)event
76{
77    // FIXME (Viewless): This method should be shared with WebHTMLView, which needs to
78    // do the same work in the usesDocumentViews case. We don't want to maintain two
79    // duplicate copies of this method.
80
81    if (_private->usesDocumentViews) {
82        [super mouseDown:event];
83        return;
84    }
85
86    // There's a chance that responding to this event will run a nested event loop, and
87    // fetching a new event might release the old one. Retaining and then autoreleasing
88    // the current event prevents that from causing a problem inside WebKit or AppKit code.
89    [[event retain] autorelease];
90
91    RetainPtr<WebView> protector = self;
92    if ([[self inputContext] wantsToHandleMouseEvents] && [[self inputContext] handleMouseEvent:event])
93        return;
94
95    _private->handlingMouseDownEvent = YES;
96
97    // Record the mouse down position so we can determine drag hysteresis.
98    [self _setMouseDownEvent:event];
99
100    NSInputManager *currentInputManager = [NSInputManager currentInputManager];
101    if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
102        goto done;
103
104    [_private->completionController endRevertingChange:NO moveLeft:NO];
105
106    // If the web page handles the context menu event and menuForEvent: returns nil, we'll get control click events here.
107    // We don't want to pass them along to KHTML a second time.
108    if (!([event modifierFlags] & NSControlKeyMask)) {
109        _private->ignoringMouseDraggedEvents = NO;
110
111        // Don't do any mouseover while the mouse is down.
112        [self _cancelUpdateMouseoverTimer];
113
114        // Let WebCore get a chance to deal with the event. This will call back to us
115        // to start the autoscroll timer if appropriate.
116        if (Frame* frame = [self _mainCoreFrame])
117            frame->eventHandler()->mouseDown(event);
118    }
119
120done:
121    _private->handlingMouseDownEvent = NO;
122}
123
124- (void)mouseUp:(NSEvent *)event
125{
126    // FIXME (Viewless): This method should be shared with WebHTMLView, which needs to
127    // do the same work in the usesDocumentViews case. We don't want to maintain two
128    // duplicate copies of this method.
129
130    if (_private->usesDocumentViews) {
131        [super mouseUp:event];
132        return;
133    }
134
135    // There's a chance that responding to this event will run a nested event loop, and
136    // fetching a new event might release the old one. Retaining and then autoreleasing
137    // the current event prevents that from causing a problem inside WebKit or AppKit code.
138    [[event retain] autorelease];
139
140    [self _setMouseDownEvent:nil];
141
142    NSInputManager *currentInputManager = [NSInputManager currentInputManager];
143    if ([currentInputManager wantsToHandleMouseEvents] && [currentInputManager handleMouseEvent:event])
144        return;
145
146    [self retain];
147
148    [self _stopAutoscrollTimer];
149    if (Frame* frame = [self _mainCoreFrame])
150        frame->eventHandler()->mouseUp(event);
151    [self _updateMouseoverWithFakeEvent];
152
153    [self release];
154}
155
156+ (void)_updateMouseoverWithEvent:(NSEvent *)event
157{
158    WebView *oldView = lastMouseoverView;
159
160    lastMouseoverView = nil;
161
162    NSView *contentView = [[event window] contentView];
163    NSPoint locationForHitTest = [[contentView superview] convertPoint:[event locationInWindow] fromView:nil];
164    for (NSView *hitView = [contentView hitTest:locationForHitTest]; hitView; hitView = [hitView superview]) {
165        if ([hitView isKindOfClass:[WebView class]]) {
166            lastMouseoverView = static_cast<WebView *>(hitView);
167            break;
168        }
169    }
170
171    if (lastMouseoverView && lastMouseoverView->_private->hoverFeedbackSuspended)
172        lastMouseoverView = nil;
173
174    if (lastMouseoverView != oldView) {
175        if (Frame* oldCoreFrame = [oldView _mainCoreFrame]) {
176            NSEvent *oldViewEvent = [NSEvent mouseEventWithType:NSMouseMoved
177                location:NSMakePoint(-1, -1)
178                modifierFlags:[[NSApp currentEvent] modifierFlags]
179                timestamp:[NSDate timeIntervalSinceReferenceDate]
180                windowNumber:[[oldView window] windowNumber]
181                context:[[NSApp currentEvent] context]
182                eventNumber:0 clickCount:0 pressure:0];
183            oldCoreFrame->eventHandler()->mouseMoved(oldViewEvent);
184        }
185    }
186
187    if (!lastMouseoverView)
188        return;
189
190    if (Frame* coreFrame = core([lastMouseoverView mainFrame]))
191        coreFrame->eventHandler()->mouseMoved(event);
192}
193
194- (void)_updateMouseoverWithFakeEvent
195{
196    [self _cancelUpdateMouseoverTimer];
197
198    NSEvent *fakeEvent = [NSEvent mouseEventWithType:NSMouseMoved
199        location:[[self window] convertScreenToBase:[NSEvent mouseLocation]]
200        modifierFlags:[[NSApp currentEvent] modifierFlags]
201        timestamp:[NSDate timeIntervalSinceReferenceDate]
202        windowNumber:[[self window] windowNumber]
203        context:[[NSApp currentEvent] context]
204        eventNumber:0 clickCount:0 pressure:0];
205
206    [[self class] _updateMouseoverWithEvent:fakeEvent];
207}
208
209- (void)_cancelUpdateMouseoverTimer
210{
211    if (_private->updateMouseoverTimer) {
212        CFRunLoopTimerInvalidate(_private->updateMouseoverTimer);
213        CFRelease(_private->updateMouseoverTimer);
214        _private->updateMouseoverTimer = NULL;
215    }
216}
217
218- (void)_stopAutoscrollTimer
219{
220    NSTimer *timer = _private->autoscrollTimer;
221    _private->autoscrollTimer = nil;
222    [_private->autoscrollTriggerEvent release];
223    _private->autoscrollTriggerEvent = nil;
224    [timer invalidate];
225    [timer release];
226}
227
228- (void)_setToolTip:(NSString *)toolTip
229{
230    if (_private->usesDocumentViews) {
231        id documentView = [[[self _selectedOrMainFrame] frameView] documentView];
232        if ([documentView isKindOfClass:[WebHTMLView class]])
233            [documentView _setToolTip:toolTip];
234        return;
235    }
236
237    // FIXME (Viewless): Code to handle tooltips needs to move into WebView.
238}
239
240@end
241