WAKScrollView.mm   [plain text]


//
//  WAKScrollView.mm
//  WebCore
//
//  Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc.  All rights reserved.
//

#import "config.h"
#import "WKGraphics.h"

#import "WAKAppKitStubs.h"
#import "WAKClipView.h"
#import "WAKScrollView.h"
#import "WAKViewPrivate.h"
#import "WAKWindow.h"
#import "WebEvent.h"

@interface WAKClipView(PrivateAPI)
- (void)_setDocumentView:(WAKView *)aView;
@end

// FIXME: get rid of this and use polymorphic response to notifications.
@interface WAKScrollView()
- (void)_adjustScrollers;
@end

static void _notificationCallback(WKViewRef v, WKViewNotificationType type, void *userInfo)
{
    UNUSED_PARAM(v);
    switch (type){
        case WKViewNotificationViewFrameSizeChanged: {
            WAKScrollView *scrollView = (WAKScrollView *)userInfo;
            ASSERT(scrollView);
            ASSERT([scrollView isKindOfClass:[WAKScrollView class]]);
            [scrollView _adjustScrollers];
            break;
        }
        default: {
            break;
        }
    }
}

@implementation WAKScrollView

- (id)initWithFrame:(CGRect)rect
{
    WKViewRef view = WKViewCreateWithFrame(rect, &viewContext);
    viewContext.notificationCallback = _notificationCallback;
    viewContext.notificationUserInfo = self;
    self = [super _initWithViewRef:(WKViewRef)view];
    WKRelease(view);

    _contentView = [[WAKClipView alloc] initWithFrame:rect];
    [self addSubview:_contentView];

    return self;
}

- (void)dealloc
{
    [_documentView autorelease];
    [_contentView release];
    [super dealloc];
}

- (BOOL)_selfHandleEvent:(WebEvent *)event
{
    switch (event.type) {
        case WebEventScrollWheel:
            [self scrollWheel:event];
            return YES;
        default:
            return NO;
    }
}

- (void)setHasVerticalScroller:(BOOL)flag
{
    UNUSED_PARAM(flag);
}

- (BOOL)hasVerticalScroller
{
    return NO;
}

- (void)setHasHorizontalScroller:(BOOL)flag
{
    UNUSED_PARAM(flag);
}

- (BOOL)hasHorizontalScroller
{
    return NO;
}

- (void)setDelegate:(id)delegate
{
    _delegate = delegate;
}

- (id)delegate
{
    return _delegate;
}

- (CGRect)documentVisibleRect 
{
    return [_contentView documentVisibleRect];
}

- (void)setDocumentView:(WAKView *)view
{
    if (view != _documentView) {
        [_documentView release];
        _documentView = [view retain];
        [_contentView _setDocumentView:view];
    }
}

- (id)documentView 
{
    return _documentView;
}

- (WAKClipView *)contentView 
{ 
    return _contentView;
}

- (void)setDrawsBackground:(BOOL)flag
{
    UNUSED_PARAM(flag);
}

- (BOOL)drawsBackground 
{
    return NO; 
}

- (void)setLineScroll:(float)value
{
    UNUSED_PARAM(value);
}

- (float)verticalLineScroll
{
    return 0;
}

- (float)horizontalLineScroll 
{ 
    return 0;
}

- (void)reflectScrolledClipView:(WAKClipView *)aClipView
{
    UNUSED_PARAM(aClipView);
}

- (void)drawRect:(CGRect)rect
{
    UNUSED_PARAM(rect);
}

// WebCoreFrameView methods

- (void)setHorizontalScrollingMode:(WebCore::ScrollbarMode)mode
{
    UNUSED_PARAM(mode);
}

- (void)setVerticalScrollingMode:(WebCore::ScrollbarMode)mode
{
    UNUSED_PARAM(mode);
}

- (void)setScrollingMode:(WebCore::ScrollbarMode)mode
{
    UNUSED_PARAM(mode);
}

- (WebCore::ScrollbarMode)horizontalScrollingMode
{
    return WebCore::ScrollbarAuto;
}

- (WebCore::ScrollbarMode)verticalScrollingMode
{
    return WebCore::ScrollbarAuto;
}

#pragma mark -
#pragma mark WebCoreFrameScrollView protocol

- (void)setScrollingModes:(WebCore::ScrollbarMode)hMode vertical:(WebCore::ScrollbarMode)vMode andLock:(BOOL)lock
{
    UNUSED_PARAM(hMode);
    UNUSED_PARAM(vMode);
    UNUSED_PARAM(lock);
}

- (void)scrollingModes:(WebCore::ScrollbarMode*)hMode vertical:(WebCore::ScrollbarMode*)vMode
{
    UNUSED_PARAM(hMode);
    UNUSED_PARAM(vMode);
}

- (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint
{
    UNUSED_PARAM(suppressed);
    UNUSED_PARAM(repaint);
}

- (void)setScrollOrigin:(NSPoint)scrollOrigin updatePositionAtAll:(BOOL)updatePositionAtAll immediately:(BOOL)updatePositionImmediately
{
    UNUSED_PARAM(updatePositionAtAll);
    UNUSED_PARAM(updatePositionImmediately);

    // The cross-platform ScrollView call already checked to see if the old/new scroll origins were the same or not
    // so we don't have to check for equivalence here.
    _scrollOrigin = scrollOrigin;

    [_documentView setBoundsOrigin:NSMakePoint(-scrollOrigin.x, -scrollOrigin.y)];
}

- (NSPoint)scrollOrigin
{
    return _scrollOrigin;
}

#pragma mark -

static bool shouldScroll(WAKScrollView *scrollView, CGPoint scrollPoint)
{
    // We can scroll as long as we are not the last scroll view.
    WAKView *view = scrollView;
    while ((view = [view superview]))
        if ([view isKindOfClass:[WAKScrollView class]])
            return YES;

    id delegate = [scrollView delegate];
    SEL selector = @selector(scrollView:shouldScrollToPoint:);
    return delegate == nil || ![delegate respondsToSelector:selector] || [delegate scrollView:scrollView shouldScrollToPoint:scrollPoint];
}

static float viewDocumentScrollableLength(WAKScrollView *scrollView, bool horizontalOrientation)
{
    float scrollableAmount = 0, documentLength = 0, clipLength = 0;
    WAKView *documentView = [scrollView documentView];
    ASSERT(documentView);

    CGRect frame = [documentView frame];
    if (horizontalOrientation)
        documentLength = frame.size.width;
    else
        documentLength = frame.size.height;

    WAKClipView *clipView = [scrollView contentView];
    ASSERT_WITH_MESSAGE(clipView, "The WAKClipView is supposed to be created by the WAKScrollView at initialization.");
    if (clipView) {
        CGRect frame = [clipView frame];
        if (horizontalOrientation)
            clipLength = frame.size.width;
        else
            clipLength = frame.size.height;
    }

    scrollableAmount = documentLength - clipLength;
    if (scrollableAmount <= 0)
        scrollableAmount = 0;

    return scrollableAmount;
}

static float updateScrollerWithDocumentPosition(WAKScrollView *scrollView, bool horizontalOrientation, float documentPosition)
{
    float documentOriginPosition = 0.;
    if (documentPosition > 0) {
        float scrollableLength = viewDocumentScrollableLength(scrollView, horizontalOrientation);
        if (scrollableLength > 0) {
            float scrolledLength = MIN(documentPosition, scrollableLength);
            documentOriginPosition = -scrolledLength;
        }
    }

    return documentOriginPosition;
}

static bool setDocumentViewOrigin(WAKScrollView *scrollView, WAKView *documentView, CGPoint newDocumentOrigin)
{
    ASSERT(documentView);
    ASSERT(documentView == [scrollView documentView]);

    CGPoint oldDocumentOrigin = [documentView frame].origin;
    if (!CGPointEqualToPoint(oldDocumentOrigin, newDocumentOrigin)) {
        [documentView setFrameOrigin:newDocumentOrigin];
        [scrollView setNeedsDisplay:YES];
        WKViewRef documentViewRef = [documentView _viewRef];
        if (documentViewRef->context && documentViewRef->context->notificationCallback)
            documentViewRef->context->notificationCallback(documentViewRef, WKViewNotificationViewDidScroll, documentViewRef->context->notificationUserInfo);
        return true;
    }
    return false;
}


static BOOL scrollViewToPoint(WAKScrollView *scrollView, CGPoint point)
{
    WAKView *documentView = [scrollView documentView];
    if (!documentView)
        return NO;

    if (!shouldScroll(scrollView, point))
        return NO;

    CGPoint newDocumentOrigin;
    newDocumentOrigin.x = updateScrollerWithDocumentPosition(scrollView, /* horizontal? = */ true, point.x);
    newDocumentOrigin.y = updateScrollerWithDocumentPosition(scrollView, /* horizontal? = */ false, point.y);
    return setDocumentViewOrigin(scrollView, documentView, newDocumentOrigin);
}

- (void)scrollPoint:(NSPoint)point
{
    scrollViewToPoint(self, point);
}

- (void)scrollWheel:(WebEvent *)anEvent
{
    if (!_documentView)
        return [[self nextResponder] scrollWheel:anEvent];
    
    CGPoint origin = [_documentView frame].origin;
    origin.x = roundf(-origin.x - anEvent.deltaX);
    origin.y = roundf(-origin.y - anEvent.deltaY);

    if (!scrollViewToPoint(self, origin))
        return [[self nextResponder] scrollWheel:anEvent];
}

- (CGRect)actualDocumentVisibleRect
{
    // Only called by WebCore::ScrollView::actualVisibleContentRect
    WAKView* view = self;
    while ((view = [view superview])) {
        if ([view isKindOfClass:[WAKScrollView class]])
            return [self documentVisibleRect];
    }

    WAKWindow* window = [self window];
    // If we don't have a WAKWindow, we must be in a offscreen WebView.
    if (!window)
        return [self documentVisibleRect];

    CGRect windowVisibleRect = CGRectIntegral([window exposedScrollViewRect]);
    return [_documentView convertRect:windowVisibleRect fromView:nil];
}

- (CGRect)documentVisibleExtent
{
    // Only called by WebCore::ScrollView::visibleExtentContentRect
    WAKView* view = self;
    while ((view = [view superview])) {
        if ([view isKindOfClass:[WAKScrollView class]])
            return [self documentVisibleRect];
    }

    WAKWindow* window = [self window];
    // If we don't have a WAKWindow, we must be in a offscreen WebView.
    if (!window)
        return [self documentVisibleRect];

    CGRect windowVisibleRect = CGRectIntegral([window extendedVisibleRect]);
    return [_documentView convertRect:windowVisibleRect fromView:nil];
}

- (void)setActualScrollPosition:(CGPoint)point
{
    WAKView* view = self;
    while ((view = [view superview])) {
        if ([view isKindOfClass:[WAKScrollView class]]) {
            // No need for coordinate transformation if what is being scrolled is a subframe
            [self scrollPoint:point];
            return;
        }
    }

    if (!_documentView)
        return;
    CGPoint windowPoint = [_documentView convertPoint:point toView:nil];
    [self scrollPoint:windowPoint];
}

- (NSString *)description
{
    NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: ; ", [super description]];

    [description appendFormat:@"documentView: WAK: %p; ", _documentView];

    CGRect frame = [self documentVisibleRect];
    [description appendFormat:@"documentVisible = (%g %g; %g %g); ", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height];

    frame = [self actualDocumentVisibleRect];
    [description appendFormat:@"actualDocumentVisible = (%g %g; %g %g)>", frame.origin.x, frame.origin.y, frame.size.width, frame.size.height];

    return description;
}

- (BOOL)inProgrammaticScroll
{
    return NO;
}

- (void)_adjustScrollers
{
    // 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.
    [_contentView setFrameSize:[self bounds].size];

    if (_documentView) {
        CGPoint newDocumentOrigin = [_documentView frame].origin;
        setDocumentViewOrigin(self, _documentView, newDocumentOrigin);
    }
}

@end