1/*
2 * Copyright (C) 2008, 2009 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. ``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 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 "RenderScrollbar.h"
28
29#include "Frame.h"
30#include "FrameView.h"
31#include "RenderPart.h"
32#include "RenderScrollbarPart.h"
33#include "RenderScrollbarTheme.h"
34
35namespace WebCore {
36
37PassRefPtr<Scrollbar> RenderScrollbar::createCustomScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, RenderBox* renderer, Frame* owningFrame)
38{
39    return adoptRef(new RenderScrollbar(scrollableArea, orientation, renderer, owningFrame));
40}
41
42RenderScrollbar::RenderScrollbar(ScrollableArea* scrollableArea, ScrollbarOrientation orientation, RenderBox* renderer, Frame* owningFrame)
43    : Scrollbar(scrollableArea, orientation, RegularScrollbar, RenderScrollbarTheme::renderScrollbarTheme())
44    , m_owner(renderer)
45    , m_owningFrame(owningFrame)
46{
47    // FIXME: We need to do this because RenderScrollbar::styleChanged is called as soon as the scrollbar is created.
48
49    // Update the scrollbar size.
50    int width = 0;
51    int height = 0;
52    updateScrollbarPart(ScrollbarBGPart);
53    if (RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart)) {
54        part->layout();
55        width = part->width();
56        height = part->height();
57    } else if (this->orientation() == HorizontalScrollbar)
58        width = this->width();
59    else
60        height = this->height();
61
62    setFrameRect(IntRect(0, 0, width, height));
63}
64
65RenderScrollbar::~RenderScrollbar()
66{
67    ASSERT(m_parts.isEmpty());
68}
69
70RenderBox* RenderScrollbar::owningRenderer() const
71{
72    if (m_owningFrame) {
73        RenderBox* currentRenderer = m_owningFrame->ownerRenderer();
74        return currentRenderer;
75    }
76    return m_owner;
77}
78
79void RenderScrollbar::setParent(ScrollView* parent)
80{
81    Scrollbar::setParent(parent);
82    if (!parent) {
83        // Destroy all of the scrollbar's RenderBoxes.
84        updateScrollbarParts(true);
85    }
86}
87
88void RenderScrollbar::setEnabled(bool e)
89{
90    bool wasEnabled = enabled();
91    Scrollbar::setEnabled(e);
92    if (wasEnabled != e)
93        updateScrollbarParts();
94}
95
96void RenderScrollbar::styleChanged()
97{
98    updateScrollbarParts();
99}
100
101void RenderScrollbar::paint(GraphicsContext* context, const IntRect& damageRect)
102{
103    if (context->updatingControlTints()) {
104        updateScrollbarParts();
105        return;
106    }
107    Scrollbar::paint(context, damageRect);
108}
109
110void RenderScrollbar::setHoveredPart(ScrollbarPart part)
111{
112    if (part == m_hoveredPart)
113        return;
114
115    ScrollbarPart oldPart = m_hoveredPart;
116    m_hoveredPart = part;
117
118    updateScrollbarPart(oldPart);
119    updateScrollbarPart(m_hoveredPart);
120
121    updateScrollbarPart(ScrollbarBGPart);
122    updateScrollbarPart(TrackBGPart);
123}
124
125void RenderScrollbar::setPressedPart(ScrollbarPart part)
126{
127    ScrollbarPart oldPart = m_pressedPart;
128    Scrollbar::setPressedPart(part);
129
130    updateScrollbarPart(oldPart);
131    updateScrollbarPart(part);
132
133    updateScrollbarPart(ScrollbarBGPart);
134    updateScrollbarPart(TrackBGPart);
135}
136
137static ScrollbarPart s_styleResolvePart;
138static RenderScrollbar* s_styleResolveScrollbar;
139
140RenderScrollbar* RenderScrollbar::scrollbarForStyleResolve()
141{
142    return s_styleResolveScrollbar;
143}
144
145ScrollbarPart RenderScrollbar::partForStyleResolve()
146{
147    return s_styleResolvePart;
148}
149
150PassRefPtr<RenderStyle> RenderScrollbar::getScrollbarPseudoStyle(ScrollbarPart partType, PseudoId pseudoId)
151{
152    if (!m_owner)
153        return 0;
154
155    s_styleResolvePart = partType;
156    s_styleResolveScrollbar = this;
157    RefPtr<RenderStyle> result = owningRenderer()->getUncachedPseudoStyle(pseudoId, owningRenderer()->style());
158    s_styleResolvePart = NoPart;
159    s_styleResolveScrollbar = 0;
160
161    // Scrollbars for root frames should always have background color
162    // unless explicitly specified as transparent. So we force it.
163    // This is because WebKit assumes scrollbar to be always painted and missing background
164    // causes visual artifact like non-repainted dirty region.
165    if (result && m_owningFrame && m_owningFrame->view() && !m_owningFrame->view()->isTransparent() && !result->hasBackground())
166        result->setBackgroundColor(Color::white);
167
168    return result;
169}
170
171void RenderScrollbar::updateScrollbarParts(bool destroy)
172{
173    updateScrollbarPart(ScrollbarBGPart, destroy);
174    updateScrollbarPart(BackButtonStartPart, destroy);
175    updateScrollbarPart(ForwardButtonStartPart, destroy);
176    updateScrollbarPart(BackTrackPart, destroy);
177    updateScrollbarPart(ThumbPart, destroy);
178    updateScrollbarPart(ForwardTrackPart, destroy);
179    updateScrollbarPart(BackButtonEndPart, destroy);
180    updateScrollbarPart(ForwardButtonEndPart, destroy);
181    updateScrollbarPart(TrackBGPart, destroy);
182
183    if (destroy)
184        return;
185
186    // See if the scrollbar's thickness changed.  If so, we need to mark our owning object as needing a layout.
187    bool isHorizontal = orientation() == HorizontalScrollbar;
188    int oldThickness = isHorizontal ? height() : width();
189    int newThickness = 0;
190    RenderScrollbarPart* part = m_parts.get(ScrollbarBGPart);
191    if (part) {
192        part->layout();
193        newThickness = isHorizontal ? part->height() : part->width();
194    }
195
196    if (newThickness != oldThickness) {
197        setFrameRect(IntRect(x(), y(), isHorizontal ? width() : newThickness, isHorizontal ? newThickness : height()));
198        owningRenderer()->setChildNeedsLayout(true);
199    }
200}
201
202static PseudoId pseudoForScrollbarPart(ScrollbarPart part)
203{
204    switch (part) {
205        case BackButtonStartPart:
206        case ForwardButtonStartPart:
207        case BackButtonEndPart:
208        case ForwardButtonEndPart:
209            return SCROLLBAR_BUTTON;
210        case BackTrackPart:
211        case ForwardTrackPart:
212            return SCROLLBAR_TRACK_PIECE;
213        case ThumbPart:
214            return SCROLLBAR_THUMB;
215        case TrackBGPart:
216            return SCROLLBAR_TRACK;
217        case ScrollbarBGPart:
218            return SCROLLBAR;
219        case NoPart:
220        case AllParts:
221            break;
222    }
223    ASSERT_NOT_REACHED();
224    return SCROLLBAR;
225}
226
227void RenderScrollbar::updateScrollbarPart(ScrollbarPart partType, bool destroy)
228{
229    if (partType == NoPart)
230        return;
231
232    RefPtr<RenderStyle> partStyle = !destroy ? getScrollbarPseudoStyle(partType,  pseudoForScrollbarPart(partType)) : 0;
233
234    bool needRenderer = !destroy && partStyle && partStyle->display() != NONE && partStyle->visibility() == VISIBLE;
235
236    if (needRenderer && partStyle->display() != BLOCK) {
237        // See if we are a button that should not be visible according to OS settings.
238        ScrollbarButtonsPlacement buttonsPlacement = theme()->buttonsPlacement();
239        switch (partType) {
240            case BackButtonStartPart:
241                needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleStart ||
242                                buttonsPlacement == ScrollbarButtonsDoubleBoth);
243                break;
244            case ForwardButtonStartPart:
245                needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleStart || buttonsPlacement == ScrollbarButtonsDoubleBoth);
246                break;
247            case BackButtonEndPart:
248                needRenderer = (buttonsPlacement == ScrollbarButtonsDoubleEnd || buttonsPlacement == ScrollbarButtonsDoubleBoth);
249                break;
250            case ForwardButtonEndPart:
251                needRenderer = (buttonsPlacement == ScrollbarButtonsSingle || buttonsPlacement == ScrollbarButtonsDoubleEnd ||
252                                buttonsPlacement == ScrollbarButtonsDoubleBoth);
253                break;
254            default:
255                break;
256        }
257    }
258
259    RenderScrollbarPart* partRenderer = m_parts.get(partType);
260    if (!partRenderer && needRenderer) {
261        partRenderer = new (owningRenderer()->renderArena()) RenderScrollbarPart(owningRenderer()->document(), this, partType);
262        m_parts.set(partType, partRenderer);
263    } else if (partRenderer && !needRenderer) {
264        m_parts.remove(partType);
265        partRenderer->destroy();
266        partRenderer = 0;
267    }
268
269    if (partRenderer)
270        partRenderer->setStyle(partStyle.release());
271}
272
273void RenderScrollbar::paintPart(GraphicsContext* graphicsContext, ScrollbarPart partType, const IntRect& rect)
274{
275    RenderScrollbarPart* partRenderer = m_parts.get(partType);
276    if (!partRenderer)
277        return;
278    partRenderer->paintIntoRect(graphicsContext, x(), y(), rect);
279}
280
281IntRect RenderScrollbar::buttonRect(ScrollbarPart partType)
282{
283    RenderScrollbarPart* partRenderer = m_parts.get(partType);
284    if (!partRenderer)
285        return IntRect();
286
287    partRenderer->layout();
288
289    bool isHorizontal = orientation() == HorizontalScrollbar;
290    if (partType == BackButtonStartPart)
291        return IntRect(x(), y(), isHorizontal ? partRenderer->width() : width(), isHorizontal ? height() : partRenderer->height());
292    if (partType == ForwardButtonEndPart)
293        return IntRect(isHorizontal ? x() + width() - partRenderer->width() : x(),
294
295                       isHorizontal ? y() : y() + height() - partRenderer->height(),
296                       isHorizontal ? partRenderer->width() : width(),
297                       isHorizontal ? height() : partRenderer->height());
298
299    if (partType == ForwardButtonStartPart) {
300        IntRect previousButton = buttonRect(BackButtonStartPart);
301        return IntRect(isHorizontal ? x() + previousButton.width() : x(),
302                       isHorizontal ? y() : y() + previousButton.height(),
303                       isHorizontal ? partRenderer->width() : width(),
304                       isHorizontal ? height() : partRenderer->height());
305    }
306
307    IntRect followingButton = buttonRect(ForwardButtonEndPart);
308    return IntRect(isHorizontal ? x() + width() - followingButton.width() - partRenderer->width() : x(),
309                   isHorizontal ? y() : y() + height() - followingButton.height() - partRenderer->height(),
310                   isHorizontal ? partRenderer->width() : width(),
311                   isHorizontal ? height() : partRenderer->height());
312}
313
314IntRect RenderScrollbar::trackRect(int startLength, int endLength)
315{
316    RenderScrollbarPart* part = m_parts.get(TrackBGPart);
317    if (part)
318        part->layout();
319
320    if (orientation() == HorizontalScrollbar) {
321        int marginLeft = part ? part->marginLeft() : 0;
322        int marginRight = part ? part->marginRight() : 0;
323        startLength += marginLeft;
324        endLength += marginRight;
325        int totalLength = startLength + endLength;
326        return IntRect(x() + startLength, y(), width() - totalLength, height());
327    }
328
329    int marginTop = part ? part->marginTop() : 0;
330    int marginBottom = part ? part->marginBottom() : 0;
331    startLength += marginTop;
332    endLength += marginBottom;
333    int totalLength = startLength + endLength;
334
335    return IntRect(x(), y() + startLength, width(), height() - totalLength);
336}
337
338IntRect RenderScrollbar::trackPieceRectWithMargins(ScrollbarPart partType, const IntRect& oldRect)
339{
340    RenderScrollbarPart* partRenderer = m_parts.get(partType);
341    if (!partRenderer)
342        return oldRect;
343
344    partRenderer->layout();
345
346    IntRect rect = oldRect;
347    if (orientation() == HorizontalScrollbar) {
348        rect.setX(rect.x() + partRenderer->marginLeft());
349        rect.setWidth(rect.width() - (partRenderer->marginLeft() + partRenderer->marginRight()));
350    } else {
351        rect.setY(rect.y() + partRenderer->marginTop());
352        rect.setHeight(rect.height() - (partRenderer->marginTop() + partRenderer->marginBottom()));
353    }
354    return rect;
355}
356
357int RenderScrollbar::minimumThumbLength()
358{
359    RenderScrollbarPart* partRenderer = m_parts.get(ThumbPart);
360    if (!partRenderer)
361        return 0;
362    partRenderer->layout();
363    return orientation() == HorizontalScrollbar ? partRenderer->width() : partRenderer->height();
364}
365
366}
367