• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2004, 2006, 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  * 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 COMPUTER, INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 
26 #include "config.h"
27 #include "Scrollbar.h"
28 
29 #include "AccessibilityScrollbar.h"
30 #include "AXObjectCache.h"
31 #include "EventHandler.h"
32 #include "Frame.h"
33 #include "FrameView.h"
34 #include "GraphicsContext.h"
35 #include "PlatformMouseEvent.h"
36 #include "ScrollbarClient.h"
37 #include "ScrollbarTheme.h"
38 
39 #include <algorithm>
40 
41 using namespace std;
42 
43 namespace WebCore {
44 
45 #if !PLATFORM(GTK)
createNativeScrollbar(ScrollbarClient * client,ScrollbarOrientation orientation,ScrollbarControlSize size)46 PassRefPtr<Scrollbar> Scrollbar::createNativeScrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize size)
47 {
48     return adoptRef(new Scrollbar(client, orientation, size));
49 }
50 #endif
51 
maxOverlapBetweenPages()52 int Scrollbar::maxOverlapBetweenPages()
53 {
54     static int maxOverlapBetweenPages = ScrollbarTheme::nativeTheme()->maxOverlapBetweenPages();
55     return maxOverlapBetweenPages;
56 }
57 
Scrollbar(ScrollbarClient * client,ScrollbarOrientation orientation,ScrollbarControlSize controlSize,ScrollbarTheme * theme)58 Scrollbar::Scrollbar(ScrollbarClient* client, ScrollbarOrientation orientation, ScrollbarControlSize controlSize,
59                      ScrollbarTheme* theme)
60     : m_client(client)
61     , m_orientation(orientation)
62     , m_controlSize(controlSize)
63     , m_theme(theme)
64     , m_visibleSize(0)
65     , m_totalSize(0)
66     , m_currentPos(0)
67     , m_dragOrigin(0)
68     , m_lineStep(0)
69     , m_pageStep(0)
70     , m_pixelStep(1)
71     , m_hoveredPart(NoPart)
72     , m_pressedPart(NoPart)
73     , m_pressedPos(0)
74     , m_enabled(true)
75     , m_scrollTimer(this, &Scrollbar::autoscrollTimerFired)
76     , m_overlapsResizer(false)
77     , m_suppressInvalidation(false)
78 {
79     if (!m_theme)
80         m_theme = ScrollbarTheme::nativeTheme();
81 
82     m_theme->registerScrollbar(this);
83 
84     // FIXME: This is ugly and would not be necessary if we fix cross-platform code to actually query for
85     // scrollbar thickness and use it when sizing scrollbars (rather than leaving one dimension of the scrollbar
86     // alone when sizing).
87     int thickness = m_theme->scrollbarThickness(controlSize);
88     Widget::setFrameRect(IntRect(0, 0, thickness, thickness));
89 }
90 
~Scrollbar()91 Scrollbar::~Scrollbar()
92 {
93     stopTimerIfNeeded();
94 
95     m_theme->unregisterScrollbar(this);
96 }
97 
setValue(int v)98 bool Scrollbar::setValue(int v)
99 {
100     v = max(min(v, m_totalSize - m_visibleSize), 0);
101     if (value() == v)
102         return false; // Our value stayed the same.
103     setCurrentPos(v);
104     return true;
105 }
106 
setProportion(int visibleSize,int totalSize)107 void Scrollbar::setProportion(int visibleSize, int totalSize)
108 {
109     if (visibleSize == m_visibleSize && totalSize == m_totalSize)
110         return;
111 
112     m_visibleSize = visibleSize;
113     m_totalSize = totalSize;
114 
115     updateThumbProportion();
116 }
117 
setSteps(int lineStep,int pageStep,int pixelsPerStep)118 void Scrollbar::setSteps(int lineStep, int pageStep, int pixelsPerStep)
119 {
120     m_lineStep = lineStep;
121     m_pageStep = pageStep;
122     m_pixelStep = 1.0f / pixelsPerStep;
123 }
124 
scroll(ScrollDirection direction,ScrollGranularity granularity,float multiplier)125 bool Scrollbar::scroll(ScrollDirection direction, ScrollGranularity granularity, float multiplier)
126 {
127 #if HAVE(ACCESSIBILITY)
128     if (AXObjectCache::accessibilityEnabled()) {
129         if (parent() && parent()->isFrameView()) {
130             Document* document = static_cast<FrameView*>(parent())->frame()->document();
131             AXObjectCache* cache = document->axObjectCache();
132             AccessibilityScrollbar* axObject = static_cast<AccessibilityScrollbar*>(cache->getOrCreate(ScrollBarRole));
133             axObject->setScrollbar(this);
134             cache->postNotification(axObject, document, AXObjectCache::AXValueChanged, true);
135         }
136     }
137 #endif
138 
139     float step = 0;
140     if ((direction == ScrollUp && m_orientation == VerticalScrollbar) || (direction == ScrollLeft && m_orientation == HorizontalScrollbar))
141         step = -1;
142     else if ((direction == ScrollDown && m_orientation == VerticalScrollbar) || (direction == ScrollRight && m_orientation == HorizontalScrollbar))
143         step = 1;
144 
145     if (granularity == ScrollByLine)
146         step *= m_lineStep;
147     else if (granularity == ScrollByPage)
148         step *= m_pageStep;
149     else if (granularity == ScrollByDocument)
150         step *= m_totalSize;
151     else if (granularity == ScrollByPixel)
152         step *= m_pixelStep;
153 
154     float newPos = m_currentPos + step * multiplier;
155     float maxPos = m_totalSize - m_visibleSize;
156     return setCurrentPos(max(min(newPos, maxPos), 0.0f));
157 }
158 
updateThumbPosition()159 void Scrollbar::updateThumbPosition()
160 {
161     theme()->invalidateParts(this, ForwardTrackPart | BackTrackPart | ThumbPart);
162 }
163 
updateThumbProportion()164 void Scrollbar::updateThumbProportion()
165 {
166     theme()->invalidateParts(this, ForwardTrackPart | BackTrackPart | ThumbPart);
167 }
168 
paint(GraphicsContext * context,const IntRect & damageRect)169 void Scrollbar::paint(GraphicsContext* context, const IntRect& damageRect)
170 {
171     if (context->updatingControlTints() && theme()->supportsControlTints()) {
172         invalidate();
173         return;
174     }
175 
176     if (context->paintingDisabled() || !frameRect().intersects(damageRect))
177         return;
178 
179     if (!theme()->paint(this, context, damageRect))
180         Widget::paint(context, damageRect);
181 }
182 
autoscrollTimerFired(Timer<Scrollbar> *)183 void Scrollbar::autoscrollTimerFired(Timer<Scrollbar>*)
184 {
185     autoscrollPressedPart(theme()->autoscrollTimerDelay());
186 }
187 
thumbUnderMouse(Scrollbar * scrollbar)188 static bool thumbUnderMouse(Scrollbar* scrollbar)
189 {
190     int thumbPos = scrollbar->theme()->trackPosition(scrollbar) + scrollbar->theme()->thumbPosition(scrollbar);
191     int thumbLength = scrollbar->theme()->thumbLength(scrollbar);
192     return scrollbar->pressedPos() >= thumbPos && scrollbar->pressedPos() < thumbPos + thumbLength;
193 }
194 
autoscrollPressedPart(double delay)195 void Scrollbar::autoscrollPressedPart(double delay)
196 {
197     // Don't do anything for the thumb or if nothing was pressed.
198     if (m_pressedPart == ThumbPart || m_pressedPart == NoPart)
199         return;
200 
201     // Handle the track.
202     if ((m_pressedPart == BackTrackPart || m_pressedPart == ForwardTrackPart) && thumbUnderMouse(this)) {
203         theme()->invalidatePart(this, m_pressedPart);
204         setHoveredPart(ThumbPart);
205         return;
206     }
207 
208     // Handle the arrows and track.
209     if (scroll(pressedPartScrollDirection(), pressedPartScrollGranularity()))
210         startTimerIfNeeded(delay);
211 }
212 
startTimerIfNeeded(double delay)213 void Scrollbar::startTimerIfNeeded(double delay)
214 {
215     // Don't do anything for the thumb.
216     if (m_pressedPart == ThumbPart)
217         return;
218 
219     // Handle the track.  We halt track scrolling once the thumb is level
220     // with us.
221     if ((m_pressedPart == BackTrackPart || m_pressedPart == ForwardTrackPart) && thumbUnderMouse(this)) {
222         theme()->invalidatePart(this, m_pressedPart);
223         setHoveredPart(ThumbPart);
224         return;
225     }
226 
227     // We can't scroll if we've hit the beginning or end.
228     ScrollDirection dir = pressedPartScrollDirection();
229     if (dir == ScrollUp || dir == ScrollLeft) {
230         if (m_currentPos == 0)
231             return;
232     } else {
233         if (m_currentPos == maximum())
234             return;
235     }
236 
237     m_scrollTimer.startOneShot(delay);
238 }
239 
stopTimerIfNeeded()240 void Scrollbar::stopTimerIfNeeded()
241 {
242     if (m_scrollTimer.isActive())
243         m_scrollTimer.stop();
244 }
245 
pressedPartScrollDirection()246 ScrollDirection Scrollbar::pressedPartScrollDirection()
247 {
248     if (m_orientation == HorizontalScrollbar) {
249         if (m_pressedPart == BackButtonStartPart || m_pressedPart == BackButtonEndPart || m_pressedPart == BackTrackPart)
250             return ScrollLeft;
251         return ScrollRight;
252     } else {
253         if (m_pressedPart == BackButtonStartPart || m_pressedPart == BackButtonEndPart || m_pressedPart == BackTrackPart)
254             return ScrollUp;
255         return ScrollDown;
256     }
257 }
258 
pressedPartScrollGranularity()259 ScrollGranularity Scrollbar::pressedPartScrollGranularity()
260 {
261     if (m_pressedPart == BackButtonStartPart || m_pressedPart == BackButtonEndPart ||  m_pressedPart == ForwardButtonStartPart || m_pressedPart == ForwardButtonEndPart)
262         return ScrollByLine;
263     return ScrollByPage;
264 }
265 
moveThumb(int pos)266 void Scrollbar::moveThumb(int pos)
267 {
268     // Drag the thumb.
269     int thumbPos = theme()->thumbPosition(this);
270     int thumbLen = theme()->thumbLength(this);
271     int trackLen = theme()->trackLength(this);
272     int maxPos = trackLen - thumbLen;
273     int delta = pos - m_pressedPos;
274     if (delta > 0)
275         delta = min(maxPos - thumbPos, delta);
276     else if (delta < 0)
277         delta = max(-thumbPos, delta);
278     if (delta)
279         setCurrentPos(static_cast<float>(thumbPos + delta) * maximum() / (trackLen - thumbLen));
280 }
281 
setCurrentPos(float pos)282 bool Scrollbar::setCurrentPos(float pos)
283 {
284     if (pos == m_currentPos)
285         return false;
286 
287     int oldValue = value();
288     int oldThumbPos = theme()->thumbPosition(this);
289     m_currentPos = pos;
290     updateThumbPosition();
291     if (m_pressedPart == ThumbPart)
292         setPressedPos(m_pressedPos + theme()->thumbPosition(this) - oldThumbPos);
293 
294     if (value() != oldValue && client())
295         client()->valueChanged(this);
296     return true;
297 }
298 
setHoveredPart(ScrollbarPart part)299 void Scrollbar::setHoveredPart(ScrollbarPart part)
300 {
301     if (part == m_hoveredPart)
302         return;
303 
304     if ((m_hoveredPart == NoPart || part == NoPart) && theme()->invalidateOnMouseEnterExit())
305         invalidate();  // Just invalidate the whole scrollbar, since the buttons at either end change anyway.
306     else if (m_pressedPart == NoPart) {  // When there's a pressed part, we don't draw a hovered state, so there's no reason to invalidate.
307         theme()->invalidatePart(this, part);
308         theme()->invalidatePart(this, m_hoveredPart);
309     }
310     m_hoveredPart = part;
311 }
312 
setPressedPart(ScrollbarPart part)313 void Scrollbar::setPressedPart(ScrollbarPart part)
314 {
315     if (m_pressedPart != NoPart)
316         theme()->invalidatePart(this, m_pressedPart);
317     m_pressedPart = part;
318     if (m_pressedPart != NoPart)
319         theme()->invalidatePart(this, m_pressedPart);
320     else if (m_hoveredPart != NoPart)  // When we no longer have a pressed part, we can start drawing a hovered state on the hovered part.
321         theme()->invalidatePart(this, m_hoveredPart);
322 }
323 
mouseMoved(const PlatformMouseEvent & evt)324 bool Scrollbar::mouseMoved(const PlatformMouseEvent& evt)
325 {
326     if (m_pressedPart == ThumbPart) {
327         if (theme()->shouldSnapBackToDragOrigin(this, evt))
328             setCurrentPos(m_dragOrigin);
329         else {
330             moveThumb(m_orientation == HorizontalScrollbar ?
331                       convertFromContainingWindow(evt.pos()).x() :
332                       convertFromContainingWindow(evt.pos()).y());
333         }
334         return true;
335     }
336 
337     if (m_pressedPart != NoPart)
338         m_pressedPos = (orientation() == HorizontalScrollbar ? convertFromContainingWindow(evt.pos()).x() : convertFromContainingWindow(evt.pos()).y());
339 
340     ScrollbarPart part = theme()->hitTest(this, evt);
341     if (part != m_hoveredPart) {
342         if (m_pressedPart != NoPart) {
343             if (part == m_pressedPart) {
344                 // The mouse is moving back over the pressed part.  We
345                 // need to start up the timer action again.
346                 startTimerIfNeeded(theme()->autoscrollTimerDelay());
347                 theme()->invalidatePart(this, m_pressedPart);
348             } else if (m_hoveredPart == m_pressedPart) {
349                 // The mouse is leaving the pressed part.  Kill our timer
350                 // if needed.
351                 stopTimerIfNeeded();
352                 theme()->invalidatePart(this, m_pressedPart);
353             }
354         }
355 
356         setHoveredPart(part);
357     }
358 
359     return true;
360 }
361 
mouseExited()362 bool Scrollbar::mouseExited()
363 {
364     setHoveredPart(NoPart);
365     return true;
366 }
367 
mouseUp()368 bool Scrollbar::mouseUp()
369 {
370     setPressedPart(NoPart);
371     m_pressedPos = 0;
372     stopTimerIfNeeded();
373 
374     if (parent() && parent()->isFrameView())
375         static_cast<FrameView*>(parent())->frame()->eventHandler()->setMousePressed(false);
376 
377     return true;
378 }
379 
mouseDown(const PlatformMouseEvent & evt)380 bool Scrollbar::mouseDown(const PlatformMouseEvent& evt)
381 {
382     // Early exit for right click
383     if (evt.button() == RightButton)
384         return true; // FIXME: Handled as context menu by Qt right now.  Should just avoid even calling this method on a right click though.
385 
386     setPressedPart(theme()->hitTest(this, evt));
387     int pressedPos = (orientation() == HorizontalScrollbar ? convertFromContainingWindow(evt.pos()).x() : convertFromContainingWindow(evt.pos()).y());
388 
389     if ((m_pressedPart == BackTrackPart || m_pressedPart == ForwardTrackPart) && theme()->shouldCenterOnThumb(this, evt)) {
390         setHoveredPart(ThumbPart);
391         setPressedPart(ThumbPart);
392         m_dragOrigin = m_currentPos;
393         int thumbLen = theme()->thumbLength(this);
394         int desiredPos = pressedPos;
395         // Set the pressed position to the middle of the thumb so that when we do the move, the delta
396         // will be from the current pixel position of the thumb to the new desired position for the thumb.
397         m_pressedPos = theme()->trackPosition(this) + theme()->thumbPosition(this) + thumbLen / 2;
398         moveThumb(desiredPos);
399         return true;
400     } else if (m_pressedPart == ThumbPart)
401         m_dragOrigin = m_currentPos;
402 
403     m_pressedPos = pressedPos;
404 
405     autoscrollPressedPart(theme()->initialAutoscrollTimerDelay());
406     return true;
407 }
408 
setFrameRect(const IntRect & rect)409 void Scrollbar::setFrameRect(const IntRect& rect)
410 {
411     // Get our window resizer rect and see if we overlap.  Adjust to avoid the overlap
412     // if necessary.
413     IntRect adjustedRect(rect);
414     bool overlapsResizer = false;
415     ScrollView* view = parent();
416     if (view && !rect.isEmpty() && !view->windowResizerRect().isEmpty()) {
417         IntRect resizerRect = view->convertFromContainingWindow(view->windowResizerRect());
418         if (rect.intersects(resizerRect)) {
419             if (orientation() == HorizontalScrollbar) {
420                 int overlap = rect.right() - resizerRect.x();
421                 if (overlap > 0 && resizerRect.right() >= rect.right()) {
422                     adjustedRect.setWidth(rect.width() - overlap);
423                     overlapsResizer = true;
424                 }
425             } else {
426                 int overlap = rect.bottom() - resizerRect.y();
427                 if (overlap > 0 && resizerRect.bottom() >= rect.bottom()) {
428                     adjustedRect.setHeight(rect.height() - overlap);
429                     overlapsResizer = true;
430                 }
431             }
432         }
433     }
434     if (overlapsResizer != m_overlapsResizer) {
435         m_overlapsResizer = overlapsResizer;
436         if (view)
437             view->adjustScrollbarsAvoidingResizerCount(m_overlapsResizer ? 1 : -1);
438     }
439 
440     Widget::setFrameRect(adjustedRect);
441 }
442 
setParent(ScrollView * parentView)443 void Scrollbar::setParent(ScrollView* parentView)
444 {
445     if (!parentView && m_overlapsResizer && parent())
446         parent()->adjustScrollbarsAvoidingResizerCount(-1);
447     Widget::setParent(parentView);
448 }
449 
setEnabled(bool e)450 void Scrollbar::setEnabled(bool e)
451 {
452     if (m_enabled == e)
453         return;
454     m_enabled = e;
455     invalidate();
456 }
457 
isWindowActive() const458 bool Scrollbar::isWindowActive() const
459 {
460     return m_client && m_client->isActive();
461 }
462 
invalidateRect(const IntRect & rect)463 void Scrollbar::invalidateRect(const IntRect& rect)
464 {
465     if (suppressInvalidation())
466         return;
467     if (m_client)
468         m_client->invalidateScrollbarRect(this, rect);
469 }
470 
convertToContainingView(const IntRect & localRect) const471 IntRect Scrollbar::convertToContainingView(const IntRect& localRect) const
472 {
473     if (m_client)
474         return m_client->convertFromScrollbarToContainingView(this, localRect);
475 
476     return Widget::convertToContainingView(localRect);
477 }
478 
convertFromContainingView(const IntRect & parentRect) const479 IntRect Scrollbar::convertFromContainingView(const IntRect& parentRect) const
480 {
481     if (m_client)
482         return m_client->convertFromContainingViewToScrollbar(this, parentRect);
483 
484     return Widget::convertFromContainingView(parentRect);
485 }
486 
convertToContainingView(const IntPoint & localPoint) const487 IntPoint Scrollbar::convertToContainingView(const IntPoint& localPoint) const
488 {
489     if (m_client)
490         return m_client->convertFromScrollbarToContainingView(this, localPoint);
491 
492     return Widget::convertToContainingView(localPoint);
493 }
494 
convertFromContainingView(const IntPoint & parentPoint) const495 IntPoint Scrollbar::convertFromContainingView(const IntPoint& parentPoint) const
496 {
497     if (m_client)
498         return m_client->convertFromContainingViewToScrollbar(this, parentPoint);
499 
500     return Widget::convertFromContainingView(parentPoint);
501 }
502 
503 }
504