RenderThemeWin.cpp revision d8543bb6618c17b12da906afa77d216f58cf4058
1/*
2 * This file is part of the WebKit project.
3 *
4 * Copyright (C) 2006 Apple Computer, Inc.
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Library General Public
8 * License as published by the Free Software Foundation; either
9 * version 2 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 * Library General Public License for more details.
15 *
16 * You should have received a copy of the GNU Library General Public License
17 * along with this library; see the file COPYING.LIB.  If not, write to
18 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
20 *
21 */
22
23#include "config.h"
24#include "RenderThemeWin.h"
25
26#include "CSSValueKeywords.h"
27#include "Document.h"
28#include "GraphicsContext.h"
29#include "PlatformString.h"
30#include "SoftLinking.h"
31
32#include <cairo-win32.h>
33
34/*
35 * The following constants are used to determine how a widget is drawn using
36 * Windows' Theme API. For more information on theme parts and states see
37 * http://msdn2.microsoft.com/en-us/library/bb773210(VS.85).aspx
38 */
39#define THEME_COLOR 204
40#define THEME_FONT  210
41
42// Generic state constants
43#define TS_NORMAL    1
44#define TS_HOVER     2
45#define TS_ACTIVE    3
46#define TS_DISABLED  4
47#define TS_FOCUSED   5
48
49// Button constants
50#define BP_BUTTON    1
51#define BP_RADIO     2
52#define BP_CHECKBOX  3
53
54// Textfield constants
55#define TFP_TEXTFIELD 1
56#define TFS_READONLY  6
57
58// Combobox constants
59#define CP_DROPDOWNBUTTON 1
60
61SOFT_LINK_LIBRARY(uxtheme)
62SOFT_LINK(uxtheme, OpenThemeData, HANDLE, WINAPI, (HWND hwnd, LPCWSTR pszClassList), (hwnd, pszClassList))
63SOFT_LINK(uxtheme, CloseThemeData, HRESULT, WINAPI, (HANDLE hTheme), (hTheme))
64SOFT_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))
65SOFT_LINK(uxtheme, DrawThemeEdge, HRESULT, WINAPI, (HANDLE hTheme, HDC hdc, int iPartId, int iStateId, const RECT* pRect, unsigned uEdge, unsigned uFlags, const RECT* pClipRect), (hTheme, hdc, iPartId, iStateId, pRect, uEdge, uFlags, pClipRect))
66SOFT_LINK(uxtheme, GetThemeContentRect, HRESULT, WINAPI, (HANDLE hTheme, HDC hdc, int iPartId, int iStateId, const RECT* pRect, const RECT* pContentRect), (hTheme, hdc, iPartId, iStateId, pRect, pContentRect))
67SOFT_LINK(uxtheme, GetThemePartSize, HRESULT, WINAPI, (HANDLE hTheme, HDC hdc, int iPartId, int iStateId, RECT* pRect, int ts, SIZE* psz), (hTheme, hdc, iPartId, iStateId, pRect, ts, psz))
68SOFT_LINK(uxtheme, GetThemeSysFont, HRESULT, WINAPI, (HANDLE hTheme, int iFontId, OUT LOGFONT* pFont), (hTheme, iFontId, pFont))
69SOFT_LINK(uxtheme, GetThemeColor, HRESULT, WINAPI, (HANDLE hTheme, HDC hdc, int iPartId, int iStateId, int iPropId, OUT COLORREF* pColor), (hTheme, hdc, iPartId, iStateId, iPropId, pColor))
70
71namespace WebCore {
72
73RenderTheme* theme()
74{
75    static RenderThemeWin winTheme;
76    return &winTheme;
77}
78
79RenderThemeWin::RenderThemeWin()
80    : m_buttonTheme(0)
81    , m_textFieldTheme(0)
82    , m_menuListTheme(0)
83{
84}
85
86RenderThemeWin::~RenderThemeWin()
87{
88    if (!uxthemeLibrary())
89        return;
90
91    close();
92}
93
94void RenderThemeWin::close()
95{
96    // This method will need to be called when the OS theme changes to flush our cached themes.
97    if (m_buttonTheme)
98        CloseThemeData(m_buttonTheme);
99    if (m_textFieldTheme)
100        CloseThemeData(m_textFieldTheme);
101    if (m_menuListTheme)
102        CloseThemeData(m_menuListTheme);
103    m_buttonTheme = m_textFieldTheme = m_menuListTheme = 0;
104}
105
106Color RenderThemeWin::platformActiveSelectionBackgroundColor() const
107{
108    COLORREF color = GetSysColor(COLOR_HIGHLIGHT);
109    return Color(GetRValue(color), GetGValue(color), GetBValue(color), 255);
110}
111
112Color RenderThemeWin::platformInactiveSelectionBackgroundColor() const
113{
114    COLORREF color = GetSysColor(COLOR_GRAYTEXT);
115    return Color(GetRValue(color), GetGValue(color), GetBValue(color), 255);
116}
117
118Color RenderThemeWin::platformActiveSelectionForegroundColor() const
119{
120    COLORREF color = GetSysColor(COLOR_HIGHLIGHTTEXT);
121    return Color(GetRValue(color), GetGValue(color), GetBValue(color), 255);
122}
123
124Color RenderThemeWin::platformInactiveSelectionForegroundColor() const
125{
126    return Color::white;
127}
128
129bool RenderThemeWin::supportsFocus(EAppearance appearance)
130{
131    switch (appearance) {
132        case PushButtonAppearance:
133        case ButtonAppearance:
134        case TextFieldAppearance:
135        case TextAreaAppearance:
136            return true;
137        default:
138            return false;
139    }
140}
141
142unsigned RenderThemeWin::determineState(RenderObject* o)
143{
144    unsigned result = TS_NORMAL;
145    if (!isEnabled(o))
146        result = TS_DISABLED;
147    else if (isReadOnlyControl(o))
148        result = TFS_READONLY; // Readonly is supported on textfields.
149    else if (supportsFocus(o->style()->appearance()) && isFocused(o))
150        result = TS_FOCUSED;
151    else if (isPressed(o)) // Active overrides hover.
152        result = TS_ACTIVE;
153    else if (isHovered(o))
154        result = TS_HOVER;
155    if (isChecked(o))
156        result += 4; // 4 unchecked states, 4 checked states.
157    return result;
158}
159
160unsigned RenderThemeWin::determineClassicState(RenderObject* o)
161{
162    unsigned result = 0;
163    if (!isEnabled(o) || isReadOnlyControl(o))
164        result = DFCS_INACTIVE;
165    else if (isPressed(o)) // Active supersedes hover
166        result = DFCS_PUSHED;
167    else if (isHovered(o))
168        result = DFCS_HOT;
169    if (isChecked(o))
170        result |= DFCS_CHECKED;
171    return result;
172}
173
174ThemeData RenderThemeWin::getThemeData(RenderObject* o)
175{
176    ThemeData result;
177    switch (o->style()->appearance()) {
178        case PushButtonAppearance:
179        case ButtonAppearance:
180            result.m_part = BP_BUTTON;
181            result.m_classicState = DFCS_BUTTONPUSH;
182            break;
183        case CheckboxAppearance:
184            result.m_part = BP_CHECKBOX;
185            result.m_classicState = DFCS_BUTTONCHECK;
186            break;
187        case RadioAppearance:
188            result.m_part = BP_RADIO;
189            result.m_classicState = DFCS_BUTTONRADIO;
190            break;
191        case ListboxAppearance:
192        case MenulistAppearance:
193        case TextFieldAppearance:
194        case TextAreaAppearance:
195            result.m_part = TFP_TEXTFIELD;
196            break;
197    }
198
199    result.m_state = determineState(o);
200    result.m_classicState |= determineClassicState(o);
201
202    return result;
203}
204
205// May need to add stuff to these later, so keep the graphics context retrieval/release in some helpers.
206static HDC prepareForDrawing(GraphicsContext* g, const IntRect& r)
207{
208    return g->getWindowsContext(r);
209}
210
211static void doneDrawing(GraphicsContext* g, HDC hdc, const IntRect& r)
212{
213    g->releaseWindowsContext(hdc, r);
214}
215
216bool RenderThemeWin::paintButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
217{
218    // Get the correct theme data for a button
219    ThemeData themeData = getThemeData(o);
220
221    // Now paint the button.
222    HDC hdc = prepareForDrawing(i.context, r);
223    RECT widgetRect = r;
224    if (uxthemeLibrary() && !m_buttonTheme)
225        m_buttonTheme = OpenThemeData(0, L"Button");
226    if (m_buttonTheme)
227        DrawThemeBackground(m_buttonTheme, hdc, themeData.m_part, themeData.m_state, &widgetRect, NULL);
228    else {
229        if ((themeData.m_part == BP_BUTTON) && isFocused(o)) {
230            // Draw black focus rect around button outer edge
231            HBRUSH brush = GetSysColorBrush(COLOR_3DDKSHADOW);
232            if (brush) {
233                FrameRect(hdc, &widgetRect, brush);
234                InflateRect(&widgetRect, -1, -1);
235            }
236        }
237        DrawFrameControl(hdc, &widgetRect, DFC_BUTTON, themeData.m_classicState);
238        if ((themeData.m_part != BP_BUTTON) && isFocused(o))
239            DrawFocusRect(hdc, &widgetRect);
240    }
241    doneDrawing(i.context, hdc, r);
242
243    return false;
244}
245
246void RenderThemeWin::setCheckboxSize(RenderStyle* style) const
247{
248    // If the width and height are both specified, then we have nothing to do.
249    if (!style->width().isIntrinsicOrAuto() && !style->height().isAuto())
250        return;
251
252    // FIXME:  A hard-coded size of 13 is used.  This is wrong but necessary for now.  It matches Firefox.
253    // At different DPI settings on Windows, querying the theme gives you a larger size that accounts for
254    // the higher DPI.  Until our entire engine honors a DPI setting other than 96, we can't rely on the theme's
255    // metrics.
256    if (style->width().isIntrinsicOrAuto())
257        style->setWidth(Length(13, Fixed));
258    if (style->height().isAuto())
259        style->setHeight(Length(13, Fixed));
260}
261
262bool RenderThemeWin::paintTextField(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
263{
264    // Get the correct theme data for a textfield
265    ThemeData themeData = getThemeData(o);
266
267    // Now paint the text field.
268    HDC hdc = prepareForDrawing(i.context, r);
269    RECT widgetRect = r;
270    if (uxthemeLibrary() && !m_textFieldTheme)
271        m_textFieldTheme = OpenThemeData(0, L"Edit");
272    if (m_textFieldTheme)
273        DrawThemeBackground(m_textFieldTheme, hdc, themeData.m_part, themeData.m_state, &widgetRect, NULL);
274    else {
275        DrawEdge(hdc, &widgetRect, EDGE_SUNKEN, BF_RECT | BF_ADJUST);
276        FillRect(hdc, &widgetRect, reinterpret_cast<HBRUSH>(((themeData.m_classicState & DFCS_INACTIVE) ? COLOR_BTNFACE : COLOR_WINDOW) + 1));
277    }
278    doneDrawing(i.context, hdc, r);
279
280    return false;
281}
282
283void RenderThemeWin::adjustMenuListStyle(CSSStyleSelector* selector, RenderStyle* style, Element* e) const
284{
285    // Height is locked to auto.
286    style->setHeight(Length(Auto));
287
288    // White-space is locked to pre
289    style->setWhiteSpace(PRE);
290
291    // Add in the padding that we'd like to use.
292    const int buttonWidth = GetSystemMetrics(SM_CXVSCROLL);
293    style->setPaddingLeft(Length(2, Fixed));
294    style->setPaddingRight(Length(buttonWidth + 2, Fixed));
295    style->setPaddingTop(Length(1, Fixed));
296    style->setPaddingBottom(Length(1, Fixed));
297}
298
299bool RenderThemeWin::paintMenuList(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
300{
301    // FIXME: All these inflate() calls are bogus, causing painting problems,
302    // as well as sizing wackiness in Classic mode
303    IntRect editRect(r);
304    paintTextField(o, i, editRect);
305
306    const int buttonWidth = GetSystemMetrics(SM_CXVSCROLL);
307    IntRect buttonRect(r.right() - buttonWidth - 1, r.y(), buttonWidth, r.height());
308    buttonRect.inflateY(-1);
309    paintMenuListButton(o, i, buttonRect);
310
311    return false;
312}
313
314bool RenderThemeWin::paintMenuListButton(RenderObject* o, const RenderObject::PaintInfo& i, const IntRect& r)
315{
316    HDC hdc = prepareForDrawing(i.context, r);
317    RECT widgetRect = r;
318    if (uxthemeLibrary() && !m_menuListTheme)
319        m_menuListTheme = OpenThemeData(0, L"Combobox");
320    if (m_menuListTheme)
321        DrawThemeBackground(m_menuListTheme, hdc, CP_DROPDOWNBUTTON, determineState(o), &widgetRect, NULL);
322    else
323        DrawFrameControl(hdc, &widgetRect, DFC_SCROLL, DFCS_SCROLLCOMBOBOX | determineClassicState(o));
324    doneDrawing(i.context, hdc, r);
325
326    return false;
327}
328
329void RenderThemeWin::systemFont(int propId, FontDescription& fontDescription) const
330{
331    static FontDescription systemFont;
332    static FontDescription smallSystemFont;
333    static FontDescription menuFont;
334    static FontDescription labelFont;
335    static FontDescription miniControlFont;
336    static FontDescription smallControlFont;
337    static FontDescription controlFont;
338
339    FontDescription* cachedDesc;
340    float fontSize = 0;
341    switch (propId) {
342        case CSS_VAL_SMALL_CAPTION:
343            cachedDesc = &smallSystemFont;
344            break;
345        case CSS_VAL_MENU:
346            cachedDesc = &menuFont;
347            break;
348        case CSS_VAL_STATUS_BAR:
349            cachedDesc = &labelFont;
350            if (!labelFont.isAbsoluteSize())
351                fontSize = 10.0f;
352            break;
353        case CSS_VAL__WEBKIT_MINI_CONTROL:
354            cachedDesc = &miniControlFont;
355            break;
356        case CSS_VAL__WEBKIT_SMALL_CONTROL:
357            cachedDesc = &smallControlFont;
358            break;
359        case CSS_VAL__WEBKIT_CONTROL:
360            cachedDesc = &controlFont;
361            break;
362        default:
363            cachedDesc = &systemFont;
364            if (!systemFont.isAbsoluteSize())
365                fontSize = 13.0f;
366    }
367
368    if (fontSize) {
369        cachedDesc->setIsAbsoluteSize(true);
370        cachedDesc->setGenericFamily(FontDescription::NoFamily);
371        cachedDesc->firstFamily().setFamily("Lucida Grande");
372        cachedDesc->setSpecifiedSize(fontSize);
373        cachedDesc->setBold(false);
374        cachedDesc->setItalic(false);
375    }
376    fontDescription = *cachedDesc;
377}
378
379} // namespace
380