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 "ScrollbarThemeWin.h"
28
29#include "GraphicsContext.h"
30#include "LocalWindowsContext.h"
31#include "PlatformMouseEvent.h"
32#include "Scrollbar.h"
33#include "SoftLinking.h"
34#include "SystemInfo.h"
35
36// Generic state constants
37#define TS_NORMAL    1
38#define TS_HOVER     2
39#define TS_ACTIVE    3
40#define TS_DISABLED  4
41
42#define SP_BUTTON          1
43#define SP_THUMBHOR        2
44#define SP_THUMBVERT       3
45#define SP_TRACKSTARTHOR   4
46#define SP_TRACKENDHOR     5
47#define SP_TRACKSTARTVERT  6
48#define SP_TRACKENDVERT    7
49#define SP_GRIPPERHOR      8
50#define SP_GRIPPERVERT     9
51
52#define TS_UP_BUTTON       0
53#define TS_DOWN_BUTTON     4
54#define TS_LEFT_BUTTON     8
55#define TS_RIGHT_BUTTON    12
56#define TS_UP_BUTTON_HOVER   17
57#define TS_DOWN_BUTTON_HOVER  18
58#define TS_LEFT_BUTTON_HOVER  19
59#define TS_RIGHT_BUTTON_HOVER   20
60
61using namespace std;
62
63namespace WebCore {
64
65static HANDLE scrollbarTheme;
66static bool runningVista;
67
68// FIXME:  Refactor the soft-linking code so that it can be shared with RenderThemeWin
69SOFT_LINK_LIBRARY(uxtheme)
70SOFT_LINK(uxtheme, OpenThemeData, HANDLE, WINAPI, (HWND hwnd, LPCWSTR pszClassList), (hwnd, pszClassList))
71SOFT_LINK(uxtheme, CloseThemeData, HRESULT, WINAPI, (HANDLE hTheme), (hTheme))
72SOFT_LINK(uxtheme, DrawThemeBackground, HRESULT, WINAPI, (HANDLE hTheme, HDC hdc, int iPartId, int iStateId, const RECT* pRect, const RECT* pClipRect), (hTheme, hdc, iPartId, iStateId, pRect, pClipRect))
73SOFT_LINK(uxtheme, IsThemeActive, BOOL, WINAPI, (), ())
74SOFT_LINK(uxtheme, IsThemeBackgroundPartiallyTransparent, BOOL, WINAPI, (HANDLE hTheme, int iPartId, int iStateId), (hTheme, iPartId, iStateId))
75
76// Constants used to figure the drag rect outside which we should snap the
77// scrollbar thumb back to its origin.  These calculations are based on
78// observing the behavior of the MSVC8 main window scrollbar + some
79// guessing/extrapolation.
80static const int kOffEndMultiplier = 3;
81static const int kOffSideMultiplier = 8;
82
83static void checkAndInitScrollbarTheme()
84{
85    if (uxthemeLibrary() && !scrollbarTheme && IsThemeActive())
86        scrollbarTheme = OpenThemeData(0, L"Scrollbar");
87}
88
89#if !USE(SAFARI_THEME)
90ScrollbarTheme* ScrollbarTheme::nativeTheme()
91{
92    static ScrollbarThemeWin winTheme;
93    return &winTheme;
94}
95#endif
96
97ScrollbarThemeWin::ScrollbarThemeWin()
98{
99    static bool initialized;
100    if (!initialized) {
101        initialized = true;
102        checkAndInitScrollbarTheme();
103        runningVista = (windowsVersion() >= WindowsVista);
104    }
105}
106
107ScrollbarThemeWin::~ScrollbarThemeWin()
108{
109}
110
111int ScrollbarThemeWin::scrollbarThickness(ScrollbarControlSize)
112{
113    static int thickness;
114    if (!thickness)
115        thickness = ::GetSystemMetrics(SM_CXVSCROLL);
116    return thickness;
117}
118
119void ScrollbarThemeWin::themeChanged()
120{
121    if (!scrollbarTheme)
122        return;
123
124    CloseThemeData(scrollbarTheme);
125    scrollbarTheme = 0;
126}
127
128bool ScrollbarThemeWin::invalidateOnMouseEnterExit()
129{
130    return runningVista;
131}
132
133bool ScrollbarThemeWin::hasThumb(Scrollbar* scrollbar)
134{
135    return thumbLength(scrollbar) > 0;
136}
137
138IntRect ScrollbarThemeWin::backButtonRect(Scrollbar* scrollbar, ScrollbarPart part, bool)
139{
140    // Windows just has single arrows.
141    if (part == BackButtonEndPart)
142        return IntRect();
143
144    // Our desired rect is essentially 17x17.
145
146    // Our actual rect will shrink to half the available space when
147    // we have < 34 pixels left.  This allows the scrollbar
148    // to scale down and function even at tiny sizes.
149    int thickness = scrollbarThickness();
150    if (scrollbar->orientation() == HorizontalScrollbar)
151        return IntRect(scrollbar->x(), scrollbar->y(),
152                       scrollbar->width() < 2 * thickness ? scrollbar->width() / 2 : thickness, thickness);
153    return IntRect(scrollbar->x(), scrollbar->y(),
154                   thickness, scrollbar->height() < 2 * thickness ? scrollbar->height() / 2 : thickness);
155}
156
157IntRect ScrollbarThemeWin::forwardButtonRect(Scrollbar* scrollbar, ScrollbarPart part, bool)
158{
159    // Windows just has single arrows.
160    if (part == ForwardButtonStartPart)
161        return IntRect();
162
163    // Our desired rect is essentially 17x17.
164
165    // Our actual rect will shrink to half the available space when
166    // we have < 34 pixels left.  This allows the scrollbar
167    // to scale down and function even at tiny sizes.
168    int thickness = scrollbarThickness();
169    if (scrollbar->orientation() == HorizontalScrollbar) {
170        int w = scrollbar->width() < 2 * thickness ? scrollbar->width() / 2 : thickness;
171        return IntRect(scrollbar->x() + scrollbar->width() - w, scrollbar->y(), w, thickness);
172    }
173
174    int h = scrollbar->height() < 2 * thickness ? scrollbar->height() / 2 : thickness;
175    return IntRect(scrollbar->x(), scrollbar->y() + scrollbar->height() - h, thickness, h);
176}
177
178IntRect ScrollbarThemeWin::trackRect(Scrollbar* scrollbar, bool)
179{
180    int thickness = scrollbarThickness();
181    if (scrollbar->orientation() == HorizontalScrollbar) {
182        if (scrollbar->width() < 2 * thickness)
183            return IntRect();
184        return IntRect(scrollbar->x() + thickness, scrollbar->y(), scrollbar->width() - 2 * thickness, thickness);
185    }
186    if (scrollbar->height() < 2 * thickness)
187        return IntRect();
188    return IntRect(scrollbar->x(), scrollbar->y() + thickness, thickness, scrollbar->height() - 2 * thickness);
189}
190
191bool ScrollbarThemeWin::shouldCenterOnThumb(Scrollbar*, const PlatformMouseEvent& evt)
192{
193    return evt.shiftKey() && evt.button() == LeftButton;
194}
195
196bool ScrollbarThemeWin::shouldSnapBackToDragOrigin(Scrollbar* scrollbar, const PlatformMouseEvent& evt)
197{
198    // Find the rect within which we shouldn't snap, by expanding the track rect
199    // in both dimensions.
200    IntRect rect = trackRect(scrollbar);
201    const bool horz = scrollbar->orientation() == HorizontalScrollbar;
202    const int thickness = scrollbarThickness(scrollbar->controlSize());
203    rect.inflateX((horz ? kOffEndMultiplier : kOffSideMultiplier) * thickness);
204    rect.inflateY((horz ? kOffSideMultiplier : kOffEndMultiplier) * thickness);
205
206    // Convert the event to local coordinates.
207    IntPoint mousePosition = scrollbar->convertFromContainingWindow(evt.pos());
208    mousePosition.move(scrollbar->x(), scrollbar->y());
209
210    // We should snap iff the event is outside our calculated rect.
211    return !rect.contains(mousePosition);
212}
213
214void ScrollbarThemeWin::paintTrackBackground(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect)
215{
216    // Just assume a forward track part.  We only paint the track as a single piece when there is no thumb.
217    if (!hasThumb(scrollbar))
218        paintTrackPiece(context, scrollbar, rect, ForwardTrackPart);
219}
220
221void ScrollbarThemeWin::paintTrackPiece(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect, ScrollbarPart partType)
222{
223    checkAndInitScrollbarTheme();
224
225    bool start = partType == BackTrackPart;
226    int part;
227    if (scrollbar->orientation() == HorizontalScrollbar)
228        part = start ? SP_TRACKSTARTHOR : SP_TRACKENDHOR;
229    else
230        part = start ? SP_TRACKSTARTVERT : SP_TRACKENDVERT;
231
232    int state;
233    if (!scrollbar->enabled())
234        state = TS_DISABLED;
235    else if ((scrollbar->hoveredPart() == BackTrackPart && start) ||
236             (scrollbar->hoveredPart() == ForwardTrackPart && !start))
237        state = (scrollbar->pressedPart() == scrollbar->hoveredPart() ? TS_ACTIVE : TS_HOVER);
238    else
239        state = TS_NORMAL;
240
241    bool alphaBlend = false;
242    if (scrollbarTheme)
243        alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, part, state);
244
245    LocalWindowsContext windowsContext(context, rect, alphaBlend);
246    RECT themeRect(rect);
247
248    if (scrollbarTheme)
249        DrawThemeBackground(scrollbarTheme, windowsContext.hdc(), part, state, &themeRect, 0);
250    else {
251        DWORD color3DFace = ::GetSysColor(COLOR_3DFACE);
252        DWORD colorScrollbar = ::GetSysColor(COLOR_SCROLLBAR);
253        DWORD colorWindow = ::GetSysColor(COLOR_WINDOW);
254        HDC hdc = windowsContext.hdc();
255        if ((color3DFace != colorScrollbar) && (colorWindow != colorScrollbar))
256            ::FillRect(hdc, &themeRect, HBRUSH(COLOR_SCROLLBAR+1));
257        else {
258            static WORD patternBits[8] = { 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55, 0xaa, 0x55 };
259            HBITMAP patternBitmap = ::CreateBitmap(8, 8, 1, 1, patternBits);
260            HBRUSH brush = ::CreatePatternBrush(patternBitmap);
261            SaveDC(hdc);
262            ::SetTextColor(hdc, ::GetSysColor(COLOR_3DHILIGHT));
263            ::SetBkColor(hdc, ::GetSysColor(COLOR_3DFACE));
264            ::SetBrushOrgEx(hdc, rect.x(), rect.y(), NULL);
265            ::SelectObject(hdc, brush);
266            ::FillRect(hdc, &themeRect, brush);
267            ::RestoreDC(hdc, -1);
268            ::DeleteObject(brush);
269            ::DeleteObject(patternBitmap);
270        }
271    }
272}
273
274void ScrollbarThemeWin::paintButton(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect, ScrollbarPart part)
275{
276    checkAndInitScrollbarTheme();
277
278    bool start = (part == BackButtonStartPart);
279    int xpState = 0;
280    int classicState = 0;
281    if (scrollbar->orientation() == HorizontalScrollbar)
282        xpState = start ? TS_LEFT_BUTTON : TS_RIGHT_BUTTON;
283    else
284        xpState = start ? TS_UP_BUTTON : TS_DOWN_BUTTON;
285    classicState = xpState / 4;
286
287    if (!scrollbar->enabled()) {
288        xpState += TS_DISABLED;
289        classicState |= DFCS_INACTIVE;
290    } else if ((scrollbar->hoveredPart() == BackButtonStartPart && start) ||
291               (scrollbar->hoveredPart() == ForwardButtonEndPart && !start)) {
292        if (scrollbar->pressedPart() == scrollbar->hoveredPart()) {
293            xpState += TS_ACTIVE;
294            classicState |= DFCS_PUSHED;
295#if !OS(WINCE)
296            classicState |= DFCS_FLAT;
297#endif
298        } else
299            xpState += TS_HOVER;
300    } else {
301        if (scrollbar->hoveredPart() == NoPart || !runningVista)
302            xpState += TS_NORMAL;
303        else {
304            if (scrollbar->orientation() == HorizontalScrollbar)
305                xpState = start ? TS_LEFT_BUTTON_HOVER : TS_RIGHT_BUTTON_HOVER;
306            else
307                xpState = start ? TS_UP_BUTTON_HOVER : TS_DOWN_BUTTON_HOVER;
308        }
309    }
310
311    bool alphaBlend = false;
312    if (scrollbarTheme)
313        alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, SP_BUTTON, xpState);
314
315    LocalWindowsContext windowsContext(context, rect, alphaBlend);
316    RECT themeRect(rect);
317    if (scrollbarTheme)
318        DrawThemeBackground(scrollbarTheme, windowsContext.hdc(), SP_BUTTON, xpState, &themeRect, 0);
319    else
320        ::DrawFrameControl(windowsContext.hdc(), &themeRect, DFC_SCROLL, classicState);
321}
322
323static IntRect gripperRect(int thickness, const IntRect& thumbRect)
324{
325    // Center in the thumb.
326    int gripperThickness = thickness / 2;
327    return IntRect(thumbRect.x() + (thumbRect.width() - gripperThickness) / 2,
328                   thumbRect.y() + (thumbRect.height() - gripperThickness) / 2,
329                   gripperThickness, gripperThickness);
330}
331
332static void paintGripper(Scrollbar* scrollbar, HDC hdc, const IntRect& rect)
333{
334    if (!scrollbarTheme)
335        return;  // Classic look has no gripper.
336
337    int state;
338    if (!scrollbar->enabled())
339        state = TS_DISABLED;
340    else if (scrollbar->pressedPart() == ThumbPart)
341        state = TS_ACTIVE; // Thumb always stays active once pressed.
342    else if (scrollbar->hoveredPart() == ThumbPart)
343        state = TS_HOVER;
344    else
345        state = TS_NORMAL;
346
347    RECT themeRect(rect);
348    DrawThemeBackground(scrollbarTheme, hdc, scrollbar->orientation() == HorizontalScrollbar ? SP_GRIPPERHOR : SP_GRIPPERVERT, state, &themeRect, 0);
349}
350
351void ScrollbarThemeWin::paintThumb(GraphicsContext* context, Scrollbar* scrollbar, const IntRect& rect)
352{
353    checkAndInitScrollbarTheme();
354
355    int state;
356    if (!scrollbar->enabled())
357        state = TS_DISABLED;
358    else if (scrollbar->pressedPart() == ThumbPart)
359        state = TS_ACTIVE; // Thumb always stays active once pressed.
360    else if (scrollbar->hoveredPart() == ThumbPart)
361        state = TS_HOVER;
362    else
363        state = TS_NORMAL;
364
365    bool alphaBlend = false;
366    if (scrollbarTheme)
367        alphaBlend = IsThemeBackgroundPartiallyTransparent(scrollbarTheme, scrollbar->orientation() == HorizontalScrollbar ? SP_THUMBHOR : SP_THUMBVERT, state);
368    HDC hdc = context->getWindowsContext(rect, alphaBlend);
369    RECT themeRect(rect);
370    if (scrollbarTheme) {
371        DrawThemeBackground(scrollbarTheme, hdc, scrollbar->orientation() == HorizontalScrollbar ? SP_THUMBHOR : SP_THUMBVERT, state, &themeRect, 0);
372        paintGripper(scrollbar, hdc, gripperRect(scrollbarThickness(), rect));
373    } else
374        ::DrawEdge(hdc, &themeRect, EDGE_RAISED, BF_RECT | BF_MIDDLE);
375    context->releaseWindowsContext(hdc, rect, alphaBlend);
376}
377
378}
379
380