1/*
2 * Copyright (C) 2005, 2008 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 *
8 * 1.  Redistributions of source code must retain the above copyright
9 *     notice, this list of conditions and the following disclaimer.
10 * 2.  Redistributions in binary form must reproduce the above copyright
11 *     notice, this list of conditions and the following disclaimer in the
12 *     documentation and/or other materials provided with the distribution.
13 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 *     its contributors may be used to endorse or promote products derived
15 *     from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#import "WebDynamicScrollBarsViewInternal.h"
30
31#import "WebDocument.h"
32#import "WebFrameInternal.h"
33#import "WebFrameView.h"
34#import "WebHTMLViewInternal.h"
35#import <WebCore/Frame.h>
36#import <WebCore/FrameView.h>
37#import <WebKitSystemInterface.h>
38
39using namespace WebCore;
40
41// FIXME: <rdar://problem/5898985> Mail expects a constant of this name to exist.
42const int WebCoreScrollbarAlwaysOn = ScrollbarAlwaysOn;
43
44@implementation WebDynamicScrollBarsView
45
46- (void)setAllowsHorizontalScrolling:(BOOL)flag
47{
48    if (hScrollModeLocked)
49        return;
50    if (flag && hScroll == ScrollbarAlwaysOff)
51        hScroll = ScrollbarAuto;
52    else if (!flag && hScroll != ScrollbarAlwaysOff)
53        hScroll = ScrollbarAlwaysOff;
54    [self updateScrollers];
55}
56
57@end
58
59@implementation WebDynamicScrollBarsView (WebInternal)
60
61- (void)setSuppressLayout:(BOOL)flag;
62{
63    suppressLayout = flag;
64}
65
66- (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint
67{
68    suppressScrollers = suppressed;
69
70    // This code was originally changes for a Leopard performance imporvement. We decided to
71    // ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>.
72#ifndef BUILDING_ON_TIGER
73    if (suppressed) {
74        [[self verticalScroller] setNeedsDisplay:NO];
75        [[self horizontalScroller] setNeedsDisplay:NO];
76    }
77
78    if (!suppressed && repaint)
79        [super reflectScrolledClipView:[self contentView]];
80#else
81    if (suppressed || repaint) {
82        [[self verticalScroller] setNeedsDisplay: !suppressed];
83        [[self horizontalScroller] setNeedsDisplay: !suppressed];
84    }
85#endif
86}
87
88static const unsigned cMaxUpdateScrollbarsPass = 2;
89
90- (void)updateScrollers
91{
92    NSView *documentView = [self documentView];
93
94    // If we came in here with the view already needing a layout, then go ahead and do that
95    // first.  (This will be the common case, e.g., when the page changes due to window resizing for example).
96    // This layout will not re-enter updateScrollers and does not count towards our max layout pass total.
97    if (!suppressLayout && !suppressScrollers && [documentView isKindOfClass:[WebHTMLView class]]) {
98        WebHTMLView* htmlView = (WebHTMLView*)documentView;
99        if ([htmlView _needsLayout]) {
100            inUpdateScrollers = YES;
101            [(id <WebDocumentView>)documentView layout];
102            inUpdateScrollers = NO;
103        }
104    }
105
106    BOOL hasHorizontalScroller = [self hasHorizontalScroller];
107    BOOL hasVerticalScroller = [self hasVerticalScroller];
108
109    BOOL newHasHorizontalScroller = hasHorizontalScroller;
110    BOOL newHasVerticalScroller = hasVerticalScroller;
111
112    if (!documentView) {
113        newHasHorizontalScroller = NO;
114        newHasVerticalScroller = NO;
115    }
116
117    if (hScroll != ScrollbarAuto)
118        newHasHorizontalScroller = (hScroll == ScrollbarAlwaysOn);
119    if (vScroll != ScrollbarAuto)
120        newHasVerticalScroller = (vScroll == ScrollbarAlwaysOn);
121
122    if (!documentView || suppressLayout || suppressScrollers || (hScroll != ScrollbarAuto && vScroll != ScrollbarAuto)) {
123        inUpdateScrollers = YES;
124        if (hasHorizontalScroller != newHasHorizontalScroller)
125            [self setHasHorizontalScroller:newHasHorizontalScroller];
126        if (hasVerticalScroller != newHasVerticalScroller)
127            [self setHasVerticalScroller:newHasVerticalScroller];
128        if (suppressScrollers) {
129            [[self verticalScroller] setNeedsDisplay:NO];
130            [[self horizontalScroller] setNeedsDisplay:NO];
131        }
132        inUpdateScrollers = NO;
133        return;
134    }
135
136    BOOL needsLayout = NO;
137
138    NSSize documentSize = [documentView frame].size;
139    NSSize visibleSize = [self documentVisibleRect].size;
140    NSSize frameSize = [self frame].size;
141
142    if (hScroll == ScrollbarAuto) {
143        newHasHorizontalScroller = documentSize.width > visibleSize.width;
144        if (newHasHorizontalScroller && !inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width)
145            newHasHorizontalScroller = NO;
146    }
147
148    if (vScroll == ScrollbarAuto) {
149        newHasVerticalScroller = documentSize.height > visibleSize.height;
150        if (newHasVerticalScroller && !inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width)
151            newHasVerticalScroller = NO;
152    }
153
154    // Unless in ScrollbarsAlwaysOn mode, if we ever turn one scrollbar off, always turn the other one off too.
155    // Never ever try to both gain/lose a scrollbar in the same pass.
156    if (!newHasHorizontalScroller && hasHorizontalScroller && vScroll != ScrollbarAlwaysOn)
157        newHasVerticalScroller = NO;
158    if (!newHasVerticalScroller && hasVerticalScroller && hScroll != ScrollbarAlwaysOn)
159        newHasHorizontalScroller = NO;
160
161    if (hasHorizontalScroller != newHasHorizontalScroller) {
162        inUpdateScrollers = YES;
163        [self setHasHorizontalScroller:newHasHorizontalScroller];
164        inUpdateScrollers = NO;
165        needsLayout = YES;
166    }
167
168    if (hasVerticalScroller != newHasVerticalScroller) {
169        inUpdateScrollers = YES;
170        [self setHasVerticalScroller:newHasVerticalScroller];
171        inUpdateScrollers = NO;
172        needsLayout = YES;
173    }
174
175    if (needsLayout && inUpdateScrollersLayoutPass < cMaxUpdateScrollbarsPass &&
176        [documentView conformsToProtocol:@protocol(WebDocumentView)]) {
177        inUpdateScrollersLayoutPass++;
178        [(id <WebDocumentView>)documentView setNeedsLayout:YES];
179        [(id <WebDocumentView>)documentView layout];
180        NSSize newDocumentSize = [documentView frame].size;
181        if (NSEqualSizes(documentSize, newDocumentSize)) {
182            // The layout with the new scroll state had no impact on
183            // the document's overall size, so updateScrollers didn't get called.
184            // Recur manually.
185            [self updateScrollers];
186        }
187        inUpdateScrollersLayoutPass--;
188    }
189}
190
191// Make the horizontal and vertical scroll bars come and go as needed.
192- (void)reflectScrolledClipView:(NSClipView *)clipView
193{
194    if (clipView == [self contentView]) {
195        // FIXME: This hack here prevents infinite recursion that takes place when we
196        // gyrate between having a vertical scroller and not having one. A reproducible
197        // case is clicking on the "the Policy Routing text" link at
198        // http://www.linuxpowered.com/archive/howto/Net-HOWTO-8.html.
199        // The underlying cause is some problem in the NSText machinery, but I was not
200        // able to pin it down.
201        NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
202        if (!inUpdateScrollers && (!currentContext || [currentContext isDrawingToScreen]))
203            [self updateScrollers];
204    }
205
206    // This code was originally changed for a Leopard performance imporvement. We decided to
207    // ifdef it to fix correctness issues on Tiger documented in <rdar://problem/5441823>.
208#ifndef BUILDING_ON_TIGER
209    // Update the scrollers if they're not being suppressed.
210    if (!suppressScrollers)
211        [super reflectScrolledClipView:clipView];
212#else
213    [super reflectScrolledClipView:clipView];
214
215    // Validate the scrollers if they're being suppressed.
216    if (suppressScrollers) {
217        [[self verticalScroller] setNeedsDisplay: NO];
218        [[self horizontalScroller] setNeedsDisplay: NO];
219    }
220#endif
221
222#if USE(ACCELERATED_COMPOSITING) && defined(BUILDING_ON_LEOPARD)
223    NSView *documentView = [self documentView];
224    if ([documentView isKindOfClass:[WebHTMLView class]]) {
225        WebHTMLView *htmlView = (WebHTMLView *)documentView;
226        if ([htmlView _isUsingAcceleratedCompositing])
227            [htmlView _updateLayerHostingViewPosition];
228    }
229#endif
230}
231
232- (BOOL)allowsHorizontalScrolling
233{
234    return hScroll != ScrollbarAlwaysOff;
235}
236
237- (BOOL)allowsVerticalScrolling
238{
239    return vScroll != ScrollbarAlwaysOff;
240}
241
242- (void)scrollingModes:(WebCore::ScrollbarMode*)hMode vertical:(WebCore::ScrollbarMode*)vMode
243{
244    *hMode = static_cast<ScrollbarMode>(hScroll);
245    *vMode = static_cast<ScrollbarMode>(vScroll);
246}
247
248- (ScrollbarMode)horizontalScrollingMode
249{
250    return static_cast<ScrollbarMode>(hScroll);
251}
252
253- (ScrollbarMode)verticalScrollingMode
254{
255    return static_cast<ScrollbarMode>(vScroll);
256}
257
258- (void)setHorizontalScrollingMode:(ScrollbarMode)horizontalMode andLock:(BOOL)lock
259{
260    [self setScrollingModes:horizontalMode vertical:[self verticalScrollingMode] andLock:lock];
261}
262
263- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode andLock:(BOOL)lock
264{
265    [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:lock];
266}
267
268// Mail uses this method, so we cannot remove it.
269- (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode
270{
271    [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:NO];
272}
273
274- (void)setScrollingModes:(ScrollbarMode)horizontalMode vertical:(ScrollbarMode)verticalMode andLock:(BOOL)lock
275{
276    BOOL update = NO;
277    if (verticalMode != vScroll && !vScrollModeLocked) {
278        vScroll = verticalMode;
279        update = YES;
280    }
281
282    if (horizontalMode != hScroll && !hScrollModeLocked) {
283        hScroll = horizontalMode;
284        update = YES;
285    }
286
287    if (lock)
288        [self setScrollingModesLocked:YES];
289
290    if (update)
291        [self updateScrollers];
292}
293
294- (void)setHorizontalScrollingModeLocked:(BOOL)locked
295{
296    hScrollModeLocked = locked;
297}
298
299- (void)setVerticalScrollingModeLocked:(BOOL)locked
300{
301    vScrollModeLocked = locked;
302}
303
304- (void)setScrollingModesLocked:(BOOL)locked
305{
306    hScrollModeLocked = vScrollModeLocked = locked;
307}
308
309- (BOOL)horizontalScrollingModeLocked
310{
311    return hScrollModeLocked;
312}
313
314- (BOOL)verticalScrollingModeLocked
315{
316    return vScrollModeLocked;
317}
318
319- (BOOL)autoforwardsScrollWheelEvents
320{
321    return YES;
322}
323
324- (void)scrollWheel:(NSEvent *)event
325{
326    float deltaX;
327    float deltaY;
328    BOOL isContinuous;
329    WKGetWheelEventDeltas(event, &deltaX, &deltaY, &isContinuous);
330
331    BOOL isLatchingEvent = WKIsLatchingWheelEvent(event);
332
333    if (fabsf(deltaY) > fabsf(deltaX)) {
334        if (![self allowsVerticalScrolling]) {
335            [[self nextResponder] scrollWheel:event];
336            return;
337        }
338
339        if (isLatchingEvent && !verticallyPinnedByPreviousWheelEvent) {
340            double verticalPosition = [[self verticalScroller] doubleValue];
341            if ((deltaY >= 0.0 && verticalPosition == 0.0) || (deltaY <= 0.0 && verticalPosition == 1.0))
342                return;
343        }
344    } else {
345        if (![self allowsHorizontalScrolling]) {
346            [[self nextResponder] scrollWheel:event];
347            return;
348        }
349
350        if (isLatchingEvent && !horizontallyPinnedByPreviousWheelEvent) {
351            double horizontalPosition = [[self horizontalScroller] doubleValue];
352            if ((deltaX >= 0.0 && horizontalPosition == 0.0) || (deltaX <= 0.0 && horizontalPosition == 1.0))
353                return;
354        }
355    }
356
357    // Calling super can release the last reference. <rdar://problem/7400263>
358    // Hold a reference so the code following the super call will not crash.
359    [self retain];
360
361    [super scrollWheel:event];
362
363    if (!isLatchingEvent) {
364        double verticalPosition = [[self verticalScroller] doubleValue];
365        double horizontalPosition = [[self horizontalScroller] doubleValue];
366
367        verticallyPinnedByPreviousWheelEvent = (verticalPosition == 0.0 || verticalPosition == 1.0);
368        horizontallyPinnedByPreviousWheelEvent = (horizontalPosition == 0.0 || horizontalPosition == 1.0);
369    }
370
371    [self release];
372}
373
374- (BOOL)accessibilityIsIgnored
375{
376    id docView = [self documentView];
377    if ([docView isKindOfClass:[WebFrameView class]] && ![(WebFrameView *)docView allowsScrolling])
378        return YES;
379
380    return [super accessibilityIsIgnored];
381}
382
383@end
384