initial import
[vuplus_webkit] / Source / WebKit / mac / WebView / WebDynamicScrollBarsView.mm
1 /*
2  * Copyright (C) 2005, 2008, 2010 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 "WebDynamicScrollBarsViewInternal.h"
27
28 #import "WebDocument.h"
29 #import "WebFrameInternal.h"
30 #import "WebFrameView.h"
31 #import "WebHTMLViewInternal.h"
32 #import <WebCore/Frame.h>
33 #import <WebCore/FrameView.h>
34 #import <WebKitSystemInterface.h>
35
36 using namespace WebCore;
37
38 // FIXME: <rdar://problem/5898985> Mail expects a constant of this name to exist.
39 const int WebCoreScrollbarAlwaysOn = ScrollbarAlwaysOn;
40
41 #ifndef __OBJC2__
42 // In <rdar://problem/7814899> we saw crashes because WebDynamicScrollBarsView increased in size, breaking ABI compatiblity.
43 COMPILE_ASSERT(sizeof(WebDynamicScrollBarsView) == 0x8c, WebDynamicScrollBarsView_is_expected_size);
44 #endif
45
46 struct WebDynamicScrollBarsViewPrivate {
47     unsigned inUpdateScrollersLayoutPass;
48
49     WebCore::ScrollbarMode hScroll;
50     WebCore::ScrollbarMode vScroll;
51
52     bool hScrollModeLocked;
53     bool vScrollModeLocked;
54     bool suppressLayout;
55     bool suppressScrollers;
56     bool inUpdateScrollers;
57     bool verticallyPinnedByPreviousWheelEvent;
58     bool horizontallyPinnedByPreviousWheelEvent;
59
60     bool allowsScrollersToOverlapContent;
61     bool alwaysHideHorizontalScroller;
62     bool alwaysHideVerticalScroller;
63     bool horizontalScrollingAllowedButScrollerHidden;
64     bool verticalScrollingAllowedButScrollerHidden;
65
66     // scrollOrigin is set for various combinations of writing mode and direction.
67     // See the comment next to the corresponding member in ScrollView.h.
68     NSPoint scrollOrigin;
69
70     // Flag to indicate that the scrollbar thumb's initial position needs to
71     // be manually set.
72     bool scrollOriginChanged;
73     NSPoint scrollPositionExcludingOrigin;
74
75     bool inProgrammaticScroll;
76 };
77
78 @implementation WebDynamicScrollBarsView
79
80 - (id)initWithFrame:(NSRect)frame
81 {
82     if (!(self = [super initWithFrame:frame]))
83         return nil;
84
85     _private = new WebDynamicScrollBarsViewPrivate;
86     memset(_private, 0, sizeof(WebDynamicScrollBarsViewPrivate));
87     return self;
88 }
89
90 - (id)initWithCoder:(NSCoder *)aDecoder
91 {
92     if (!(self = [super initWithCoder:aDecoder]))
93         return nil;
94
95     _private = new WebDynamicScrollBarsViewPrivate;
96     memset(_private, 0, sizeof(WebDynamicScrollBarsViewPrivate));
97     return self;
98 }
99
100 - (void)dealloc
101 {
102     delete _private;
103     [super dealloc];
104 }
105
106 - (void)finalize
107 {
108     delete _private;
109     [super finalize];
110 }
111
112 - (void)setAllowsHorizontalScrolling:(BOOL)flag
113 {
114     if (_private->hScrollModeLocked)
115         return;
116     if (flag && _private->hScroll == ScrollbarAlwaysOff)
117         _private->hScroll = ScrollbarAuto;
118     else if (!flag && _private->hScroll != ScrollbarAlwaysOff)
119         _private->hScroll = ScrollbarAlwaysOff;
120     [self updateScrollers];
121 }
122
123 - (void)setAllowsScrollersToOverlapContent:(BOOL)flag
124 {
125     if (_private->allowsScrollersToOverlapContent == flag)
126         return;
127
128     _private->allowsScrollersToOverlapContent = flag;
129
130     [[self contentView] setFrame:[self contentViewFrame]];
131     [[self documentView] setNeedsLayout:YES];
132     [[self documentView] layout];
133 }
134
135 - (void)setAlwaysHideHorizontalScroller:(BOOL)shouldBeHidden
136 {
137     if (_private->alwaysHideHorizontalScroller == shouldBeHidden)
138         return;
139
140     _private->alwaysHideHorizontalScroller = shouldBeHidden;
141     [self updateScrollers];
142 }
143
144 - (void)setAlwaysHideVerticalScroller:(BOOL)shouldBeHidden
145 {
146     if (_private->alwaysHideVerticalScroller == shouldBeHidden)
147         return;
148
149     _private->alwaysHideVerticalScroller = shouldBeHidden;
150     [self updateScrollers];
151 }
152
153 - (BOOL)horizontalScrollingAllowed
154 {
155     return _private->horizontalScrollingAllowedButScrollerHidden || [self hasHorizontalScroller];
156 }
157
158 - (BOOL)verticalScrollingAllowed
159 {
160     return _private->verticalScrollingAllowedButScrollerHidden || [self hasVerticalScroller];
161 }
162
163 @end
164
165 @implementation WebDynamicScrollBarsView (WebInternal)
166
167 - (NSRect)contentViewFrame
168 {
169     NSRect frame = [[self contentView] frame];
170
171     if ([self hasHorizontalScroller])
172         frame.size.height = (_private->allowsScrollersToOverlapContent ? NSMaxY([[self horizontalScroller] frame]) : NSMinY([[self horizontalScroller] frame]));
173     if ([self hasVerticalScroller])
174         frame.size.width = (_private->allowsScrollersToOverlapContent ? NSMaxX([[self verticalScroller] frame]) : NSMinX([[self verticalScroller] frame]));
175     return frame;
176 }
177
178 - (void)tile
179 {
180     [super tile];
181
182     // [super tile] sets the contentView size so that it does not overlap with the scrollers,
183     // we want to re-set the contentView to overlap scrollers before displaying.
184     if (_private->allowsScrollersToOverlapContent)
185         [[self contentView] setFrame:[self contentViewFrame]];
186 }
187
188 - (void)setSuppressLayout:(BOOL)flag
189 {
190     _private->suppressLayout = flag;
191 }
192
193 - (void)setScrollBarsSuppressed:(BOOL)suppressed repaintOnUnsuppress:(BOOL)repaint
194 {
195     _private->suppressScrollers = suppressed;
196
197     if (suppressed) {
198         [[self verticalScroller] setNeedsDisplay:NO];
199         [[self horizontalScroller] setNeedsDisplay:NO];
200     }
201
202     if (!suppressed && repaint)
203         [super reflectScrolledClipView:[self contentView]];
204 }
205
206 - (void)adjustForScrollOriginChange
207 {
208     if (!_private->scrollOriginChanged)
209         return;
210
211     _private->scrollOriginChanged = false;
212
213     NSView *documentView = [self documentView];
214     NSRect documentRect = [documentView bounds];
215
216     // The call to [NSView scrollPoint:] fires off notification the handler for which needs to know that
217     // we're setting the initial scroll position so it doesn't interpret this as a user action and
218     // fire off a JS event.
219     _private->inProgrammaticScroll = true;
220     [documentView scrollPoint:NSMakePoint(_private->scrollPositionExcludingOrigin.x + documentRect.origin.x, _private->scrollPositionExcludingOrigin.y + documentRect.origin.y)];
221     _private->inProgrammaticScroll = false;
222 }
223
224 static const unsigned cMaxUpdateScrollbarsPass = 2;
225
226 - (void)updateScrollers
227 {
228     NSView *documentView = [self documentView];
229
230     // If we came in here with the view already needing a layout, then go ahead and do that
231     // first.  (This will be the common case, e.g., when the page changes due to window resizing for example).
232     // This layout will not re-enter updateScrollers and does not count towards our max layout pass total.
233     if (!_private->suppressLayout && !_private->suppressScrollers && [documentView isKindOfClass:[WebHTMLView class]]) {
234         WebHTMLView* htmlView = (WebHTMLView*)documentView;
235         if ([htmlView _needsLayout]) {
236             _private->inUpdateScrollers = YES;
237             [(id <WebDocumentView>)documentView layout];
238             _private->inUpdateScrollers = NO;
239         }
240     }
241
242     BOOL hasHorizontalScroller = [self hasHorizontalScroller];
243     BOOL hasVerticalScroller = [self hasVerticalScroller];
244
245     BOOL newHasHorizontalScroller = hasHorizontalScroller;
246     BOOL newHasVerticalScroller = hasVerticalScroller;
247
248     if (!documentView) {
249         newHasHorizontalScroller = NO;
250         newHasVerticalScroller = NO;
251     }
252
253     if (_private->hScroll != ScrollbarAuto)
254         newHasHorizontalScroller = (_private->hScroll == ScrollbarAlwaysOn);
255     if (_private->vScroll != ScrollbarAuto)
256         newHasVerticalScroller = (_private->vScroll == ScrollbarAlwaysOn);
257
258     if (!documentView || _private->suppressLayout || _private->suppressScrollers || (_private->hScroll != ScrollbarAuto && _private->vScroll != ScrollbarAuto)) {
259         _private->horizontalScrollingAllowedButScrollerHidden = newHasHorizontalScroller && _private->alwaysHideHorizontalScroller;
260         if (_private->horizontalScrollingAllowedButScrollerHidden)
261             newHasHorizontalScroller = NO;
262
263         _private->verticalScrollingAllowedButScrollerHidden = newHasVerticalScroller && _private->alwaysHideVerticalScroller;
264         if (_private->verticalScrollingAllowedButScrollerHidden)
265             newHasVerticalScroller = NO;
266
267         _private->inUpdateScrollers = YES;
268         if (hasHorizontalScroller != newHasHorizontalScroller)
269             [self setHasHorizontalScroller:newHasHorizontalScroller];
270         if (hasVerticalScroller != newHasVerticalScroller)
271             [self setHasVerticalScroller:newHasVerticalScroller];
272         if (_private->suppressScrollers) {
273             [[self verticalScroller] setNeedsDisplay:NO];
274             [[self horizontalScroller] setNeedsDisplay:NO];
275         }
276         _private->inUpdateScrollers = NO;
277         return;
278     }
279
280     BOOL needsLayout = NO;
281
282     NSSize documentSize = [documentView frame].size;
283     NSSize visibleSize = [self documentVisibleRect].size;
284     NSSize frameSize = [self frame].size;
285     
286     // When in HiDPI with a scale factor > 1, the visibleSize and frameSize may be non-integral values,
287     // while the documentSize (set by WebCore) will be integral.  Round up the non-integral sizes so that
288     // the mismatch won't cause unwanted scrollbars to appear.  This can result in slightly cut off content,
289     // but it will always be less than one pixel, which should not be noticeable.
290     visibleSize.width = ceilf(visibleSize.width);
291     visibleSize.height = ceilf(visibleSize.height);
292     frameSize.width = ceilf(frameSize.width);
293     frameSize.height = ceilf(frameSize.height);
294
295     if (_private->hScroll == ScrollbarAuto) {
296         newHasHorizontalScroller = documentSize.width > visibleSize.width;
297         if (newHasHorizontalScroller && !_private->inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width)
298             newHasHorizontalScroller = NO;
299     }
300
301     if (_private->vScroll == ScrollbarAuto) {
302         newHasVerticalScroller = documentSize.height > visibleSize.height;
303         if (newHasVerticalScroller && !_private->inUpdateScrollersLayoutPass && documentSize.height <= frameSize.height && documentSize.width <= frameSize.width)
304             newHasVerticalScroller = NO;
305     }
306
307     // Unless in ScrollbarsAlwaysOn mode, if we ever turn one scrollbar off, always turn the other one off too.
308     // Never ever try to both gain/lose a scrollbar in the same pass.
309     if (!newHasHorizontalScroller && hasHorizontalScroller && _private->vScroll != ScrollbarAlwaysOn)
310         newHasVerticalScroller = NO;
311     if (!newHasVerticalScroller && hasVerticalScroller && _private->hScroll != ScrollbarAlwaysOn)
312         newHasHorizontalScroller = NO;
313
314     _private->horizontalScrollingAllowedButScrollerHidden = newHasHorizontalScroller && _private->alwaysHideHorizontalScroller;
315     if (_private->horizontalScrollingAllowedButScrollerHidden)
316         newHasHorizontalScroller = NO;
317
318     _private->verticalScrollingAllowedButScrollerHidden = newHasVerticalScroller && _private->alwaysHideVerticalScroller;
319     if (_private->verticalScrollingAllowedButScrollerHidden)
320         newHasVerticalScroller = NO;
321
322     if (hasHorizontalScroller != newHasHorizontalScroller) {
323         _private->inUpdateScrollers = YES;
324         [self setHasHorizontalScroller:newHasHorizontalScroller];
325         _private->inUpdateScrollers = NO;
326         needsLayout = YES;
327         NSView *documentView = [self documentView];
328         NSRect documentRect = [documentView bounds];
329         if (documentRect.origin.y < 0 && !newHasHorizontalScroller)
330             [documentView setBoundsOrigin:NSMakePoint(documentRect.origin.x, documentRect.origin.y + 15)];
331     }
332
333     if (hasVerticalScroller != newHasVerticalScroller) {
334         _private->inUpdateScrollers = YES;
335         [self setHasVerticalScroller:newHasVerticalScroller];
336         _private->inUpdateScrollers = NO;
337         needsLayout = YES;
338         NSView *documentView = [self documentView];
339         NSRect documentRect = [documentView bounds];
340         if (documentRect.origin.x < 0 && !newHasVerticalScroller)
341             [documentView setBoundsOrigin:NSMakePoint(documentRect.origin.x + 15, documentRect.origin.y)];
342     }
343
344     if (needsLayout && _private->inUpdateScrollersLayoutPass < cMaxUpdateScrollbarsPass &&
345         [documentView conformsToProtocol:@protocol(WebDocumentView)]) {
346         _private->inUpdateScrollersLayoutPass++;
347         [(id <WebDocumentView>)documentView setNeedsLayout:YES];
348         [(id <WebDocumentView>)documentView layout];
349         NSSize newDocumentSize = [documentView frame].size;
350         if (NSEqualSizes(documentSize, newDocumentSize)) {
351             // The layout with the new scroll state had no impact on
352             // the document's overall size, so updateScrollers didn't get called.
353             // Recur manually.
354             [self updateScrollers];
355         }
356         _private->inUpdateScrollersLayoutPass--;
357     }
358 }
359
360 // Make the horizontal and vertical scroll bars come and go as needed.
361 - (void)reflectScrolledClipView:(NSClipView *)clipView
362 {
363     if (clipView == [self contentView]) {
364         // Prevent appearance of trails because of overlapping views
365         if (_private->allowsScrollersToOverlapContent)
366             [self setDrawsBackground:NO];
367
368         // FIXME: This hack here prevents infinite recursion that takes place when we
369         // gyrate between having a vertical scroller and not having one. A reproducible
370         // case is clicking on the "the Policy Routing text" link at
371         // http://www.linuxpowered.com/archive/howto/Net-HOWTO-8.html.
372         // The underlying cause is some problem in the NSText machinery, but I was not
373         // able to pin it down.
374         NSGraphicsContext *currentContext = [NSGraphicsContext currentContext];
375         if (!_private->inUpdateScrollers && (!currentContext || [currentContext isDrawingToScreen]))
376             [self updateScrollers];
377     }
378
379     // Update the scrollers if they're not being suppressed.
380     if (!_private->suppressScrollers)
381         [super reflectScrolledClipView:clipView];
382
383     // The call to [NSView reflectScrolledClipView] sets the scrollbar thumb
384     // position to 0 (the left) when the view is initially displayed.
385     // This call updates the initial position correctly.
386     [self adjustForScrollOriginChange];
387
388 #if USE(ACCELERATED_COMPOSITING) && defined(BUILDING_ON_LEOPARD)
389     NSView *documentView = [self documentView];
390     if ([documentView isKindOfClass:[WebHTMLView class]]) {
391         WebHTMLView *htmlView = (WebHTMLView *)documentView;
392         if ([htmlView _isUsingAcceleratedCompositing])
393             [htmlView _updateLayerHostingViewPosition];
394     }
395 #endif
396 }
397
398 - (BOOL)allowsHorizontalScrolling
399 {
400     return _private->hScroll != ScrollbarAlwaysOff;
401 }
402
403 - (BOOL)allowsVerticalScrolling
404 {
405     return _private->vScroll != ScrollbarAlwaysOff;
406 }
407
408 - (void)scrollingModes:(WebCore::ScrollbarMode*)hMode vertical:(WebCore::ScrollbarMode*)vMode
409 {
410     *hMode = _private->hScroll;
411     *vMode = _private->vScroll;
412 }
413
414 - (ScrollbarMode)horizontalScrollingMode
415 {
416     return _private->hScroll;
417 }
418
419 - (ScrollbarMode)verticalScrollingMode
420 {
421     return _private->vScroll;
422 }
423
424 - (void)setHorizontalScrollingMode:(ScrollbarMode)horizontalMode andLock:(BOOL)lock
425 {
426     [self setScrollingModes:horizontalMode vertical:[self verticalScrollingMode] andLock:lock];
427 }
428
429 - (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode andLock:(BOOL)lock
430 {
431     [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:lock];
432 }
433
434 // Mail uses this method, so we cannot remove it. 
435 - (void)setVerticalScrollingMode:(ScrollbarMode)verticalMode 
436
437     [self setScrollingModes:[self horizontalScrollingMode] vertical:verticalMode andLock:NO]; 
438
439
440 - (void)setScrollingModes:(ScrollbarMode)horizontalMode vertical:(ScrollbarMode)verticalMode andLock:(BOOL)lock
441 {
442     BOOL update = NO;
443     if (verticalMode != _private->vScroll && !_private->vScrollModeLocked) {
444         _private->vScroll = verticalMode;
445         update = YES;
446     }
447
448     if (horizontalMode != _private->hScroll && !_private->hScrollModeLocked) {
449         _private->hScroll = horizontalMode;
450         update = YES;
451     }
452
453     if (lock)
454         [self setScrollingModesLocked:YES];
455
456     if (update)
457         [self updateScrollers];
458 }
459
460 - (void)setHorizontalScrollingModeLocked:(BOOL)locked
461 {
462     _private->hScrollModeLocked = locked;
463 }
464
465 - (void)setVerticalScrollingModeLocked:(BOOL)locked
466 {
467     _private->vScrollModeLocked = locked;
468 }
469
470 - (void)setScrollingModesLocked:(BOOL)locked
471 {
472     _private->hScrollModeLocked = _private->vScrollModeLocked = locked;
473 }
474
475 - (BOOL)horizontalScrollingModeLocked
476 {
477     return _private->hScrollModeLocked;
478 }
479
480 - (BOOL)verticalScrollingModeLocked
481 {
482     return _private->vScrollModeLocked;
483 }
484
485 - (BOOL)autoforwardsScrollWheelEvents
486 {
487     return YES;
488 }
489
490 - (void)scrollWheel:(NSEvent *)event
491 {
492     float deltaX;
493     float deltaY;
494     BOOL isContinuous;
495     WKGetWheelEventDeltas(event, &deltaX, &deltaY, &isContinuous);
496
497 #if !defined(BUILDING_ON_LEOPARD) && !defined(BUILDING_ON_SNOW_LEOPARD)
498     NSEventPhase momentumPhase = [event momentumPhase];
499     BOOL isLatchingEvent = momentumPhase & NSEventPhaseBegan || momentumPhase & NSEventPhaseStationary;
500 #else
501     int momentumPhase = WKGetNSEventMomentumPhase(event);
502     BOOL isLatchingEvent = momentumPhase == WKEventPhaseBegan || momentumPhase == WKEventPhaseChanged;
503 #endif
504
505     if (fabsf(deltaY) > fabsf(deltaX)) {
506         if (![self allowsVerticalScrolling]) {
507             [[self nextResponder] scrollWheel:event];
508             return;
509         }
510
511         if (isLatchingEvent && !_private->verticallyPinnedByPreviousWheelEvent) {
512             double verticalPosition = [[self verticalScroller] doubleValue];
513             if ((deltaY >= 0.0 && verticalPosition == 0.0) || (deltaY <= 0.0 && verticalPosition == 1.0))
514                 return;
515         }
516     } else {
517         if (![self allowsHorizontalScrolling]) {
518             [[self nextResponder] scrollWheel:event];
519             return;
520         }
521
522         if (isLatchingEvent && !_private->horizontallyPinnedByPreviousWheelEvent) {
523             double horizontalPosition = [[self horizontalScroller] doubleValue];
524             if ((deltaX >= 0.0 && horizontalPosition == 0.0) || (deltaX <= 0.0 && horizontalPosition == 1.0))
525                 return;
526         }
527     }
528
529     // Calling super can release the last reference. <rdar://problem/7400263>
530     // Hold a reference so the code following the super call will not crash.
531     [self retain];
532
533     [super scrollWheel:event];
534
535     if (!isLatchingEvent) {
536         double verticalPosition = [[self verticalScroller] doubleValue];
537         double horizontalPosition = [[self horizontalScroller] doubleValue];
538
539         _private->verticallyPinnedByPreviousWheelEvent = (verticalPosition == 0.0 || verticalPosition == 1.0);
540         _private->horizontallyPinnedByPreviousWheelEvent = (horizontalPosition == 0.0 || horizontalPosition == 1.0);
541     }
542
543     [self release];
544 }
545
546 // This object will be the parent of the web area in WK1, so it should not be ignored.
547 - (BOOL)accessibilityIsIgnored 
548 {
549     return NO;
550 }
551
552 - (void)setScrollOrigin:(NSPoint)scrollOrigin updatePositionAtAll:(BOOL)updatePositionAtAll immediately:(BOOL)updatePositionSynchronously
553 {
554     // The cross-platform ScrollView call already checked to see if the old/new scroll origins were the same or not
555     // so we don't have to check for equivalence here.
556     _private->scrollOrigin = scrollOrigin;
557     id docView = [self documentView];
558
559     NSRect visibleRect = [self documentVisibleRect];
560
561     [docView setBoundsOrigin:NSMakePoint(-scrollOrigin.x, -scrollOrigin.y)];
562
563     if (updatePositionAtAll)
564         _private->scrollOriginChanged = true;
565
566     // Maintain our original position in the presence of the new scroll origin.
567     _private->scrollPositionExcludingOrigin = NSMakePoint(visibleRect.origin.x + scrollOrigin.x, visibleRect.origin.y + scrollOrigin.y);
568
569     if (updatePositionAtAll && updatePositionSynchronously) // Otherwise we'll just let the snap happen when we update for the resize.
570         [self adjustForScrollOriginChange];
571 }
572
573 - (NSPoint)scrollOrigin
574 {
575     return _private->scrollOrigin;
576 }
577
578 - (BOOL)inProgrammaticScroll
579 {
580     return _private->inProgrammaticScroll;
581 }
582
583 @end