blob: 3c28e3c7666ae149519389df42966b126fe3b035 [file] [log] [blame]
/*
* Copyright (C) 2005, 2008 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.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE 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 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 "WebDynamicScrollBarsViewInternal.h"
#import "WebDocument.h"
#import "WebFrameInternal.h"
#import "WebFrameView.h"
#import "WebHTMLViewInternal.h"
#import <WebCore/Frame.h>
#import <WebCore/FrameView.h>
#import <WebKitSystemInterface.h>
using namespace WebCore;
// FIXME: <rdar://problem/5898985> Mail expects a constant of this name to exist.
const int WebCoreScrollbarAlwaysOn = ScrollbarAlwaysOn;
@implementation WebDynamicScrollBarsView
- (void)setAllowsHorizontalScrolling:(BOOL)flag
{
if (hScrollModeLocked)
return;
if (flag && hScroll == ScrollbarAlwaysOff)
hScroll = ScrollbarAuto;
else if (!flag && hScroll != ScrollbarAlwaysOff)
hScroll = ScrollbarAlwaysOff;
[self updateScrollers];
}
@end
@implementation WebDynamicScrollBarsView (WebInternal)
- (void)setSuppressLayout:(BOOL)flag;
{
suppressLayout = flag;
}
- (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint
{
suppressScrollers = suppressed;
// This code was originally changes for a Leopard performance imporvement. We decided to
// ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>.
#ifndef BUILDING_ON_TIGER
if (suppressed) {
[[self verticalScroller] setNeedsDisplay:NO];
[[self horizontalScroller] setNeedsDisplay:NO];
}
if (!suppressed && repaint)
[super reflectScrolledClipView:[self contentView]];
#else
if (suppressed || repaint) {
[[self verticalScroller] setNeedsDisplay: !suppressed];
[[self horizontalScroller] setNeedsDisplay: !suppressed];
}
#endif
}
static const unsigned cMaxUpdateScrollbarsPass = 2;
- (void)updateScrollers
{
NSView *documentView = [self documentView];
// If we came in here with the view already needing a layout, then go ahead and do that
// first. (This will be the common case, e.g., when the page changes due to window resizing for example).
// This layout will not re-enter updateScrollers and does not count towards our max layout pass total.
if (!suppressLayout && !suppressScrollers && [documentView isKindOfClass:[WebHTMLView class]]) {
WebHTMLView* htmlView = (WebHTMLView*)documentView;
if ([htmlView _needsLayout]) {
inUpdateScrollers = YES;
[(id <WebDocumentView>)documentView layout];
inUpdateScrollers = NO;
}
}
BOOL hasHorizontalScroller = [self hasHorizontalScroller];
BOOL hasVerticalScroller = [self hasVerticalScroller];
BOOL newHasHorizontalScroller = hasHorizontalScroller;
BOOL newHasVerticalScroller = hasVerticalScroller;
if (!documentView) {
newHasHorizontalScroller = NO;
newHasVerticalScroller = NO;
}
if (hScroll != ScrollbarAuto)
newHasHorizontalScroller = (hScroll == ScrollbarAlwaysOn);
if (vScroll != ScrollbarAuto)
newHasVerticalScroller = (vScroll == ScrollbarAlwaysOn);
if (!documentView || suppressLayout || suppressScrollers || (hScroll != ScrollbarAuto && vScroll != ScrollbarAuto)) {
inUpdateScrollers = YES;
if (hasHorizontalScroller != newHasHorizontalScroller)
[self setHasHorizontalScroller:newHasHorizontalScroller];
if (hasVerticalScroller != newHasVerticalScroller)
[self setHasVerticalScroller:newHasVerticalScroller];
if (suppressScrollers) {
[[self verticalScroller] setNeedsDisplay:NO];
[[self horizontalScroller] setNeedsDisplay:NO];
}
inUpdateScrollers = NO;
return;
}
BOOL needsLayout = NO;
NSSize documentSize = [documentView frame].size;
NSSize visibleSize = [self documentVisibleRect].size;
NSSize frameSize = [self frame].size;
if (hScroll == ScrollbarAuto) {
newHasHorizontalScroller = documentSize.width > visibleSize.width;
if (newHasHorizontalScroller && !inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width)
newHasHorizontalScroller = NO;
}
if (vScroll == ScrollbarAuto) {
newHasVerticalScroller = documentSize.height > visibleSize.height;
if (newHasVerticalScroller && !inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width)
newHasVerticalScroller = NO;
}
// Unless in ScrollbarsAlwaysOn mode, if we ever turn one scrollbar off, always turn the other one off too.
// Never ever try to both gain/lose a scrollbar in the same pass.
if (!newHasHorizontalScroller && hasHorizontalScroller && vScroll != ScrollbarAlwaysOn)
newHasVerticalScroller = NO;
if (!newHasVerticalScroller && hasVerticalScroller && hScroll != ScrollbarAlwaysOn)
newHasHorizontalScroller = NO;
if (hasHorizontalScroller != newHasHorizontalScroller) {
inUpdateScrollers = YES;
[self setHasHorizontalScroller:newHasHorizontalScroller];
inUpdateScrollers = NO;
needsLayout = YES;
}
if (hasVerticalScroller != newHasVerticalScroller) {
inUpdateScrollers = YES;
[self setHasVerticalScroller:newHasVerticalScroller];
inUpdateScrollers = NO;
needsLayout = YES;
}
if (needsLayout && inUpdateScrollersLayoutPass < cMaxUpdateScrollbarsPass &&
[documentView conformsToProtocol:@protocol(WebDocumentView)]) {
inUpdateScrollersLayoutPass++;
[(id <WebDocumentView>)documentView setNeedsLayout:YES];
[(id <WebDocumentView>)documentView layout];
NSSize newDocumentSize = [documentView frame].size;
if (NSEqualSizes(documentSize, newDocumentSize)) {
// The layout with the new scroll state had no impact on
// the document's overall size, so updateScrollers didn't get called.
// Recur manually.
[self updateScrollers];
}
inUpdateScrollersLayoutPass--;
}
}
// Make the horizontal and vertical scroll bars come and go as needed.
- (void)reflectScrolledClipView:(NSClipView *)clipView
{
if (clipView == [self contentView]) {
// FIXME: This hack here prevents infinite recursion that takes place when we
// gyrate between having a vertical scroller and not having one. A reproducible
// case is clicking on the "the Policy Routing text" link at
// http://www.linuxpowered.com/archive/howto/Net-HOWTO-8.html.
// The underlying cause is some problem in the NSText machinery, but I was not
// able to pin it down.
NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
if (!inUpdateScrollers && (!currentContext || [currentContext isDrawingToScreen]))
[self updateScrollers];
}
// This code was originally changed for a Leopard performance imporvement. We decided to
// ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>.
#ifndef BUILDING_ON_TIGER
// Update the scrollers if they're not being suppressed.
if (!suppressScrollers)
[super reflectScrolledClipView:clipView];
#else
[super reflectScrolledClipView:clipView];
// Validate the scrollers if they're being suppressed.
if (suppressScrollers) {
[[self verticalScroller] setNeedsDisplay: NO];
[[self horizontalScroller] setNeedsDisplay: NO];
}
#endif
#if USE(ACCELERATED_COMPOSITING) && defined(BUILDING_ON_LEOPARD)
NSView *documentView = [self documentView];
if ([documentView isKindOfClass:[WebHTMLView class]]) {
WebHTMLView *htmlView = (WebHTMLView *)documentView;
if ([htmlView _isUsingAcceleratedCompositing])
[htmlView _updateLayerHostingViewPosition];
}
#endif
}
- (BOOL)allowsHorizontalScrolling
{
return hScroll != ScrollbarAlwaysOff;
}
- (BOOL)allowsVerticalScrolling
{
return vScroll != ScrollbarAlwaysOff;
}
- (void)scrollingModes:(WebCore::ScrollbarMode*)hMode vertical:(WebCore::ScrollbarMode*)vMode
{
*hMode = static_cast<ScrollbarMode>(hScroll);
*vMode = static_cast<ScrollbarMode>(vScroll);
}
- (ScrollbarMode)horizontalScrollingMode
{
return static_cast<ScrollbarMode>(hScroll);
}
- (ScrollbarMode)verticalScrollingMode
{
return static_cast<ScrollbarMode>(vScroll);
}
- (void)setHorizontalScrollingMode:(ScrollbarMode)horizontalMode andLock:(BOOL)lock
{
[self setScrollingModes:horizontalMode vertical:[self verticalScrollingMode] andLock:lock];
}
- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode andLock:(BOOL)lock
{
[self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:lock];
}
// Mail uses this method, so we cannot remove it.
- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode
{
[self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:NO];
}
- (void)setScrollingModes:(ScrollbarMode)horizontalMode vertical:(ScrollbarMode)verticalMode andLock:(BOOL)lock
{
BOOL update = NO;
if (verticalMode != vScroll && !vScrollModeLocked) {
vScroll = verticalMode;
update = YES;
}
if (horizontalMode != hScroll && !hScrollModeLocked) {
hScroll = horizontalMode;
update = YES;
}
if (lock)
[self setScrollingModesLocked:YES];
if (update)
[self updateScrollers];
}
- (void)setHorizontalScrollingModeLocked:(BOOL)locked
{
hScrollModeLocked = locked;
}
- (void)setVerticalScrollingModeLocked:(BOOL)locked
{
vScrollModeLocked = locked;
}
- (void)setScrollingModesLocked:(BOOL)locked
{
hScrollModeLocked = vScrollModeLocked = locked;
}
- (BOOL)horizontalScrollingModeLocked
{
return hScrollModeLocked;
}
- (BOOL)verticalScrollingModeLocked
{
return vScrollModeLocked;
}
- (BOOL)autoforwardsScrollWheelEvents
{
return YES;
}
- (void)scrollWheel:(NSEvent *)event
{
float deltaX;
float deltaY;
BOOL isContinuous;
WKGetWheelEventDeltas(event, &deltaX, &deltaY, &isContinuous);
BOOL isLatchingEvent = WKIsLatchingWheelEvent(event);
if (fabsf(deltaY) > fabsf(deltaX)) {
if (![self allowsVerticalScrolling]) {
[[self nextResponder] scrollWheel:event];
return;
}
if (isLatchingEvent && !verticallyPinnedByPreviousWheelEvent) {
double verticalPosition = [[self verticalScroller] doubleValue];
if ((deltaY >= 0.0 && verticalPosition == 0.0) || (deltaY <= 0.0 && verticalPosition == 1.0))
return;
}
} else {
if (![self allowsHorizontalScrolling]) {
[[self nextResponder] scrollWheel:event];
return;
}
if (isLatchingEvent && !horizontallyPinnedByPreviousWheelEvent) {
double horizontalPosition = [[self horizontalScroller] doubleValue];
if ((deltaX >= 0.0 && horizontalPosition == 0.0) || (deltaX <= 0.0 && horizontalPosition == 1.0))
return;
}
}
[super scrollWheel:event];
if (!isLatchingEvent) {
double verticalPosition = [[self verticalScroller] doubleValue];
double horizontalPosition = [[self horizontalScroller] doubleValue];
verticallyPinnedByPreviousWheelEvent = (verticalPosition == 0.0 || verticalPosition == 1.0);
horizontallyPinnedByPreviousWheelEvent = (horizontalPosition == 0.0 || horizontalPosition == 1.0);
}
}
- (BOOL)accessibilityIsIgnored
{
id docView = [self documentView];
if ([docView isKindOfClass:[WebFrameView class]] && ![(WebFrameView *)docView allowsScrolling])
return YES;
return [super accessibilityIsIgnored];
}
@end