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