1/*
2 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple 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 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#import "config.h"
27#import "WAKScrollView.h"
28
29#if PLATFORM(IOS)
30
31#import "WAKAppKitStubs.h"
32#import "WAKClipView.h"
33#import "WAKViewInternal.h"
34#import "WAKWindow.h"
35#import "WebEvent.h"
36
37@interface WAKClipView(PrivateAPI)
38- (void)_setDocumentView:(WAKView *)aView;
39@end
40
41// FIXME: get rid of this and use polymorphic response to notifications.
42@interface WAKScrollView()
43- (void)_adjustScrollers;
44@end
45
46static void _notificationCallback(WKViewRef v, WKViewNotificationType type, void *userInfo)
47{
48    UNUSED_PARAM(v);
49    switch (type){
50        case WKViewNotificationViewFrameSizeChanged: {
51            WAKScrollView *scrollView = (WAKScrollView *)userInfo;
52            ASSERT(scrollView);
53            ASSERT([scrollView isKindOfClass:[WAKScrollView class]]);
54            [scrollView _adjustScrollers];
55            break;
56        }
57        default: {
58            break;
59        }
60    }
61}
62
63@implementation WAKScrollView
64
65- (id)initWithFrame:(CGRect)rect
66{
67    WKViewRef view = WKViewCreateWithFrame(rect, &viewContext);
68    viewContext.notificationCallback = _notificationCallback;
69    viewContext.notificationUserInfo = self;
70    self = [super _initWithViewRef:(WKViewRef)view];
71    WKRelease(view);
72
73    _contentView = [[WAKClipView alloc] initWithFrame:rect];
74    [self addSubview:_contentView];
75
76    return self;
77}
78
79- (void)dealloc
80{
81    [_documentView autorelease];
82    [_contentView release];
83    [super dealloc];
84}
85
86- (BOOL)_selfHandleEvent:(WebEvent *)event
87{
88    switch (event.type) {
89        case WebEventScrollWheel:
90            [self scrollWheel:event];
91            return YES;
92        default:
93            return NO;
94    }
95}
96
97- (void)setHasVerticalScroller:(BOOL)flag
98{
99    UNUSED_PARAM(flag);
100}
101
102- (BOOL)hasVerticalScroller
103{
104    return NO;
105}
106
107- (void)setHasHorizontalScroller:(BOOL)flag
108{
109    UNUSED_PARAM(flag);
110}
111
112- (BOOL)hasHorizontalScroller
113{
114    return NO;
115}
116
117- (void)setDelegate:(id)delegate
118{
119    _delegate = delegate;
120}
121
122- (id)delegate
123{
124    return _delegate;
125}
126
127- (CGRect)documentVisibleRect
128{
129    return [_contentView documentVisibleRect];
130}
131
132- (void)setDocumentView:(WAKView *)view
133{
134    if (view != _documentView) {
135        [_documentView release];
136        _documentView = [view retain];
137        [_contentView _setDocumentView:view];
138    }
139}
140
141- (id)documentView
142{
143    return _documentView;
144}
145
146- (WAKClipView *)contentView
147{
148    return _contentView;
149}
150
151- (void)setDrawsBackground:(BOOL)flag
152{
153    UNUSED_PARAM(flag);
154}
155
156- (BOOL)drawsBackground
157{
158    return NO;
159}
160
161- (void)setLineScroll:(float)value
162{
163    UNUSED_PARAM(value);
164}
165
166- (float)verticalLineScroll
167{
168    return 0;
169}
170
171- (float)horizontalLineScroll
172{
173    return 0;
174}
175
176- (void)reflectScrolledClipView:(WAKClipView *)aClipView
177{
178    UNUSED_PARAM(aClipView);
179}
180
181- (void)drawRect:(CGRect)rect
182{
183    UNUSED_PARAM(rect);
184}
185
186// WebCoreFrameView methods
187
188- (void)setHorizontalScrollingMode:(WebCore::ScrollbarMode)mode
189{
190    UNUSED_PARAM(mode);
191}
192
193- (void)setVerticalScrollingMode:(WebCore::ScrollbarMode)mode
194{
195    UNUSED_PARAM(mode);
196}
197
198- (void)setScrollingMode:(WebCore::ScrollbarMode)mode
199{
200    UNUSED_PARAM(mode);
201}
202
203- (WebCore::ScrollbarMode)horizontalScrollingMode
204{
205    return WebCore::ScrollbarAuto;
206}
207
208- (WebCore::ScrollbarMode)verticalScrollingMode
209{
210    return WebCore::ScrollbarAuto;
211}
212
213#pragma mark -
214#pragma mark WebCoreFrameScrollView protocol
215
216- (void)setScrollingModes:(WebCore::ScrollbarMode)hMode vertical:(WebCore::ScrollbarMode)vMode andLock:(BOOL)lock
217{
218    UNUSED_PARAM(hMode);
219    UNUSED_PARAM(vMode);
220    UNUSED_PARAM(lock);
221}
222
223- (void)scrollingModes:(WebCore::ScrollbarMode*)hMode vertical:(WebCore::ScrollbarMode*)vMode
224{
225    UNUSED_PARAM(hMode);
226    UNUSED_PARAM(vMode);
227}
228
229- (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint
230{
231    UNUSED_PARAM(suppressed);
232    UNUSED_PARAM(repaint);
233}
234
235- (void)setScrollOrigin:(NSPoint)scrollOrigin updatePositionAtAll:(BOOL)updatePositionAtAll immediately:(BOOL)updatePositionImmediately
236{
237    UNUSED_PARAM(updatePositionAtAll);
238    UNUSED_PARAM(updatePositionImmediately);
239
240    // The cross-platform ScrollView call already checked to see if the old/new scroll origins were the same or not
241    // so we don't have to check for equivalence here.
242    _scrollOrigin = scrollOrigin;
243
244    [_documentView setBoundsOrigin:NSMakePoint(-scrollOrigin.x, -scrollOrigin.y)];
245}
246
247- (NSPoint)scrollOrigin
248{
249    return _scrollOrigin;
250}
251
252#pragma mark -
253
254static bool shouldScroll(WAKScrollView *scrollView, CGPoint scrollPoint)
255{
256    // We can scroll as long as we are not the last scroll view.
257    WAKView *view = scrollView;
258    while ((view = [view superview]))
259        if ([view isKindOfClass:[WAKScrollView class]])
260            return YES;
261
262    id delegate = [scrollView delegate];
263    SEL selector = @selector(scrollView:shouldScrollToPoint:);
264    return delegate == nil || ![delegate respondsToSelector:selector] || [delegate scrollView:scrollView shouldScrollToPoint:scrollPoint];
265}
266
267static float viewDocumentScrollableLength(WAKScrollView *scrollView, bool horizontalOrientation)
268{
269    float scrollableAmount = 0, documentLength = 0, clipLength = 0;
270    WAKView *documentView = [scrollView documentView];
271    ASSERT(documentView);
272
273    CGRect frame = [documentView frame];
274    if (horizontalOrientation)
275        documentLength = frame.size.width;
276    else
277        documentLength = frame.size.height;
278
279    WAKClipView *clipView = [scrollView contentView];
280    ASSERT_WITH_MESSAGE(clipView, "The WAKClipView is supposed to be created by the WAKScrollView at initialization.");
281    if (clipView) {
282        CGRect frame = [clipView frame];
283        if (horizontalOrientation)
284            clipLength = frame.size.width;
285        else
286            clipLength = frame.size.height;
287    }
288
289    scrollableAmount = documentLength - clipLength;
290    if (scrollableAmount <= 0)
291        scrollableAmount = 0;
292
293    return scrollableAmount;
294}
295
296static float updateScrollerWithDocumentPosition(WAKScrollView *scrollView, bool horizontalOrientation, float documentPosition)
297{
298    float documentOriginPosition = 0.;
299    if (documentPosition > 0) {
300        float scrollableLength = viewDocumentScrollableLength(scrollView, horizontalOrientation);
301        if (scrollableLength > 0) {
302            float scrolledLength = MIN(documentPosition, scrollableLength);
303            documentOriginPosition = -scrolledLength;
304        }
305    }
306
307    return documentOriginPosition;
308}
309
310static bool setDocumentViewOrigin(WAKScrollView *scrollView, WAKView *documentView, CGPoint newDocumentOrigin)
311{
312    ASSERT(documentView);
313    ASSERT(documentView == [scrollView documentView]);
314
315    CGPoint oldDocumentOrigin = [documentView frame].origin;
316    if (!CGPointEqualToPoint(oldDocumentOrigin, newDocumentOrigin)) {
317        [documentView setFrameOrigin:newDocumentOrigin];
318        [scrollView setNeedsDisplay:YES];
319        WKViewRef documentViewRef = [documentView _viewRef];
320        if (documentViewRef->context && documentViewRef->context->notificationCallback)
321            documentViewRef->context->notificationCallback(documentViewRef, WKViewNotificationViewDidScroll, documentViewRef->context->notificationUserInfo);
322        return true;
323    }
324    return false;
325}
326
327
328static BOOL scrollViewToPoint(WAKScrollView *scrollView, CGPoint point)
329{
330    WAKView *documentView = [scrollView documentView];
331    if (!documentView)
332        return NO;
333
334    if (!shouldScroll(scrollView, point))
335        return NO;
336
337    CGPoint newDocumentOrigin;
338    newDocumentOrigin.x = updateScrollerWithDocumentPosition(scrollView, /* horizontal? = */ true, point.x);
339    newDocumentOrigin.y = updateScrollerWithDocumentPosition(scrollView, /* horizontal? = */ false, point.y);
340    return setDocumentViewOrigin(scrollView, documentView, newDocumentOrigin);
341}
342
343- (void)scrollPoint:(NSPoint)point
344{
345    scrollViewToPoint(self, point);
346}
347
348- (void)scrollWheel:(WebEvent *)anEvent
349{
350    if (!_documentView)
351        return [[self nextResponder] scrollWheel:anEvent];
352
353    CGPoint origin = [_documentView frame].origin;
354    origin.x = roundf(-origin.x - anEvent.deltaX);
355    origin.y = roundf(-origin.y - anEvent.deltaY);
356
357    if (!scrollViewToPoint(self, origin))
358        return [[self nextResponder] scrollWheel:anEvent];
359}
360
361- (CGRect)unobscuredContentRect
362{
363    // Only called by WebCore::ScrollView::unobscuredContentRect
364    WAKView* view = self;
365    while ((view = [view superview])) {
366        if ([view isKindOfClass:[WAKScrollView class]])
367            return [self documentVisibleRect];
368    }
369
370    WAKWindow* window = [self window];
371    // If we don't have a WAKWindow, we must be in a offscreen WebView.
372    if (!window)
373        return [self documentVisibleRect];
374
375    CGRect windowVisibleRect = CGRectIntegral([window exposedScrollViewRect]);
376    return [_documentView convertRect:windowVisibleRect fromView:nil];
377}
378
379- (CGRect)exposedContentRect
380{
381    // Only called by WebCore::ScrollView::exposedContentRect
382    WAKView* view = self;
383    while ((view = [view superview])) {
384        if ([view isKindOfClass:[WAKScrollView class]])
385            return [self documentVisibleRect];
386    }
387
388    WAKWindow* window = [self window];
389    // If we don't have a WAKWindow, we must be in a offscreen WebView.
390    if (!window)
391        return [self documentVisibleRect];
392
393    CGRect windowVisibleRect = CGRectIntegral([window extendedVisibleRect]);
394    return [_documentView convertRect:windowVisibleRect fromView:nil];
395}
396
397- (void)setActualScrollPosition:(CGPoint)point
398{
399    WAKView* view = self;
400    while ((view = [view superview])) {
401        if ([view isKindOfClass:[WAKScrollView class]]) {
402            // No need for coordinate transformation if what is being scrolled is a subframe
403            [self scrollPoint:point];
404            return;
405        }
406    }
407
408    if (!_documentView)
409        return;
410    CGPoint windowPoint = [_documentView convertPoint:point toView:nil];
411    [self scrollPoint:windowPoint];
412}
413
414- (NSString *)description
415{
416    NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ; ", [super description]];
417
418    [description appendFormat:@"documentView: WAK: %p; ", _documentView];
419
420    CGRect frame = [self documentVisibleRect];
421    [description appendFormat:@"documentVisible = (%g %g; %g %g); ", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height];
422
423    frame = [self unobscuredContentRect];
424    [description appendFormat:@"actualDocumentVisible = (%g %g; %g %g)>", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height];
425
426    return description;
427}
428
429- (BOOL)inProgrammaticScroll
430{
431    return NO;
432}
433
434- (void)_adjustScrollers
435{
436    // Set the clip view's size to the scroll view's size so the document view can use the correct clip view size when laying out.
437    [_contentView setFrameSize:[self bounds].size];
438
439    if (_documentView) {
440        CGPoint newDocumentOrigin = [_documentView frame].origin;
441        setDocumentViewOrigin(self, _documentView, newDocumentOrigin);
442    }
443}
444
445@end
446
447#endif // PLATFORM(IOS)
448