[iOS] Upstream WAK
[WebKit.git] / Source / WebCore / platform / ios / wak / WAKScrollView.mm
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 "WAKViewPrivate.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
46 static 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
254 static 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
267 static 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
296 static 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
310 static 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
328 static 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)actualDocumentVisibleRect
362 {
363     // Only called by WebCore::ScrollView::actualVisibleContentRect
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)documentVisibleExtent
380 {
381     // Only called by WebCore::ScrollView::visibleExtentContentRect
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 actualDocumentVisibleRect];
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)