1/*
2 * Copyright (C) 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 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 "ScrollbarThemeGtk.h"
28
29#include "PlatformMouseEvent.h"
30#include "RenderThemeGtk.h"
31#include "ScrollView.h"
32#include "Scrollbar.h"
33
34namespace WebCore {
35
36static HashSet<ScrollbarThemeClient*>* gScrollbars;
37
38ScrollbarTheme* ScrollbarTheme::nativeTheme()
39{
40    static ScrollbarThemeGtk theme;
41    return &theme;
42}
43
44ScrollbarThemeGtk::~ScrollbarThemeGtk()
45{
46}
47
48void ScrollbarThemeGtk::registerScrollbar(ScrollbarThemeClient* scrollbar)
49{
50    if (!gScrollbars)
51        gScrollbars = new HashSet<ScrollbarThemeClient*>;
52    gScrollbars->add(scrollbar);
53}
54
55void ScrollbarThemeGtk::unregisterScrollbar(ScrollbarThemeClient* scrollbar)
56{
57    gScrollbars->remove(scrollbar);
58    if (gScrollbars->isEmpty()) {
59        delete gScrollbars;
60        gScrollbars = 0;
61    }
62}
63
64void ScrollbarThemeGtk::updateScrollbarsFrameThickness()
65{
66    if (!gScrollbars)
67        return;
68
69    // Update the thickness of every interior frame scrollbar widget. The
70    // platform-independent scrollbar them code isn't yet smart enough to get
71    // this information when it paints.
72    HashSet<ScrollbarThemeClient*>::iterator end = gScrollbars->end();
73    for (HashSet<ScrollbarThemeClient*>::iterator it = gScrollbars->begin(); it != end; ++it) {
74        ScrollbarThemeClient* scrollbar = (*it);
75
76        // Top-level scrollbar i.e. scrollbars who have a parent ScrollView
77        // with no parent are native, and thus do not need to be resized.
78        if (!scrollbar->parent() || !scrollbar->parent()->parent())
79            return;
80
81        int thickness = scrollbarThickness(scrollbar->controlSize());
82        if (scrollbar->orientation() == HorizontalScrollbar)
83            scrollbar->setFrameRect(IntRect(0, scrollbar->parent()->height() - thickness, scrollbar->width(), thickness));
84        else
85            scrollbar->setFrameRect(IntRect(scrollbar->parent()->width() - thickness, 0, thickness, scrollbar->height()));
86    }
87}
88
89bool ScrollbarThemeGtk::hasThumb(ScrollbarThemeClient* scrollbar)
90{
91    // This method is just called as a paint-time optimization to see if
92    // painting the thumb can be skipped.  We don't have to be exact here.
93    return thumbLength(scrollbar) > 0;
94}
95
96IntRect ScrollbarThemeGtk::backButtonRect(ScrollbarThemeClient* scrollbar, ScrollbarPart part, bool)
97{
98    if (part == BackButtonEndPart && !m_hasBackButtonEndPart)
99        return IntRect();
100    if (part == BackButtonStartPart && !m_hasBackButtonStartPart)
101        return IntRect();
102
103    int x = scrollbar->x() + m_troughBorderWidth;
104    int y = scrollbar->y() + m_troughBorderWidth;
105    IntSize size = buttonSize(scrollbar);
106    if (part == BackButtonStartPart)
107        return IntRect(x, y, size.width(), size.height());
108
109    // BackButtonEndPart (alternate button)
110    if (scrollbar->orientation() == HorizontalScrollbar)
111        return IntRect(scrollbar->x() + scrollbar->width() - m_troughBorderWidth - (2 * size.width()), y, size.width(), size.height());
112
113    // VerticalScrollbar alternate button
114    return IntRect(x, scrollbar->y() + scrollbar->height() - m_troughBorderWidth - (2 * size.height()), size.width(), size.height());
115}
116
117IntRect ScrollbarThemeGtk::forwardButtonRect(ScrollbarThemeClient* scrollbar, ScrollbarPart part, bool)
118{
119    if (part == ForwardButtonStartPart && !m_hasForwardButtonStartPart)
120        return IntRect();
121    if (part == ForwardButtonEndPart && !m_hasForwardButtonEndPart)
122        return IntRect();
123
124    IntSize size = buttonSize(scrollbar);
125    if (scrollbar->orientation() == HorizontalScrollbar) {
126        int y = scrollbar->y() + m_troughBorderWidth;
127        if (part == ForwardButtonEndPart)
128            return IntRect(scrollbar->x() + scrollbar->width() - size.width() - m_troughBorderWidth, y, size.width(), size.height());
129
130        // ForwardButtonStartPart (alternate button)
131        return IntRect(scrollbar->x() + m_troughBorderWidth + size.width(), y, size.width(), size.height());
132    }
133
134    // VerticalScrollbar
135    int x = scrollbar->x() + m_troughBorderWidth;
136    if (part == ForwardButtonEndPart)
137        return IntRect(x, scrollbar->y() + scrollbar->height() - size.height() - m_troughBorderWidth, size.width(), size.height());
138
139    // ForwardButtonStartPart (alternate button)
140    return IntRect(x, scrollbar->y() + m_troughBorderWidth + size.height(), size.width(), size.height());
141}
142
143IntRect ScrollbarThemeGtk::trackRect(ScrollbarThemeClient* scrollbar, bool)
144{
145    // The padding along the thumb movement axis includes the trough border
146    // plus the size of stepper spacing (the space between the stepper and
147    // the place where the thumb stops). There is often no stepper spacing.
148    int movementAxisPadding = m_troughBorderWidth + m_stepperSpacing;
149
150    // The fatness of the scrollbar on the non-movement axis.
151    int thickness = scrollbarThickness(scrollbar->controlSize());
152
153    int startButtonsOffset = 0;
154    int buttonsWidth = 0;
155    if (m_hasForwardButtonStartPart) {
156        startButtonsOffset += m_stepperSize;
157        buttonsWidth += m_stepperSize;
158    }
159    if (m_hasBackButtonStartPart) {
160        startButtonsOffset += m_stepperSize;
161        buttonsWidth += m_stepperSize;
162    }
163    if (m_hasBackButtonEndPart)
164        buttonsWidth += m_stepperSize;
165    if (m_hasForwardButtonEndPart)
166        buttonsWidth += m_stepperSize;
167
168    if (scrollbar->orientation() == HorizontalScrollbar) {
169        // Once the scrollbar becomes smaller than the natural size of the
170        // two buttons, the track disappears.
171        if (scrollbar->width() < 2 * thickness)
172            return IntRect();
173        return IntRect(scrollbar->x() + movementAxisPadding + startButtonsOffset, scrollbar->y(),
174                       scrollbar->width() - (2 * movementAxisPadding) - buttonsWidth, thickness);
175    }
176
177    if (scrollbar->height() < 2 * thickness)
178        return IntRect();
179    return IntRect(scrollbar->x(), scrollbar->y() + movementAxisPadding + startButtonsOffset,
180                   thickness, scrollbar->height() - (2 * movementAxisPadding) - buttonsWidth);
181}
182
183IntRect ScrollbarThemeGtk::thumbRect(ScrollbarThemeClient* scrollbar, const IntRect& unconstrainedTrackRect)
184{
185    IntRect trackRect = constrainTrackRectToTrackPieces(scrollbar, unconstrainedTrackRect);
186    int thumbPos = thumbPosition(scrollbar);
187    if (scrollbar->orientation() == HorizontalScrollbar)
188        return IntRect(trackRect.x() + thumbPos, trackRect.y() + (trackRect.height() - m_thumbFatness) / 2, thumbLength(scrollbar), m_thumbFatness);
189
190    // VerticalScrollbar
191    return IntRect(trackRect.x() + (trackRect.width() - m_thumbFatness) / 2, trackRect.y() + thumbPos, m_thumbFatness, thumbLength(scrollbar));
192}
193
194bool ScrollbarThemeGtk::paint(ScrollbarThemeClient* scrollbar, GraphicsContext* graphicsContext, const IntRect& damageRect)
195{
196    if (graphicsContext->paintingDisabled())
197        return false;
198
199    // Create the ScrollbarControlPartMask based on the damageRect
200    ScrollbarControlPartMask scrollMask = NoPart;
201
202    IntRect backButtonStartPaintRect;
203    IntRect backButtonEndPaintRect;
204    IntRect forwardButtonStartPaintRect;
205    IntRect forwardButtonEndPaintRect;
206    if (hasButtons(scrollbar)) {
207        backButtonStartPaintRect = backButtonRect(scrollbar, BackButtonStartPart, true);
208        if (damageRect.intersects(backButtonStartPaintRect))
209            scrollMask |= BackButtonStartPart;
210        backButtonEndPaintRect = backButtonRect(scrollbar, BackButtonEndPart, true);
211        if (damageRect.intersects(backButtonEndPaintRect))
212            scrollMask |= BackButtonEndPart;
213        forwardButtonStartPaintRect = forwardButtonRect(scrollbar, ForwardButtonStartPart, true);
214        if (damageRect.intersects(forwardButtonStartPaintRect))
215            scrollMask |= ForwardButtonStartPart;
216        forwardButtonEndPaintRect = forwardButtonRect(scrollbar, ForwardButtonEndPart, true);
217        if (damageRect.intersects(forwardButtonEndPaintRect))
218            scrollMask |= ForwardButtonEndPart;
219    }
220
221    IntRect trackPaintRect = trackRect(scrollbar, true);
222    if (damageRect.intersects(trackPaintRect))
223        scrollMask |= TrackBGPart;
224
225    if (m_troughUnderSteppers && (scrollMask & BackButtonStartPart
226            || scrollMask & BackButtonEndPart
227            || scrollMask & ForwardButtonStartPart
228            || scrollMask & ForwardButtonEndPart))
229        scrollMask |= TrackBGPart;
230
231    bool thumbPresent = hasThumb(scrollbar);
232    IntRect currentThumbRect;
233    if (thumbPresent) {
234        IntRect track = trackRect(scrollbar, false);
235        currentThumbRect = thumbRect(scrollbar, track);
236        if (damageRect.intersects(currentThumbRect))
237            scrollMask |= ThumbPart;
238    }
239
240    ScrollbarControlPartMask allButtons = BackButtonStartPart | BackButtonEndPart
241                                         | ForwardButtonStartPart | ForwardButtonEndPart;
242    if (scrollMask & TrackBGPart || scrollMask & ThumbPart || scrollMask & allButtons)
243        paintScrollbarBackground(graphicsContext, scrollbar);
244        paintTrackBackground(graphicsContext, scrollbar, trackPaintRect);
245
246    // Paint the back and forward buttons.
247    if (scrollMask & BackButtonStartPart)
248        paintButton(graphicsContext, scrollbar, backButtonStartPaintRect, BackButtonStartPart);
249    if (scrollMask & BackButtonEndPart)
250        paintButton(graphicsContext, scrollbar, backButtonEndPaintRect, BackButtonEndPart);
251    if (scrollMask & ForwardButtonStartPart)
252        paintButton(graphicsContext, scrollbar, forwardButtonStartPaintRect, ForwardButtonStartPart);
253    if (scrollMask & ForwardButtonEndPart)
254        paintButton(graphicsContext, scrollbar, forwardButtonEndPaintRect, ForwardButtonEndPart);
255
256    // Paint the thumb.
257    if (scrollMask & ThumbPart)
258        paintThumb(graphicsContext, scrollbar, currentThumbRect);
259
260    return true;
261}
262
263bool ScrollbarThemeGtk::shouldCenterOnThumb(ScrollbarThemeClient*, const PlatformMouseEvent& event)
264{
265    return (event.shiftKey() && event.button() == LeftButton) || (event.button() == MiddleButton);
266}
267
268int ScrollbarThemeGtk::scrollbarThickness(ScrollbarControlSize)
269{
270    return m_thumbFatness + (m_troughBorderWidth * 2);
271}
272
273IntSize ScrollbarThemeGtk::buttonSize(ScrollbarThemeClient* scrollbar)
274{
275    if (scrollbar->orientation() == VerticalScrollbar)
276        return IntSize(m_thumbFatness, m_stepperSize);
277
278    // HorizontalScrollbar
279    return IntSize(m_stepperSize, m_thumbFatness);
280}
281
282int ScrollbarThemeGtk::minimumThumbLength(ScrollbarThemeClient* scrollbar)
283{
284    return m_minThumbLength;
285}
286
287}
288
289