comctl32/tests: Make the test dpi and theme aware by using SM_CYSIZE.
[wine] / dlls / comctl32 / hotkey.c
1 /*
2  * Hotkey control
3  *
4  * Copyright 1998, 1999 Eric Kohl
5  * Copyright 2002 Gyorgy 'Nog' Jeney
6  * Copyright 2004 Robert Shearman
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  *
22  * This code was audited for completeness against the documented features
23  * of Comctl32.dll version 6.0 on Sep. 21, 2004, by Robert Shearman.
24  * 
25  * Unless otherwise noted, we believe this code to be complete, as per
26  * the specification mentioned above.
27  * If you discover missing features or bugs please note them below.
28  *
29  */
30
31 #include <stdarg.h>
32 #include <string.h>
33 #include "windef.h"
34 #include "winbase.h"
35 #include "wingdi.h"
36 #include "winuser.h"
37 #include "winnls.h"
38 #include "commctrl.h"
39 #include "comctl32.h"
40 #include "wine/debug.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(hotkey);
43
44 typedef struct tagHOTKEY_INFO
45 {
46     HWND  hwndSelf;
47     HWND  hwndNotify;
48     HFONT hFont;
49     BOOL  bFocus;
50     INT   nHeight;
51     WORD  HotKey;
52     WORD  InvComb;
53     WORD  InvMod;
54     BYTE  CurrMod;
55     INT   CaretPos;
56     DWORD ScanCode;
57     WCHAR strNone[15]; /* hope it's long enough ... */
58 } HOTKEY_INFO;
59
60 static const WCHAR HOTKEY_plussep[] = { ' ', '+', ' ' };
61 static LRESULT HOTKEY_SetFont (HOTKEY_INFO *infoPtr, HFONT hFont, BOOL redraw);
62
63 #define IsOnlySet(flags) (infoPtr->CurrMod == (flags))
64
65 static BOOL
66 HOTKEY_IsCombInv(const HOTKEY_INFO *infoPtr)
67 {
68     TRACE("(infoPtr=%p)\n", infoPtr);
69     if((infoPtr->InvComb & HKCOMB_NONE) && !infoPtr->CurrMod)
70         return TRUE;
71     if((infoPtr->InvComb & HKCOMB_S) && IsOnlySet(HOTKEYF_SHIFT))
72         return TRUE;
73     if((infoPtr->InvComb & HKCOMB_C) && IsOnlySet(HOTKEYF_CONTROL))
74         return TRUE;
75     if((infoPtr->InvComb & HKCOMB_A) && IsOnlySet(HOTKEYF_ALT))
76         return TRUE;
77     if((infoPtr->InvComb & HKCOMB_SC) && 
78        IsOnlySet(HOTKEYF_SHIFT | HOTKEYF_CONTROL))
79         return TRUE;
80     if((infoPtr->InvComb & HKCOMB_SA) && IsOnlySet(HOTKEYF_SHIFT | HOTKEYF_ALT))
81         return TRUE;
82     if((infoPtr->InvComb & HKCOMB_CA) && 
83        IsOnlySet(HOTKEYF_CONTROL | HOTKEYF_ALT))
84         return TRUE;
85     if((infoPtr->InvComb & HKCOMB_SCA) && 
86        IsOnlySet(HOTKEYF_SHIFT | HOTKEYF_CONTROL | HOTKEYF_ALT))
87         return TRUE;
88
89     TRACE("() Modifiers are valid\n");
90     return FALSE;
91 }
92 #undef IsOnlySet
93
94 static void
95 HOTKEY_DrawHotKey(HOTKEY_INFO *infoPtr, HDC hdc, LPCWSTR KeyName, WORD NameLen)
96 {
97     SIZE TextSize;
98     INT nXStart, nYStart;
99     COLORREF clrOldText, clrOldBk;
100     HFONT hFontOld;
101
102     /* Make a gap from the frame */
103     nXStart = GetSystemMetrics(SM_CXBORDER);
104     nYStart = GetSystemMetrics(SM_CYBORDER);
105
106     hFontOld = SelectObject(hdc, infoPtr->hFont);
107     if (GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_DISABLED)
108     {
109         clrOldText = SetTextColor(hdc, comctl32_color.clrGrayText);
110         clrOldBk = SetBkColor(hdc, comctl32_color.clrBtnFace);
111     }
112     else
113     {
114         clrOldText = SetTextColor(hdc, comctl32_color.clrWindowText);
115         clrOldBk = SetBkColor(hdc, comctl32_color.clrWindow);
116     }
117
118     TextOutW(hdc, nXStart, nYStart, KeyName, NameLen);
119
120     /* Get the text width for the caret */
121     GetTextExtentPoint32W(hdc, KeyName, NameLen, &TextSize);
122     infoPtr->CaretPos = nXStart + TextSize.cx;
123
124     SetBkColor(hdc, clrOldBk);
125     SetTextColor(hdc, clrOldText);
126     SelectObject(hdc, hFontOld);
127
128     /* position the caret */
129     SetCaretPos(infoPtr->CaretPos, nYStart);
130 }
131
132 /* Draw the names of the keys in the control */
133 static void 
134 HOTKEY_Refresh(HOTKEY_INFO *infoPtr, HDC hdc)
135 {
136     WCHAR KeyName[64];
137     WORD NameLen = 0;
138     BYTE Modifier;
139
140     TRACE("(infoPtr=%p hdc=%p)\n", infoPtr, hdc);
141
142     if(!infoPtr->CurrMod && !infoPtr->HotKey) {
143         HOTKEY_DrawHotKey (infoPtr, hdc, infoPtr->strNone, 4);
144         return;
145     }
146         
147     if(infoPtr->HotKey)
148         Modifier = HIBYTE(infoPtr->HotKey);
149     else if(HOTKEY_IsCombInv(infoPtr)) 
150         Modifier = infoPtr->InvMod;
151     else
152         Modifier = infoPtr->CurrMod;
153
154     if(Modifier & HOTKEYF_CONTROL) {
155         GetKeyNameTextW(MAKELPARAM(0, MapVirtualKeyW(VK_CONTROL, 0)),
156                                   KeyName, 64);
157         NameLen = lstrlenW(KeyName);
158         memcpy(&KeyName[NameLen], HOTKEY_plussep, sizeof(HOTKEY_plussep));
159         NameLen += 3;
160     }
161     if(Modifier & HOTKEYF_SHIFT) {
162         GetKeyNameTextW(MAKELPARAM(0, MapVirtualKeyW(VK_SHIFT, 0)),
163                                    &KeyName[NameLen], 64 - NameLen);
164         NameLen = lstrlenW(KeyName);
165         memcpy(&KeyName[NameLen], HOTKEY_plussep, sizeof(HOTKEY_plussep));
166         NameLen += 3;
167     }
168     if(Modifier & HOTKEYF_ALT) {
169         GetKeyNameTextW(MAKELPARAM(0, MapVirtualKeyW(VK_MENU, 0)),
170                                    &KeyName[NameLen], 64 - NameLen);
171         NameLen = lstrlenW(KeyName);
172         memcpy(&KeyName[NameLen], HOTKEY_plussep, sizeof(HOTKEY_plussep));
173         NameLen += 3;
174     }
175
176     if(infoPtr->HotKey) {
177         GetKeyNameTextW(infoPtr->ScanCode, &KeyName[NameLen], 64 - NameLen);
178         NameLen = lstrlenW(KeyName);
179     }
180     else
181         KeyName[NameLen] = 0;
182
183     HOTKEY_DrawHotKey (infoPtr, hdc, KeyName, NameLen);
184 }
185
186 static void
187 HOTKEY_Paint(HOTKEY_INFO *infoPtr, HDC hdc)
188 {
189     if (hdc)
190         HOTKEY_Refresh(infoPtr, hdc);
191     else {
192         PAINTSTRUCT ps;
193         hdc = BeginPaint (infoPtr->hwndSelf, &ps);
194         HOTKEY_Refresh (infoPtr, hdc);
195         EndPaint (infoPtr->hwndSelf, &ps);
196     }
197 }
198
199 static LRESULT
200 HOTKEY_GetHotKey(const HOTKEY_INFO *infoPtr)
201 {
202     TRACE("(infoPtr=%p) Modifiers: 0x%x, Virtual Key: %d\n", infoPtr, 
203           HIBYTE(infoPtr->HotKey), LOBYTE(infoPtr->HotKey));
204     return (LRESULT)infoPtr->HotKey;
205 }
206
207 static void
208 HOTKEY_SetHotKey(HOTKEY_INFO *infoPtr, WORD hotKey)
209 {
210     infoPtr->HotKey = hotKey;
211     infoPtr->ScanCode = 
212         MAKELPARAM(0, MapVirtualKeyW(LOBYTE(infoPtr->HotKey), 0));
213     TRACE("(infoPtr=%p hotKey=%x) Modifiers: 0x%x, Virtual Key: %d\n", infoPtr,
214           hotKey, HIBYTE(infoPtr->HotKey), LOBYTE(infoPtr->HotKey));
215     InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
216 }
217
218 static void 
219 HOTKEY_SetRules(HOTKEY_INFO *infoPtr, WORD invComb, WORD invMod)
220 {
221     infoPtr->InvComb = invComb;
222     infoPtr->InvMod = invMod;
223     TRACE("(infoPtr=%p) Invalid Modifers: 0x%x, If Invalid: 0x%x\n", infoPtr,
224           infoPtr->InvComb, infoPtr->InvMod);
225 }
226
227
228 static LRESULT
229 HOTKEY_Create (HOTKEY_INFO *infoPtr, const CREATESTRUCTW *lpcs)
230 {
231     infoPtr->hwndNotify = lpcs->hwndParent;
232
233     HOTKEY_SetFont(infoPtr, GetStockObject(SYSTEM_FONT), 0);
234
235     return 0;
236 }
237
238
239 static LRESULT
240 HOTKEY_Destroy (HOTKEY_INFO *infoPtr)
241 {
242     HWND hwnd = infoPtr->hwndSelf;
243     /* free hotkey info data */
244     Free (infoPtr);
245     SetWindowLongPtrW (hwnd, 0, 0);
246     return 0;
247 }
248
249
250 static LRESULT
251 HOTKEY_EraseBackground (const HOTKEY_INFO *infoPtr, HDC hdc)
252 {
253     HBRUSH hBrush, hSolidBrush = NULL;
254     RECT   rc;
255
256     if (GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_DISABLED)
257         hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrBtnFace);
258     else
259     {
260         hBrush = (HBRUSH)SendMessageW(infoPtr->hwndNotify, WM_CTLCOLOREDIT,
261                                       (WPARAM)hdc, (LPARAM)infoPtr->hwndSelf);
262         if (!hBrush)
263             hBrush = hSolidBrush = CreateSolidBrush(comctl32_color.clrWindow);
264     }
265
266     GetClientRect (infoPtr->hwndSelf, &rc);
267
268     FillRect (hdc, &rc, hBrush);
269
270     if (hSolidBrush)
271         DeleteObject(hSolidBrush);
272
273     return -1;
274 }
275
276
277 static inline LRESULT
278 HOTKEY_GetFont (const HOTKEY_INFO *infoPtr)
279 {
280     return (LRESULT)infoPtr->hFont;
281 }
282
283 static LRESULT
284 HOTKEY_KeyDown (HOTKEY_INFO *infoPtr, DWORD key, DWORD flags)
285 {
286     WORD wOldHotKey;
287     BYTE bOldMod;
288
289     if (GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_DISABLED)
290         return 0;
291
292     TRACE("() Key: %d\n", key);
293
294     wOldHotKey = infoPtr->HotKey;
295     bOldMod = infoPtr->CurrMod;
296
297     /* If any key is Pressed, we have to reset the hotkey in the control */
298     infoPtr->HotKey = 0;
299
300     switch (key)
301     {
302         case VK_RETURN:
303         case VK_TAB:
304         case VK_SPACE:
305         case VK_DELETE:
306         case VK_ESCAPE:
307         case VK_BACK:
308             InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
309             return DefWindowProcW (infoPtr->hwndSelf, WM_KEYDOWN, key, flags);
310
311         case VK_SHIFT:
312             infoPtr->CurrMod |= HOTKEYF_SHIFT;
313             break;
314         case VK_CONTROL:
315             infoPtr->CurrMod |= HOTKEYF_CONTROL;
316             break;
317         case VK_MENU:
318             infoPtr->CurrMod |= HOTKEYF_ALT;
319             break;
320
321         default:
322             if(HOTKEY_IsCombInv(infoPtr))
323                 infoPtr->HotKey = MAKEWORD(key, infoPtr->InvMod);
324             else
325                 infoPtr->HotKey = MAKEWORD(key, infoPtr->CurrMod);
326             infoPtr->ScanCode = flags;
327             break;
328     }
329
330     if ((wOldHotKey != infoPtr->HotKey) || (bOldMod != infoPtr->CurrMod))
331     {
332         InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
333
334         /* send EN_CHANGE notification */
335         SendMessageW(infoPtr->hwndNotify, WM_COMMAND,
336             MAKEWPARAM(GetDlgCtrlID(infoPtr->hwndSelf), EN_CHANGE),
337             (LPARAM)infoPtr->hwndSelf);
338     }
339
340     return 0;
341 }
342
343
344 static LRESULT
345 HOTKEY_KeyUp (HOTKEY_INFO *infoPtr, DWORD key)
346 {
347     BYTE bOldMod;
348
349     if (GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_DISABLED)
350         return 0;
351
352     TRACE("() Key: %d\n", key);
353
354     bOldMod = infoPtr->CurrMod;
355
356     switch (key)
357     {
358         case VK_SHIFT:
359             infoPtr->CurrMod &= ~HOTKEYF_SHIFT;
360             break;
361         case VK_CONTROL:
362             infoPtr->CurrMod &= ~HOTKEYF_CONTROL;
363             break;
364         case VK_MENU:
365             infoPtr->CurrMod &= ~HOTKEYF_ALT;
366             break;
367         default:
368             return 1;
369     }
370
371     if (bOldMod != infoPtr->CurrMod)
372     {
373         InvalidateRect(infoPtr->hwndSelf, NULL, TRUE);
374
375         /* send EN_CHANGE notification */
376         SendMessageW(infoPtr->hwndNotify, WM_COMMAND,
377             MAKEWPARAM(GetDlgCtrlID(infoPtr->hwndSelf), EN_CHANGE),
378             (LPARAM)infoPtr->hwndSelf);
379     }
380
381     return 0;
382 }
383
384
385 static LRESULT
386 HOTKEY_KillFocus (HOTKEY_INFO *infoPtr)
387 {
388     infoPtr->bFocus = FALSE;
389     DestroyCaret ();
390
391     return 0;
392 }
393
394
395 static LRESULT
396 HOTKEY_LButtonDown (const HOTKEY_INFO *infoPtr)
397 {
398     if (!(GetWindowLongW(infoPtr->hwndSelf, GWL_STYLE) & WS_DISABLED))
399         SetFocus (infoPtr->hwndSelf);
400
401     return 0;
402 }
403
404
405 static inline LRESULT
406 HOTKEY_NCCreate (HWND hwnd, const CREATESTRUCTW *lpcs)
407 {
408     HOTKEY_INFO *infoPtr;
409     DWORD dwExStyle = GetWindowLongW (hwnd, GWL_EXSTYLE);
410     SetWindowLongW (hwnd, GWL_EXSTYLE, 
411                     dwExStyle | WS_EX_CLIENTEDGE);
412
413     /* allocate memory for info structure */
414     infoPtr = Alloc (sizeof(HOTKEY_INFO));
415     SetWindowLongPtrW(hwnd, 0, (DWORD_PTR)infoPtr);
416
417     /* initialize info structure */
418     infoPtr->HotKey = infoPtr->InvComb = infoPtr->InvMod = infoPtr->CurrMod = 0;
419     infoPtr->CaretPos = GetSystemMetrics(SM_CXBORDER);
420     infoPtr->hwndSelf = hwnd;
421     LoadStringW(COMCTL32_hModule, HKY_NONE, infoPtr->strNone, 15);
422
423     return DefWindowProcW (infoPtr->hwndSelf, WM_NCCREATE, 0, (LPARAM)lpcs);
424 }
425
426 static LRESULT
427 HOTKEY_SetFocus (HOTKEY_INFO *infoPtr)
428 {
429     infoPtr->bFocus = TRUE;
430
431     CreateCaret (infoPtr->hwndSelf, NULL, 1, infoPtr->nHeight);
432     SetCaretPos (infoPtr->CaretPos, GetSystemMetrics(SM_CYBORDER));
433     ShowCaret (infoPtr->hwndSelf);
434
435     return 0;
436 }
437
438
439 static LRESULT
440 HOTKEY_SetFont (HOTKEY_INFO *infoPtr, HFONT hFont, BOOL redraw)
441 {
442     TEXTMETRICW tm;
443     HDC hdc;
444     HFONT hOldFont = 0;
445
446     infoPtr->hFont = hFont;
447
448     hdc = GetDC (infoPtr->hwndSelf);
449     if (infoPtr->hFont)
450         hOldFont = SelectObject (hdc, infoPtr->hFont);
451
452     GetTextMetricsW (hdc, &tm);
453     infoPtr->nHeight = tm.tmHeight;
454
455     if (infoPtr->hFont)
456         SelectObject (hdc, hOldFont);
457     ReleaseDC (infoPtr->hwndSelf, hdc);
458
459     if (redraw)
460         InvalidateRect (infoPtr->hwndSelf, NULL, TRUE);
461
462     return 0;
463 }
464
465 static LRESULT WINAPI
466 HOTKEY_WindowProc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
467 {
468     HOTKEY_INFO *infoPtr = (HOTKEY_INFO *)GetWindowLongPtrW (hwnd, 0);
469     TRACE("hwnd=%p msg=%x wparam=%lx lparam=%lx\n", hwnd, uMsg, wParam, lParam);
470     if (!infoPtr && (uMsg != WM_NCCREATE))
471         return DefWindowProcW (hwnd, uMsg, wParam, lParam);
472     switch (uMsg)
473     {
474         case HKM_GETHOTKEY:
475             return HOTKEY_GetHotKey (infoPtr);
476         case HKM_SETHOTKEY:
477             HOTKEY_SetHotKey (infoPtr, (WORD)wParam);
478             break;
479         case HKM_SETRULES:
480             HOTKEY_SetRules (infoPtr, (WORD)wParam, (WORD)lParam);
481             break;
482
483         case WM_CHAR:
484         case WM_SYSCHAR:
485             return HOTKEY_KeyDown (infoPtr, MapVirtualKeyW(LOBYTE(HIWORD(lParam)), 1), lParam);
486
487         case WM_CREATE:
488             return HOTKEY_Create (infoPtr, (LPCREATESTRUCTW)lParam);
489
490         case WM_DESTROY:
491             return HOTKEY_Destroy (infoPtr);
492
493         case WM_ERASEBKGND:
494             return HOTKEY_EraseBackground (infoPtr, (HDC)wParam);
495
496         case WM_GETDLGCODE:
497             return DLGC_WANTCHARS | DLGC_WANTARROWS;
498
499         case WM_GETFONT:
500             return HOTKEY_GetFont (infoPtr);
501
502         case WM_KEYDOWN:
503         case WM_SYSKEYDOWN:
504             return HOTKEY_KeyDown (infoPtr, wParam, lParam);
505
506         case WM_KEYUP:
507         case WM_SYSKEYUP:
508             return HOTKEY_KeyUp (infoPtr, wParam);
509
510         case WM_KILLFOCUS:
511             return HOTKEY_KillFocus (infoPtr);
512
513         case WM_LBUTTONDOWN:
514             return HOTKEY_LButtonDown (infoPtr);
515
516         case WM_NCCREATE:
517             return HOTKEY_NCCreate (hwnd, (LPCREATESTRUCTW)lParam);
518
519         case WM_PRINTCLIENT:
520         case WM_PAINT:
521             HOTKEY_Paint(infoPtr, (HDC)wParam);
522             return 0;
523
524         case WM_SETFOCUS:
525             return HOTKEY_SetFocus (infoPtr);
526
527         case WM_SETFONT:
528             return HOTKEY_SetFont (infoPtr, (HFONT)wParam, LOWORD(lParam));
529
530         default:
531             if ((uMsg >= WM_USER) && (uMsg < WM_APP) && !COMCTL32_IsReflectedMessage(uMsg))
532                 ERR("unknown msg %04x wp=%08lx lp=%08lx\n",
533                      uMsg, wParam, lParam);
534             return DefWindowProcW (hwnd, uMsg, wParam, lParam);
535     }
536     return 0;
537 }
538
539
540 void
541 HOTKEY_Register (void)
542 {
543     WNDCLASSW wndClass;
544
545     ZeroMemory (&wndClass, sizeof(WNDCLASSW));
546     wndClass.style         = CS_GLOBALCLASS;
547     wndClass.lpfnWndProc   = HOTKEY_WindowProc;
548     wndClass.cbClsExtra    = 0;
549     wndClass.cbWndExtra    = sizeof(HOTKEY_INFO *);
550     wndClass.hCursor       = 0;
551     wndClass.hbrBackground = 0;
552     wndClass.lpszClassName = HOTKEY_CLASSW;
553
554     RegisterClassW (&wndClass);
555 }
556
557
558 void
559 HOTKEY_Unregister (void)
560 {
561     UnregisterClassW (HOTKEY_CLASSW, NULL);
562 }