2 * Copyright (C) 2004, 2006, 2008 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "Scrollbar.h"
29 #include "EventHandler.h"
31 #include "FrameView.h"
32 #include "GraphicsContext.h"
33 #include "PlatformMouseEvent.h"
34 #include "ScrollbarClient.h"
35 #include "ScrollbarTheme.h"
45 PassRefPtr
<Scrollbar
> Scrollbar::createNativeScrollbar(ScrollbarClient
* client
, ScrollbarOrientation orientation
, ScrollbarControlSize size
)
47 return adoptRef(new Scrollbar(client
, orientation
, size
));
51 Scrollbar::Scrollbar(ScrollbarClient
* client
, ScrollbarOrientation orientation
, ScrollbarControlSize controlSize
,
52 ScrollbarTheme
* theme
)
54 , m_orientation(orientation
)
55 , m_controlSize(controlSize
)
64 , m_hoveredPart(NoPart
)
65 , m_pressedPart(NoPart
)
68 , m_scrollTimer(this, &Scrollbar::autoscrollTimerFired
)
69 , m_overlapsResizer(false)
70 , m_suppressInvalidation(false)
73 m_theme
= ScrollbarTheme::nativeTheme();
75 m_theme
->registerScrollbar(this);
77 // FIXME: This is ugly and would not be necessary if we fix cross-platform code to actually query for
78 // scrollbar thickness and use it when sizing scrollbars (rather than leaving one dimension of the scrollbar
79 // alone when sizing).
80 int thickness
= m_theme
->scrollbarThickness(controlSize
);
81 Widget::setFrameRect(IntRect(0, 0, thickness
, thickness
));
84 Scrollbar::~Scrollbar()
88 m_theme
->unregisterScrollbar(this);
91 bool Scrollbar::setValue(int v
)
93 v
= max(min(v
, m_totalSize
- m_visibleSize
), 0);
95 return false; // Our value stayed the same.
100 void Scrollbar::setProportion(int visibleSize
, int totalSize
)
102 if (visibleSize
== m_visibleSize
&& totalSize
== m_totalSize
)
105 m_visibleSize
= visibleSize
;
106 m_totalSize
= totalSize
;
108 updateThumbProportion();
111 void Scrollbar::setSteps(int lineStep
, int pageStep
, int pixelsPerStep
)
113 m_lineStep
= lineStep
;
114 m_pageStep
= pageStep
;
115 m_pixelStep
= 1.0f
/ pixelsPerStep
;
118 bool Scrollbar::scroll(ScrollDirection direction
, ScrollGranularity granularity
, float multiplier
)
121 if ((direction
== ScrollUp
&& m_orientation
== VerticalScrollbar
) || (direction
== ScrollLeft
&& m_orientation
== HorizontalScrollbar
))
123 else if ((direction
== ScrollDown
&& m_orientation
== VerticalScrollbar
) || (direction
== ScrollRight
&& m_orientation
== HorizontalScrollbar
))
126 if (granularity
== ScrollByLine
)
128 else if (granularity
== ScrollByPage
)
130 else if (granularity
== ScrollByDocument
)
132 else if (granularity
== ScrollByPixel
)
135 float newPos
= m_currentPos
+ step
* multiplier
;
136 float maxPos
= m_totalSize
- m_visibleSize
;
137 return setCurrentPos(max(min(newPos
, maxPos
), 0.0f
));
140 void Scrollbar::updateThumbPosition()
142 theme()->invalidateParts(this, ForwardTrackPart
| BackTrackPart
| ThumbPart
);
145 void Scrollbar::updateThumbProportion()
147 theme()->invalidateParts(this, ForwardTrackPart
| BackTrackPart
| ThumbPart
);
150 void Scrollbar::paint(GraphicsContext
* context
, const IntRect
& damageRect
)
152 if (context
->updatingControlTints() && theme()->supportsControlTints()) {
157 if (context
->paintingDisabled() || !frameRect().intersects(damageRect
))
160 if (!theme()->paint(this, context
, damageRect
))
161 Widget::paint(context
, damageRect
);
164 void Scrollbar::autoscrollTimerFired(Timer
<Scrollbar
>*)
166 autoscrollPressedPart(theme()->autoscrollTimerDelay());
169 static bool thumbUnderMouse(Scrollbar
* scrollbar
)
171 int thumbPos
= scrollbar
->theme()->trackPosition(scrollbar
) + scrollbar
->theme()->thumbPosition(scrollbar
);
172 int thumbLength
= scrollbar
->theme()->thumbLength(scrollbar
);
173 return scrollbar
->pressedPos() >= thumbPos
&& scrollbar
->pressedPos() < thumbPos
+ thumbLength
;
176 void Scrollbar::autoscrollPressedPart(double delay
)
178 // Don't do anything for the thumb or if nothing was pressed.
179 if (m_pressedPart
== ThumbPart
|| m_pressedPart
== NoPart
)
183 if ((m_pressedPart
== BackTrackPart
|| m_pressedPart
== ForwardTrackPart
) && thumbUnderMouse(this)) {
184 theme()->invalidatePart(this, m_pressedPart
);
185 setHoveredPart(ThumbPart
);
189 // Handle the arrows and track.
190 if (scroll(pressedPartScrollDirection(), pressedPartScrollGranularity()))
191 startTimerIfNeeded(delay
);
194 void Scrollbar::startTimerIfNeeded(double delay
)
196 // Don't do anything for the thumb.
197 if (m_pressedPart
== ThumbPart
)
200 // Handle the track. We halt track scrolling once the thumb is level
202 if ((m_pressedPart
== BackTrackPart
|| m_pressedPart
== ForwardTrackPart
) && thumbUnderMouse(this)) {
203 theme()->invalidatePart(this, m_pressedPart
);
204 setHoveredPart(ThumbPart
);
208 // We can't scroll if we've hit the beginning or end.
209 ScrollDirection dir
= pressedPartScrollDirection();
210 if (dir
== ScrollUp
|| dir
== ScrollLeft
) {
211 if (m_currentPos
== 0)
214 if (m_currentPos
== maximum())
218 m_scrollTimer
.startOneShot(delay
);
221 void Scrollbar::stopTimerIfNeeded()
223 if (m_scrollTimer
.isActive())
224 m_scrollTimer
.stop();
227 ScrollDirection
Scrollbar::pressedPartScrollDirection()
229 if (m_orientation
== HorizontalScrollbar
) {
230 if (m_pressedPart
== BackButtonStartPart
|| m_pressedPart
== BackButtonEndPart
|| m_pressedPart
== BackTrackPart
)
234 if (m_pressedPart
== BackButtonStartPart
|| m_pressedPart
== BackButtonEndPart
|| m_pressedPart
== BackTrackPart
)
240 ScrollGranularity
Scrollbar::pressedPartScrollGranularity()
242 if (m_pressedPart
== BackButtonStartPart
|| m_pressedPart
== BackButtonEndPart
|| m_pressedPart
== ForwardButtonStartPart
|| m_pressedPart
== ForwardButtonEndPart
)
247 void Scrollbar::moveThumb(int pos
)
250 int thumbPos
= theme()->thumbPosition(this);
251 int thumbLen
= theme()->thumbLength(this);
252 int trackLen
= theme()->trackLength(this);
253 int maxPos
= trackLen
- thumbLen
;
254 int delta
= pos
- m_pressedPos
;
256 delta
= min(maxPos
- thumbPos
, delta
);
258 delta
= max(-thumbPos
, delta
);
260 setCurrentPos(static_cast<float>(thumbPos
+ delta
) * maximum() / (trackLen
- thumbLen
));
263 bool Scrollbar::setCurrentPos(float pos
)
265 if (pos
== m_currentPos
)
268 int oldValue
= value();
269 int oldThumbPos
= theme()->thumbPosition(this);
271 updateThumbPosition();
272 if (m_pressedPart
== ThumbPart
)
273 setPressedPos(m_pressedPos
+ theme()->thumbPosition(this) - oldThumbPos
);
275 if (value() != oldValue
&& client())
276 client()->valueChanged(this);
280 void Scrollbar::setHoveredPart(ScrollbarPart part
)
282 if (part
== m_hoveredPart
)
285 if ((m_hoveredPart
== NoPart
|| part
== NoPart
) && theme()->invalidateOnMouseEnterExit())
286 invalidate(); // Just invalidate the whole scrollbar, since the buttons at either end change anyway.
287 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.
288 theme()->invalidatePart(this, part
);
289 theme()->invalidatePart(this, m_hoveredPart
);
291 m_hoveredPart
= part
;
294 void Scrollbar::setPressedPart(ScrollbarPart part
)
296 if (m_pressedPart
!= NoPart
)
297 theme()->invalidatePart(this, m_pressedPart
);
298 m_pressedPart
= part
;
299 if (m_pressedPart
!= NoPart
)
300 theme()->invalidatePart(this, m_pressedPart
);
301 else if (m_hoveredPart
!= NoPart
) // When we no longer have a pressed part, we can start drawing a hovered state on the hovered part.
302 theme()->invalidatePart(this, m_hoveredPart
);
305 bool Scrollbar::mouseMoved(const PlatformMouseEvent
& evt
)
307 if (m_pressedPart
== ThumbPart
) {
308 if (theme()->shouldSnapBackToDragOrigin(this, evt
))
309 setCurrentPos(m_dragOrigin
);
311 moveThumb(m_orientation
== HorizontalScrollbar
?
312 convertFromContainingWindow(evt
.pos()).x() :
313 convertFromContainingWindow(evt
.pos()).y());
318 if (m_pressedPart
!= NoPart
)
319 m_pressedPos
= (orientation() == HorizontalScrollbar
? convertFromContainingWindow(evt
.pos()).x() : convertFromContainingWindow(evt
.pos()).y());
321 ScrollbarPart part
= theme()->hitTest(this, evt
);
322 if (part
!= m_hoveredPart
) {
323 if (m_pressedPart
!= NoPart
) {
324 if (part
== m_pressedPart
) {
325 // The mouse is moving back over the pressed part. We
326 // need to start up the timer action again.
327 startTimerIfNeeded(theme()->autoscrollTimerDelay());
328 theme()->invalidatePart(this, m_pressedPart
);
329 } else if (m_hoveredPart
== m_pressedPart
) {
330 // The mouse is leaving the pressed part. Kill our timer
333 theme()->invalidatePart(this, m_pressedPart
);
337 setHoveredPart(part
);
343 bool Scrollbar::mouseExited()
345 setHoveredPart(NoPart
);
349 bool Scrollbar::mouseUp()
351 setPressedPart(NoPart
);
355 if (parent() && parent()->isFrameView())
356 static_cast<FrameView
*>(parent())->frame()->eventHandler()->setMousePressed(false);
361 bool Scrollbar::mouseDown(const PlatformMouseEvent
& evt
)
363 // Early exit for right click
364 if (evt
.button() == RightButton
)
365 return true; // FIXME: Handled as context menu by Qt right now. Should just avoid even calling this method on a right click though.
367 setPressedPart(theme()->hitTest(this, evt
));
368 int pressedPos
= (orientation() == HorizontalScrollbar
? convertFromContainingWindow(evt
.pos()).x() : convertFromContainingWindow(evt
.pos()).y());
370 if ((m_pressedPart
== BackTrackPart
|| m_pressedPart
== ForwardTrackPart
) && theme()->shouldCenterOnThumb(this, evt
)) {
371 setHoveredPart(ThumbPart
);
372 setPressedPart(ThumbPart
);
373 m_dragOrigin
= m_currentPos
;
374 int thumbLen
= theme()->thumbLength(this);
375 int desiredPos
= pressedPos
;
376 // Set the pressed position to the middle of the thumb so that when we do the move, the delta
377 // will be from the current pixel position of the thumb to the new desired position for the thumb.
378 m_pressedPos
= theme()->trackPosition(this) + theme()->thumbPosition(this) + thumbLen
/ 2;
379 moveThumb(desiredPos
);
381 } else if (m_pressedPart
== ThumbPart
)
382 m_dragOrigin
= m_currentPos
;
384 m_pressedPos
= pressedPos
;
386 autoscrollPressedPart(theme()->initialAutoscrollTimerDelay());
390 void Scrollbar::setFrameRect(const IntRect
& rect
)
392 // Get our window resizer rect and see if we overlap. Adjust to avoid the overlap
394 IntRect
adjustedRect(rect
);
395 bool overlapsResizer
= false;
396 ScrollView
* view
= parent();
397 if (view
&& !rect
.isEmpty() && !view
->windowResizerRect().isEmpty()) {
398 IntRect resizerRect
= view
->convertFromContainingWindow(view
->windowResizerRect());
399 if (rect
.intersects(resizerRect
)) {
400 if (orientation() == HorizontalScrollbar
) {
401 int overlap
= rect
.right() - resizerRect
.x();
402 if (overlap
> 0 && resizerRect
.right() >= rect
.right()) {
403 adjustedRect
.setWidth(rect
.width() - overlap
);
404 overlapsResizer
= true;
407 int overlap
= rect
.bottom() - resizerRect
.y();
408 if (overlap
> 0 && resizerRect
.bottom() >= rect
.bottom()) {
409 adjustedRect
.setHeight(rect
.height() - overlap
);
410 overlapsResizer
= true;
415 if (overlapsResizer
!= m_overlapsResizer
) {
416 m_overlapsResizer
= overlapsResizer
;
418 view
->adjustScrollbarsAvoidingResizerCount(m_overlapsResizer
? 1 : -1);
421 Widget::setFrameRect(adjustedRect
);
424 void Scrollbar::setParent(ScrollView
* parentView
)
426 if (!parentView
&& m_overlapsResizer
&& parent())
427 parent()->adjustScrollbarsAvoidingResizerCount(-1);
428 Widget::setParent(parentView
);
431 void Scrollbar::setEnabled(bool e
)
439 bool Scrollbar::isWindowActive() const
441 return m_client
&& m_client
->isActive();
444 void Scrollbar::invalidateRect(const IntRect
& rect
)
446 if (suppressInvalidation())
449 m_client
->invalidateScrollbarRect(this, rect
);
452 IntRect
Scrollbar::convertToContainingView(const IntRect
& localRect
) const
455 return m_client
->convertFromScrollbarToContainingView(this, localRect
);
457 return Widget::convertToContainingView(localRect
);
460 IntRect
Scrollbar::convertFromContainingView(const IntRect
& parentRect
) const
463 return m_client
->convertFromContainingViewToScrollbar(this, parentRect
);
465 return Widget::convertFromContainingView(parentRect
);
468 IntPoint
Scrollbar::convertToContainingView(const IntPoint
& localPoint
) const
471 return m_client
->convertFromScrollbarToContainingView(this, localPoint
);
473 return Widget::convertToContainingView(localPoint
);
476 IntPoint
Scrollbar::convertFromContainingView(const IntPoint
& parentPoint
) const
479 return m_client
->convertFromContainingViewToScrollbar(this, parentPoint
);
481 return Widget::convertFromContainingView(parentPoint
);