WAKView.mm   [plain text]


/*
 * Copyright (C) 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

#import "config.h"
#import "WAKViewInternal.h"

#if PLATFORM(IOS)

#import "GraphicsContext.h"
#import "WAKClipView.h"
#import "WAKScrollView.h"
#import "WAKWindow.h"
#import "WKGraphics.h"
#import "WKUtilities.h"
#import "WKViewPrivate.h"
#import "WebCoreThreadMessage.h"
#import "WebEvent.h"
#import <wtf/Assertions.h>

WEBCORE_EXPORT NSString *WAKViewFrameSizeDidChangeNotification =   @"WAKViewFrameSizeDidChangeNotification";
WEBCORE_EXPORT NSString *WAKViewDidScrollNotification =            @"WAKViewDidScrollNotification";

static WAKView *globalFocusView = nil;
static CGInterpolationQuality sInterpolationQuality;

static void setGlobalFocusView(WAKView *view)
{
    if (view == globalFocusView)
        return;

    [view retain];
    [globalFocusView release];
    globalFocusView = view;
}

static WAKScrollView *enclosingScrollView(WAKView *view)
{
    view = [view superview];
    while (view && ![view isKindOfClass:[WAKScrollView class]])
        view = [view superview];
    return (WAKScrollView *)view;
}

@interface WAKScrollView()
- (void)_adjustScrollers;
@end

@interface WAKView (WAKInternal)
- (id)_initWithViewRef:(WKViewRef)viewRef;
@end

@implementation WAKView

static NSInvocation* invocationForPostNotification(NSString *name, id object, id userInfo)
{
    NSNotificationCenter *target = [NSNotificationCenter defaultCenter];
    NSInvocation *invocation = WebThreadMakeNSInvocation(target, @selector(postNotificationName:object:userInfo:));
    [invocation setArgument:&name atIndex:2];
    [invocation setArgument:&object atIndex:3];
    [invocation setArgument:&userInfo atIndex:4];
    return invocation;
}

static void notificationCallback (WKViewRef v, WKViewNotificationType type, void *userInfo)
{
    UNUSED_PARAM(v);
    WAKView *view = (WAKView *)userInfo;
    switch (type){
        case WKViewNotificationViewDidMoveToWindow: {
            [view viewDidMoveToWindow];
            break;
        }
        case WKViewNotificationViewFrameSizeChanged: {
            [view frameSizeChanged];
            if (WAKScrollView *scrollView = enclosingScrollView(view))
                [scrollView _adjustScrollers];

            // Posting a notification to the main thread can cause the WebThreadLock to be
            // relinquished, which gives the main thread a change to run (and possible try
            // to paint). We don't want this to happen if we've updating view sizes in the middle
            // of layout, so use an async notification. <rdar://problem/6745974>
            NSInvocation *invocation = invocationForPostNotification(WAKViewFrameSizeDidChangeNotification, view, nil);
            WebThreadCallDelegateAsync(invocation);
            break;
        }
        case WKViewNotificationViewDidScroll: {
            WebThreadRunOnMainThread(^ {
                 [[NSNotificationCenter defaultCenter] postNotificationName:WAKViewDidScrollNotification object:view userInfo:nil];
            });
            break;
        }            
        default: {
            break;
        }
    }
}

- (void)handleEvent:(WebEvent *)event
{
    ASSERT(event);
    WAKView *view = self;
    while (view) {
        if ([view _selfHandleEvent:event])
            break;
        view = [view superview];
    }
}

- (BOOL)_selfHandleEvent:(WebEvent *)event
{
    WebEventType type = event.type;
    
    switch (type) {
        case WebEventMouseDown: {
            [self mouseDown:event];
            break;
        }
        case WebEventMouseUp: {
            [self mouseUp:event];
            break;
        }
        case WebEventMouseMoved: {
            [self mouseMoved:event];
            break;
        }
        case WebEventKeyDown: {
            [self keyDown:event];
            break;
        }
        case WebEventKeyUp: {
            [self keyUp:event];
            break;
        }
        case WebEventScrollWheel: {
            [self scrollWheel:event];
            break;
        }
#if ENABLE(TOUCH_EVENTS)
        case WebEventTouchBegin:
        case WebEventTouchChange:
        case WebEventTouchEnd:
        case WebEventTouchCancel: {
            [self touch:event];
            break;
        }
#endif
        default:
            ASSERT_NOT_REACHED();
            break;
    }
    return YES;
}

- (NSResponder *)nextResponder
{
    return [self superview];
}

static bool responderCallback(WKViewRef, WKViewResponderCallbackType type, void *userInfo)
{
    return [(WAKView *)userInfo _handleResponderCall:type];
}

- (BOOL)_handleResponderCall:(WKViewResponderCallbackType)type
{
    switch (type) {
        case WKViewResponderAcceptsFirstResponder:
            return [self acceptsFirstResponder];
        case WKViewResponderBecomeFirstResponder:
            return [self becomeFirstResponder];
        case WKViewResponderResignFirstResponder:
            return [self resignFirstResponder];
    }
    return NO;
}

static void willRemoveSubviewCallback(WKViewRef view, WKViewRef subview)
{
    [WAKViewForWKViewRef(view) willRemoveSubview:WAKViewForWKViewRef(subview)];
}

static void invalidateGStateCallback(WKViewRef view)
{
    [WAKViewForWKViewRef(view) invalidateGState];
}

+ (WAKView *)_wrapperForViewRef:(WKViewRef)_viewRef
{
    ASSERT(_viewRef);
    if (_viewRef->isa.classInfo == &WKViewClassInfo)
        return [[[WAKView alloc] _initWithViewRef:_viewRef] autorelease];
    WKError ("unable to create wrapper for %s\n", _viewRef->isa.classInfo->name);
    return nil;
}

- (id)_initWithViewRef:(WKViewRef)viewR
{
    self = [super init];
    if (!self)
        return nil;

    viewRef = (WKViewRef)WKRetain (viewR);
    viewRef->wrapper = (void *)self;

    return self;
}

- (id)init
{
    return [self initWithFrame:CGRectZero];
}

- (id)initWithFrame:(CGRect)rect
{
    WKViewRef view = WKViewCreateWithFrame(rect, &viewContext);
    self = [self _initWithViewRef:view];
    if (self) {
        viewContext.notificationCallback = notificationCallback;
        viewContext.notificationUserInfo = self;
        viewContext.responderCallback = responderCallback;
        viewContext.responderUserInfo = self;
        viewContext.willRemoveSubviewCallback = willRemoveSubviewCallback;
        viewContext.invalidateGStateCallback = invalidateGStateCallback;
    }
    WKRelease(view);
    return self;
}

- (void)dealloc
{
    [[[subviewReferences copy] autorelease] makeObjectsPerformSelector:@selector(removeFromSuperview)];

    if (viewRef) {
        _WKViewSetViewContext (viewRef, 0);
        viewRef->wrapper = NULL;
        WKRelease (viewRef);
    }
    
    [subviewReferences release];
    
    [super dealloc];
}

- (WAKWindow *)window
{
    return WKViewGetWindow(viewRef);
}

- (WKViewRef)_viewRef
{
    return viewRef;
}

- (NSMutableSet *)_subviewReferences
{
    if (!subviewReferences)
        subviewReferences = [[NSMutableSet alloc] init];
    return subviewReferences;
}

static void _WAKCopyWrapper(const void *value, void *context)
{
    if (!value)
        return;
    if (!context)
        return;
    
    NSMutableArray *array = (NSMutableArray *)context;
    WAKView *view = WAKViewForWKViewRef((WKViewRef)value);
    if (view)
        [array addObject:view];
}

- (NSArray *)subviews
{
    CFArrayRef subviews = WKViewGetSubviews([self _viewRef]);
    if (!subviews)
        return [NSArray array];
    
    CFIndex count = CFArrayGetCount(subviews);
    if (count == 0)
        return [NSArray array];
    
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:count];
    if (!result)
        return [NSArray array];
    
    CFArrayApplyFunction(subviews, CFRangeMake(0, count), _WAKCopyWrapper, (void*)result);
    
    return result;
}

- (WAKView *)superview
{
    return WAKViewForWKViewRef(viewRef->superview);
}

- (WAKView *)lastScrollableAncestor
{
    WAKView *view = nil;
    WAKScrollView *scrollView = enclosingScrollView(self);
    
    while (scrollView) {
        
        CGSize scrollViewSize = WKViewGetFrame((WKViewRef)scrollView).size;

        WAKView *documentView = [scrollView documentView];
        scrollView = enclosingScrollView(scrollView);
                                         
        // If this document view can be scrolled, and this is NOT the last scroll view.
        // Our last scroll view is non-existent as far as we're concerned.

        if (scrollView && !CGSizeEqualToSize(scrollViewSize, [documentView frame].size))
            view = documentView;
    }
    
    return view;
}

- (void)addSubview:(WAKView *)subview
{
    [subview retain];
    [subview removeFromSuperview];
    WKViewAddSubview (viewRef, [subview _viewRef]);
    
    // Keep a reference to subview so it sticks around.
    [[self _subviewReferences] addObject:subview];
    [subview release];
}

- (void)willRemoveSubview:(WAKView *)subview
{
    UNUSED_PARAM(subview);
}

- (void)removeFromSuperview
{
    WAKView *oldSuperview = [[self superview] retain];
    WKViewRemoveFromSuperview (viewRef);
    [[oldSuperview _subviewReferences] removeObject:self];
    [oldSuperview release];
}

- (void)viewDidMoveToWindow
{
}

- (void)frameSizeChanged
{
}

- (void)setNeedsDisplay:(BOOL)flag
{
    if (!flag)
        return;
    [self setNeedsDisplayInRect:WKViewGetBounds(viewRef)];
}

- (void)setNeedsDisplayInRect:(CGRect)invalidRect
{
    if (_isHidden)
        return;

    WAKWindow *window = [self window];
    if (!window || CGRectIsEmpty(invalidRect))
        return;

    CGRect rect = CGRectIntersection(invalidRect, [self bounds]);
    if (CGRectIsEmpty(rect))
        return;

    CGRect baseRect = WKViewConvertRectToBase(viewRef, rect);
    [window setNeedsDisplayInRect:baseRect];
}

- (BOOL)needsDisplay 
{
    return NO;
}

- (void)display
{
    [self displayRect:WKViewGetBounds(viewRef)];
}

- (void)displayIfNeeded
{
}

- (void)drawRect:(CGRect)rect
{
    // Do nothing.  Override in subclass.
    UNUSED_PARAM(rect);
}

- (void)viewWillDraw
{
    [[self subviews] makeObjectsPerformSelector:@selector(viewWillDraw)];
}

+ (WAKView *)focusView
{
    return globalFocusView;
}

- (NSRect)bounds
{ 
    return WKViewGetBounds (viewRef);
}

- (NSRect)frame 
{ 
    return WKViewGetFrame (viewRef);
}

- (void)setFrame:(NSRect)frameRect
{
    WKViewSetFrameOrigin (viewRef, frameRect.origin);
    WKViewSetFrameSize (viewRef, frameRect.size);
}

- (void)setFrameOrigin:(NSPoint)newOrigin
{
    WKViewSetFrameOrigin (viewRef, newOrigin);
}

- (void)setFrameSize:(NSSize)newSize
{
    WKViewSetFrameSize (viewRef, newSize);
}

- (void)setBoundsSize:(NSSize)size
{
    WKViewSetBoundsSize (viewRef, size);
}

- (void)setBoundsOrigin:(NSPoint)newOrigin
{
    WKViewSetBoundsOrigin(viewRef, newOrigin);
}

- (void)_lockFocusViewInContext:(CGContextRef)context
{
    ASSERT(context);
    setGlobalFocusView(self);
    CGContextSaveGState(context);

    WAKView *superview = [self superview];
    if (superview)
        CGContextClipToRect(context, [superview bounds]);

    CGContextConcatCTM(context, _WKViewGetTransform(viewRef));
}

- (void)_unlockFocusViewInContext:(CGContextRef)context
{
    ASSERT(context);
    CGContextRestoreGState(context);
    setGlobalFocusView(nil);
}

static CGInterpolationQuality toCGInterpolationQuality(WebCore::InterpolationQuality quality)
{
    switch (quality) {
    case WebCore::InterpolationDefault:
        return kCGInterpolationDefault;
    case WebCore::InterpolationNone:
        return kCGInterpolationNone;
    case WebCore::InterpolationLow:
        return kCGInterpolationLow;
    case WebCore::InterpolationMedium:
        return kCGInterpolationMedium;
    case WebCore::InterpolationHigh:
        return kCGInterpolationHigh;
    default:
        ASSERT_NOT_REACHED();
        return kCGInterpolationLow;
    }
}

+ (void)_setInterpolationQuality:(int)quality
{
    sInterpolationQuality = toCGInterpolationQuality((WebCore::InterpolationQuality)quality);
}

- (void)_drawRect:(NSRect)dirtyRect context:(CGContextRef)context lockFocus:(bool)lockFocus
{
    if (_isHidden)
        return;

    ASSERT(viewRef);
    ASSERT(context);
    CGRect localRect = WKViewGetBounds(viewRef);
    dirtyRect = CGRectIntersection(localRect, dirtyRect);
    if (CGRectIsEmpty(dirtyRect))
        return;

    if (lockFocus)
        [self _lockFocusViewInContext:context];

    if (viewRef->scale != 1)
        CGContextSetInterpolationQuality(context, sInterpolationQuality);

    CGContextClipToRect(context, dirtyRect);
    [self drawRect:dirtyRect];

    if (!_drawsOwnDescendants) {
        NSArray *subViews = [self subviews];
        for (WAKView *subView in subViews) {
            NSRect childDirtyRect = [self convertRect:dirtyRect toView:subView];
            [subView _drawRect:CGRectIntegral(childDirtyRect) context:context lockFocus:YES];
        }
    }

    if (lockFocus)
        [self _unlockFocusViewInContext:context];
}

- (void)displayRect:(NSRect)rect
{
    CGContextRef context = WKGetCurrentGraphicsContext();
    if (!context) {
        WKError ("unable to get context for view");
        return;
    }

    [self _drawRect:rect context:context lockFocus:YES];
}

- (void)displayRectIgnoringOpacity:(NSRect)rect
{
    [self displayRect:rect];
}

- (void)displayRectIgnoringOpacity:(NSRect)rect inContext:(CGContextRef)context
{
    if (!context) {
        WKError ("invalid parameter: context must not be NULL");
        return;
    }

    CGContextRef previousContext = WKGetCurrentGraphicsContext();
    if (context != previousContext)
        WKSetCurrentGraphicsContext(context);

    [self _drawRect:rect context:context lockFocus:NO];

    if (context != previousContext)
        WKSetCurrentGraphicsContext (previousContext);
}

- (NSRect)visibleRect
{
    return WKViewGetVisibleRect (viewRef);
}

- (NSPoint)convertPoint:(NSPoint)aPoint toView:(WAKView *)aView 
{
    CGPoint p = WKViewConvertPointToBase (viewRef, aPoint);
    if (aView)
        return WKViewConvertPointFromBase ([aView _viewRef], p);
    return p;
}

- (NSPoint)convertPoint:(NSPoint)aPoint fromView:(WAKView *)aView
{
    if (aView)
        aPoint = WKViewConvertPointToBase ([aView _viewRef], aPoint);
    return WKViewConvertPointFromBase (viewRef, aPoint);
}

- (NSSize)convertSize:(NSSize)size toView:(WAKView *)aView
{
    return [self convertRect:NSMakeRect(0.0f, 0.0f, size.width, size.height) toView:aView].size;
}

- (NSRect)convertRect:(NSRect)aRect fromView:(WAKView *)aView
{
    if (aView)
        aRect = WKViewConvertRectToBase ([aView _viewRef], aRect);
    return WKViewConvertRectFromBase (viewRef, aRect);
}

- (NSRect)convertRect:(NSRect)aRect toView:(WAKView *)aView
{
    CGRect r = WKViewConvertRectToBase (viewRef, aRect);
    if (aView)
        return WKViewConvertRectFromBase ([aView _viewRef], r);
    return r;
}

- (void)lockFocus
{
    [self _lockFocusViewInContext:WKGetCurrentGraphicsContext()];
}

- (void)unlockFocus
{
    [self _unlockFocusViewInContext:WKGetCurrentGraphicsContext()];
}

- (WAKView *)hitTest:(NSPoint)point
{
    if (!CGRectContainsPoint([self frame], point))
        return nil;

    CGPoint subviewPoint = WKViewConvertPointFromSuperview(viewRef, point);
    for (WAKView *subview in [self subviews]) {
        if (WAKView *hitView = [subview hitTest: subviewPoint])
            return hitView;
    }

    return self;
}

- (void)setHidden:(BOOL)flag 
{
    ASSERT(viewRef);
    if (_isHidden != flag) {
        // setNeedsDisplay does nothing if the view is hidden so call it at the right time.
        if (flag) {
            [self setNeedsDisplay:YES];
            _isHidden = flag;
        } else {
            _isHidden = flag;
            [self setNeedsDisplay:YES];
        }
    }
}

- (BOOL)isDescendantOf:(NSView *)aView
{
    if (aView == self)
        return YES;
    else if (![self superview])
        return NO;
    else
        return [[self superview] isDescendantOf:aView];
}

- (BOOL)isHiddenOrHasHiddenAncestor
{
    if (_isHidden)
        return YES;

    if (![self superview])
        return NO;

    return [[self superview] isHiddenOrHasHiddenAncestor];
}

- (BOOL)mouse:(NSPoint)aPoint inRect:(NSRect)aRect 
{ 
    return WKMouseInRect (aPoint, aRect);
}

- (BOOL)needsPanelToBecomeKey
{
    return true;
}

- (void)setNextKeyView:(NSView *)aView
{
    UNUSED_PARAM(aView);
}

- (WAKView *)previousValidKeyView { return nil; }
- (WAKView *)nextKeyView { return nil; }
- (WAKView *)nextValidKeyView { return nil; }
- (WAKView *)previousKeyView { return nil; }

- (void)invalidateGState { }
- (void)releaseGState { }
- (BOOL)inLiveResize { return NO; }

- (void)setAutoresizingMask:(unsigned int)mask
{
    WKViewSetAutoresizingMask(viewRef, mask);
}

- (unsigned int)autoresizingMask
{
    return WKViewGetAutoresizingMask(viewRef);
}

- (void)scrollPoint:(NSPoint)point 
{
    if (WAKScrollView *scrollView = enclosingScrollView(self)) {
        CGPoint scrollViewPoint = [self convertPoint:point toView:scrollView];
        [scrollView scrollPoint:scrollViewPoint];
    }
}

- (BOOL)scrollRectToVisible:(NSRect)rect
{
    // Scroll if the rect is not already visible.
    CGRect visibleRect = [self visibleRect];
    if (!CGRectContainsRect(visibleRect, rect) && !CGRectContainsRect(rect, visibleRect))
        [self scrollPoint:rect.origin];

    // Return value ignored by WebCore.

    return NO;
}

// Overridden by subclasses (only WebHTMLView for now).
- (void)setNeedsLayout:(BOOL)flag
{
    UNUSED_PARAM(flag);
}

- (void)layout { }
- (void)layoutIfNeeded { }

- (void)setScale:(float)scale
{
    WKViewSetScale(viewRef, scale);
}

- (float)scale
{
    return WKViewGetScale(viewRef);
}

- (void)_setDrawsOwnDescendants:(BOOL)draw
{
    _drawsOwnDescendants = draw;
}

- (NSString *)description
{
    NSMutableString *description = [NSMutableString stringWithFormat:@"<%@: WAK: %p (WK: %p); ", [self class], self, viewRef];

    float scale = [self scale];
    if (scale != 1)
        [description appendFormat:@"scale = %g ", scale];

    CGPoint origin = WKViewGetOrigin(viewRef);
    if (origin.x || origin.y)
        [description appendFormat:@"origin = (%g %g) ", origin.x, origin.y];

    CGRect bounds = [self bounds];
    CGRect frame = [self frame];

    if (frame.origin.x != bounds.origin.x || frame.origin.y != bounds.origin.y || frame.size.width != bounds.size.width || frame.size.height != bounds.size.height)
        [description appendFormat:@"bounds = (%g %g; %g %g) ", bounds.origin.x, bounds.origin.y, bounds.size.width, bounds.size.height];

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

    return description;
}

- (void)_appendDescriptionToString:(NSMutableString *)info atLevel:(int)level
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    if ([info length] != 0)
        [info appendString:@"\n"];

    for (int i = 1; i <= level; i++)
        [info appendString:@"   | "];

    [info appendString:[self description]];

    for (WAKView *subview in [self subviews])
        [subview _appendDescriptionToString:info atLevel:level + 1];

    [pool release];
}

@end

#endif // PLATFORM(IOS)