wined3d: Shaders will never have a NULL function.
[wine] / dlls / comctl32 / theme_button.c
1 /*
2  * Theming - Button control
3  *
4  * Copyright (c) 2008 by Reece H. Dunn
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 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  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  */
21
22 #include <stdarg.h>
23 #include <string.h>
24 #include <stdlib.h>
25
26 #include "windef.h"
27 #include "winbase.h"
28 #include "wingdi.h"
29 #include "winuser.h"
30 #include "uxtheme.h"
31 #include "tmschema.h"
32 #include "comctl32.h"
33 #include "wine/debug.h"
34
35 WINE_DEFAULT_DEBUG_CHANNEL(themingbutton);
36
37 #define BUTTON_TYPE 0x0f /* bit mask for the available button types */
38
39 /* These are indices into a states array to determine the theme state for a given theme part. */
40 typedef enum
41 {
42         STATE_NORMAL,
43         STATE_DISABLED,
44         STATE_HOT,
45         STATE_PRESSED,
46         STATE_DEFAULTED
47 } ButtonState;
48
49 typedef void (*pfThemedPaint)(HTHEME theme, HWND hwnd, HDC hdc, ButtonState drawState, UINT dtFlags);
50
51 static UINT get_drawtext_flags(DWORD style, DWORD ex_style)
52 {
53     UINT flags = 0;
54
55     if (style & BS_PUSHLIKE)
56         style &= ~BUTTON_TYPE;
57
58     if (!(style & BS_MULTILINE))
59         flags |= DT_SINGLELINE;
60     else
61         flags |= DT_WORDBREAK;
62
63     switch (style & BS_CENTER)
64     {
65     case BS_LEFT:   flags |= DT_LEFT;   break;
66     case BS_RIGHT:  flags |= DT_RIGHT;  break;
67     case BS_CENTER: flags |= DT_CENTER; break;
68     default:
69         flags |= ((style & BUTTON_TYPE) <= BS_DEFPUSHBUTTON)
70                ? DT_CENTER : DT_LEFT;
71     }
72
73     if (ex_style & WS_EX_RIGHT)
74         flags = DT_RIGHT | (flags & ~(DT_LEFT | DT_CENTER));
75
76     if ((style & BUTTON_TYPE) != BS_GROUPBOX)
77     {
78         switch (style & BS_VCENTER)
79         {
80         case BS_TOP:     flags |= DT_TOP;     break;
81         case BS_BOTTOM:  flags |= DT_BOTTOM;  break;
82         case BS_VCENTER: /* fall through */
83         default:         flags |= DT_VCENTER; break;
84         }
85     }
86     else
87         /* GroupBox's text is always single line and is top aligned. */
88         flags |= DT_SINGLELINE | DT_TOP;
89
90     return flags;
91 }
92
93 static inline WCHAR *get_button_text(HWND hwnd)
94 {
95     INT len = 512;
96     WCHAR *text;
97     text = HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(WCHAR));
98     if (text) InternalGetWindowText(hwnd, text, len + 1);
99     return text;
100 }
101
102 static void PB_draw(HTHEME theme, HWND hwnd, HDC hDC, ButtonState drawState, UINT dtFlags)
103 {
104     static const int states[] = { PBS_NORMAL, PBS_DISABLED, PBS_HOT, PBS_PRESSED, PBS_DEFAULTED };
105
106     RECT bgRect, textRect;
107     HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
108     HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL;
109     int state = states[ drawState ];
110     WCHAR *text = get_button_text(hwnd);
111
112     GetClientRect(hwnd, &bgRect);
113     GetThemeBackgroundContentRect(theme, hDC, BP_PUSHBUTTON, state, &bgRect, &textRect);
114
115     if (IsThemeBackgroundPartiallyTransparent(theme, BP_PUSHBUTTON, state))
116         DrawThemeParentBackground(hwnd, hDC, NULL);
117     DrawThemeBackground(theme, hDC, BP_PUSHBUTTON, state, &bgRect, NULL);
118     if (text)
119     {
120         DrawThemeText(theme, hDC, BP_PUSHBUTTON, state, text, lstrlenW(text), dtFlags, 0, &textRect);
121         HeapFree(GetProcessHeap(), 0, text);
122     }
123
124     if (hPrevFont) SelectObject(hDC, hPrevFont);
125 }
126
127 static void CB_draw(HTHEME theme, HWND hwnd, HDC hDC, ButtonState drawState, UINT dtFlags)
128 {
129     static const int cb_states[3][5] =
130     {
131         { CBS_UNCHECKEDNORMAL, CBS_UNCHECKEDDISABLED, CBS_UNCHECKEDHOT, CBS_UNCHECKEDPRESSED, CBS_UNCHECKEDNORMAL },
132         { CBS_CHECKEDNORMAL, CBS_CHECKEDDISABLED, CBS_CHECKEDHOT, CBS_CHECKEDPRESSED, CBS_CHECKEDNORMAL },
133         { CBS_MIXEDNORMAL, CBS_MIXEDDISABLED, CBS_MIXEDHOT, CBS_MIXEDPRESSED, CBS_MIXEDNORMAL }
134     };
135
136     static const int rb_states[2][5] =
137     {
138         { RBS_UNCHECKEDNORMAL, RBS_UNCHECKEDDISABLED, RBS_UNCHECKEDHOT, RBS_UNCHECKEDPRESSED, RBS_UNCHECKEDNORMAL },
139         { RBS_CHECKEDNORMAL, RBS_CHECKEDDISABLED, RBS_CHECKEDHOT, RBS_CHECKEDPRESSED, RBS_CHECKEDNORMAL }
140     };
141
142     static const int cb_size = 13;
143
144     RECT bgRect, textRect;
145     HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
146     HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL;
147     LRESULT checkState = SendMessageW(hwnd, BM_GETCHECK, 0, 0);
148     DWORD dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
149     int part = ((dwStyle & BUTTON_TYPE) == BS_RADIOBUTTON) || ((dwStyle & BUTTON_TYPE) == BS_AUTORADIOBUTTON)
150              ? BP_RADIOBUTTON
151              : BP_CHECKBOX;
152     int state = (part == BP_CHECKBOX)
153               ? cb_states[ checkState ][ drawState ]
154               : rb_states[ checkState ][ drawState ];
155     WCHAR *text = get_button_text(hwnd);
156
157     GetClientRect(hwnd, &bgRect);
158     GetThemeBackgroundContentRect(theme, hDC, part, state, &bgRect, &textRect);
159
160     if (dtFlags & DT_SINGLELINE) /* Center the checkbox / radio button to the text. */
161         bgRect.top = bgRect.top + (textRect.bottom - textRect.top - cb_size) / 2;
162
163     /* adjust for the check/radio marker */
164     bgRect.bottom = bgRect.top + cb_size;
165     bgRect.right = bgRect.left + cb_size;
166     textRect.left = bgRect.right + 6;
167
168     if (IsThemeBackgroundPartiallyTransparent(theme, part, state))
169         DrawThemeParentBackground(hwnd, hDC, NULL);
170     DrawThemeBackground(theme, hDC, part, state, &bgRect, NULL);
171     if (text)
172     {
173         DrawThemeText(theme, hDC, part, state, text, lstrlenW(text), dtFlags, 0, &textRect);
174         HeapFree(GetProcessHeap(), 0, text);
175     }
176
177     if (hPrevFont) SelectObject(hDC, hPrevFont);
178 }
179
180 static void GB_draw(HTHEME theme, HWND hwnd, HDC hDC, ButtonState drawState, UINT dtFlags)
181 {
182     static const int states[] = { GBS_NORMAL, GBS_DISABLED, GBS_NORMAL, GBS_NORMAL, GBS_NORMAL };
183
184     RECT bgRect, textRect, contentRect;
185     HFONT font = (HFONT)SendMessageW(hwnd, WM_GETFONT, 0, 0);
186     HFONT hPrevFont = font ? SelectObject(hDC, font) : NULL;
187     int state = states[ drawState ];
188     WCHAR *text = get_button_text(hwnd);
189
190     GetClientRect(hwnd, &bgRect);
191     textRect = bgRect;
192
193     if (text)
194     {
195         SIZE textExtent;
196         GetTextExtentPoint32W(hDC, text, lstrlenW(text), &textExtent);
197         bgRect.top += (textExtent.cy / 2);
198         textRect.left += 10;
199         textRect.bottom = textRect.top + textExtent.cy;
200         textRect.right = textRect.left + textExtent.cx + 4;
201
202         ExcludeClipRect(hDC, textRect.left, textRect.top, textRect.right, textRect.bottom);
203     }
204
205     GetThemeBackgroundContentRect(theme, hDC, BP_GROUPBOX, state, &bgRect, &contentRect);
206     ExcludeClipRect(hDC, contentRect.left, contentRect.top, contentRect.right, contentRect.bottom);
207
208     if (IsThemeBackgroundPartiallyTransparent(theme, BP_GROUPBOX, state))
209         DrawThemeParentBackground(hwnd, hDC, NULL);
210     DrawThemeBackground(theme, hDC, BP_GROUPBOX, state, &bgRect, NULL);
211
212     SelectClipRgn(hDC, NULL);
213
214     if (text)
215     {
216         textRect.left += 2;
217         textRect.right -= 2;
218         DrawThemeText(theme, hDC, BP_GROUPBOX, state, text, lstrlenW(text), 0, 0, &textRect);
219         HeapFree(GetProcessHeap(), 0, text);
220     }
221
222     if (hPrevFont) SelectObject(hDC, hPrevFont);
223 }
224
225 static const pfThemedPaint btnThemedPaintFunc[BUTTON_TYPE + 1] =
226 {
227     PB_draw, /* BS_PUSHBUTTON */
228     PB_draw, /* BS_DEFPUSHBUTTON */
229     CB_draw, /* BS_CHECKBOX */
230     CB_draw, /* BS_AUTOCHECKBOX */
231     CB_draw, /* BS_RADIOBUTTON */
232     CB_draw, /* BS_3STATE */
233     CB_draw, /* BS_AUTO3STATE */
234     GB_draw, /* BS_GROUPBOX */
235     NULL, /* BS_USERBUTTON */
236     CB_draw, /* BS_AUTORADIOBUTTON */
237     NULL, /* Not defined */
238     NULL, /* BS_OWNERDRAW */
239     NULL, /* Not defined */
240     NULL, /* Not defined */
241     NULL, /* Not defined */
242     NULL, /* Not defined */
243 };
244
245 static BOOL BUTTON_Paint(HTHEME theme, HWND hwnd, HDC hParamDC)
246 {
247     PAINTSTRUCT ps;
248     HDC hDC;
249     DWORD dwStyle = GetWindowLongW(hwnd, GWL_STYLE);
250     DWORD dwStyleEx = GetWindowLongW(hwnd, GWL_EXSTYLE);
251     UINT dtFlags = get_drawtext_flags(dwStyle, dwStyleEx);
252     ButtonState drawState = IsWindowEnabled(hwnd) ? STATE_NORMAL : STATE_DISABLED;
253     pfThemedPaint paint = btnThemedPaintFunc[ dwStyle & BUTTON_TYPE ];
254
255     if (paint)
256     {
257         hDC = hParamDC ? hParamDC : BeginPaint(hwnd, &ps);
258         paint(theme, hwnd, hDC, drawState, dtFlags);
259         if (!hParamDC) EndPaint(hwnd, &ps);
260         return TRUE;
261     }
262
263     return FALSE; /* Delegate drawing to the non-themed code. */
264 }
265
266 /**********************************************************************
267  * The button control subclass window proc.
268  */
269 LRESULT CALLBACK THEMING_ButtonSubclassProc(HWND hwnd, UINT msg,
270                                             WPARAM wParam, LPARAM lParam,
271                                             ULONG_PTR dwRefData)
272 {
273     const WCHAR* themeClass = WC_BUTTONW;
274     HTHEME theme;
275     LRESULT result;
276
277     switch (msg)
278     {
279     case WM_CREATE:
280         result = THEMING_CallOriginalClass(hwnd, msg, wParam, lParam);
281         OpenThemeData(hwnd, themeClass);
282         return result;
283
284     case WM_DESTROY:
285         theme = GetWindowTheme(hwnd);
286         CloseThemeData (theme);
287         return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam);
288
289     case WM_THEMECHANGED:
290         theme = GetWindowTheme(hwnd);
291         CloseThemeData (theme);
292         OpenThemeData(hwnd, themeClass);
293         break;
294
295     case WM_SYSCOLORCHANGE:
296         theme = GetWindowTheme(hwnd);
297         if (!theme) return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam);
298         /* Do nothing. When themed, a WM_THEMECHANGED will be received, too,
299          * which will do the repaint. */
300         break;
301
302     case WM_PAINT:
303         theme = GetWindowTheme(hwnd);
304         if (theme && BUTTON_Paint(theme, hwnd, (HDC)wParam))
305             return 0;
306         else
307             return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam);
308
309     case WM_ENABLE:
310         theme = GetWindowTheme(hwnd);
311         if (theme) RedrawWindow(hwnd, NULL, NULL,
312                                 RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW);
313         return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam);
314
315     default:
316         /* Call old proc */
317         return THEMING_CallOriginalClass(hwnd, msg, wParam, lParam);
318     }
319     return 0;
320 }