2009-11-10 Yaar Schnitman <yaar@chromium.org>
[WebKit-https.git] / WebKit / chromium / src / win / WebInputEventFactory.cpp
1 /*
2  * Copyright (C) 2006-2009 Google 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 are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "WebInputEventFactory.h"
33
34 #include "WebInputEvent.h"
35
36 #include <wtf/Assertions.h>
37
38 namespace WebKit {
39
40 static const unsigned long defaultScrollLinesPerWheelDelta = 3;
41 static const unsigned long defaultScrollCharsPerWheelDelta = 1;
42
43 // WebKeyboardEvent -----------------------------------------------------------
44
45 static bool isKeyPad(WPARAM wparam, LPARAM lparam)
46 {
47     bool keypad = false;
48     switch (wparam) {
49     case VK_RETURN:
50         keypad = (lparam >> 16) & KF_EXTENDED;
51         break;
52     case VK_INSERT:
53     case VK_DELETE:
54     case VK_HOME:
55     case VK_END:
56     case VK_PRIOR:
57     case VK_NEXT:
58     case VK_UP:
59     case VK_DOWN:
60     case VK_LEFT:
61     case VK_RIGHT:
62         keypad = !((lparam >> 16) & KF_EXTENDED);
63         break;
64     case VK_NUMLOCK:
65     case VK_NUMPAD0:
66     case VK_NUMPAD1:
67     case VK_NUMPAD2:
68     case VK_NUMPAD3:
69     case VK_NUMPAD4:
70     case VK_NUMPAD5:
71     case VK_NUMPAD6:
72     case VK_NUMPAD7:
73     case VK_NUMPAD8:
74     case VK_NUMPAD9:
75     case VK_DIVIDE:
76     case VK_MULTIPLY:
77     case VK_SUBTRACT:
78     case VK_ADD:
79     case VK_DECIMAL:
80     case VK_CLEAR:
81         keypad = true;
82         break;
83     default:
84         keypad = false;
85     }
86     return keypad;
87 }
88
89 WebKeyboardEvent WebInputEventFactory::keyboardEvent(HWND hwnd, UINT message,
90                                                      WPARAM wparam, LPARAM lparam)
91 {
92     WebKeyboardEvent result;
93
94     // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
95     // GetMessageTime() refers to is the same one that we're passed in? Perhaps
96     // one of the construction parameters should be the time passed by the
97     // caller, who would know for sure.
98     result.timeStampSeconds = GetMessageTime() / 1000.0;
99
100     result.windowsKeyCode = result.nativeKeyCode = static_cast<int>(wparam);
101
102     switch (message) {
103     case WM_SYSKEYDOWN:
104         result.isSystemKey = true;
105     case WM_KEYDOWN:
106         result.type = WebInputEvent::RawKeyDown;
107         break;
108     case WM_SYSKEYUP:
109         result.isSystemKey = true;
110     case WM_KEYUP:
111         result.type = WebInputEvent::KeyUp;
112         break;
113     case WM_IME_CHAR:
114         result.type = WebInputEvent::Char;
115         break;
116     case WM_SYSCHAR:
117         result.isSystemKey = true;
118         result.type = WebInputEvent::Char;
119     case WM_CHAR:
120         result.type = WebInputEvent::Char;
121         break;
122     default:
123         ASSERT_NOT_REACHED();
124     }
125
126     if (result.type == WebInputEvent::Char || result.type == WebInputEvent::RawKeyDown) {
127         result.text[0] = result.windowsKeyCode;
128         result.unmodifiedText[0] = result.windowsKeyCode;
129     }
130     if (result.type != WebInputEvent::Char)
131         result.setKeyIdentifierFromWindowsKeyCode();
132
133     if (GetKeyState(VK_SHIFT) & 0x8000)
134         result.modifiers |= WebInputEvent::ShiftKey;
135     if (GetKeyState(VK_CONTROL) & 0x8000)
136         result.modifiers |= WebInputEvent::ControlKey;
137     if (GetKeyState(VK_MENU) & 0x8000)
138         result.modifiers |= WebInputEvent::AltKey;
139     // NOTE: There doesn't seem to be a way to query the mouse button state in
140     // this case.
141
142     if (LOWORD(lparam) > 1)
143         result.modifiers |= WebInputEvent::IsAutoRepeat;
144     if (isKeyPad(wparam, lparam))
145         result.modifiers |= WebInputEvent::IsKeyPad;
146
147     return result;
148 }
149
150 // WebMouseEvent --------------------------------------------------------------
151
152 static int gLastClickCount;
153 static double gLastClickTime;
154
155 static LPARAM GetRelativeCursorPos(HWND hwnd)
156 {
157     POINT pos = {-1, -1};
158     GetCursorPos(&pos);
159     ScreenToClient(hwnd, &pos);
160     return MAKELPARAM(pos.x, pos.y);
161 }
162
163 void WebInputEventFactory::resetLastClickState()
164 {
165     gLastClickTime = gLastClickCount = 0;
166 }
167
168 WebMouseEvent WebInputEventFactory::mouseEvent(HWND hwnd, UINT message,
169                                                WPARAM wparam, LPARAM lparam)
170 {
171     WebMouseEvent result; //(WebInputEvent::Uninitialized());
172
173     switch (message) {
174     case WM_MOUSEMOVE:
175         result.type = WebInputEvent::MouseMove;
176         if (wparam & MK_LBUTTON)
177             result.button = WebMouseEvent::ButtonLeft;
178         else if (wparam & MK_MBUTTON)
179             result.button = WebMouseEvent::ButtonMiddle;
180         else if (wparam & MK_RBUTTON)
181             result.button = WebMouseEvent::ButtonRight;
182         else
183             result.button = WebMouseEvent::ButtonNone;
184         break;
185     case WM_MOUSELEAVE:
186         result.type = WebInputEvent::MouseLeave;
187         result.button = WebMouseEvent::ButtonNone;
188         // set the current mouse position (relative to the client area of the
189         // current window) since none is specified for this event
190         lparam = GetRelativeCursorPos(hwnd);
191         break;
192     case WM_LBUTTONDOWN:
193     case WM_LBUTTONDBLCLK:
194         result.type = WebInputEvent::MouseDown;
195         result.button = WebMouseEvent::ButtonLeft;
196         break;
197     case WM_MBUTTONDOWN:
198     case WM_MBUTTONDBLCLK:
199         result.type = WebInputEvent::MouseDown;
200         result.button = WebMouseEvent::ButtonMiddle;
201         break;
202     case WM_RBUTTONDOWN:
203     case WM_RBUTTONDBLCLK:
204         result.type = WebInputEvent::MouseDown;
205         result.button = WebMouseEvent::ButtonRight;
206         break;
207     case WM_LBUTTONUP:
208         result.type = WebInputEvent::MouseUp;
209         result.button = WebMouseEvent::ButtonLeft;
210         break;
211     case WM_MBUTTONUP:
212         result.type = WebInputEvent::MouseUp;
213         result.button = WebMouseEvent::ButtonMiddle;
214         break;
215     case WM_RBUTTONUP:
216         result.type = WebInputEvent::MouseUp;
217         result.button = WebMouseEvent::ButtonRight;
218         break;
219     default:
220         ASSERT_NOT_REACHED();
221     }
222
223     // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
224     // GetMessageTime() refers to is the same one that we're passed in? Perhaps
225     // one of the construction parameters should be the time passed by the
226     // caller, who would know for sure.
227     result.timeStampSeconds = GetMessageTime() / 1000.0;
228
229     // set position fields:
230
231     result.x = static_cast<short>(LOWORD(lparam));
232     result.y = static_cast<short>(HIWORD(lparam));
233     result.windowX = result.x;
234     result.windowY = result.y;
235
236     POINT globalPoint = { result.x, result.y };
237     ClientToScreen(hwnd, &globalPoint);
238
239     result.globalX = globalPoint.x;
240     result.globalY = globalPoint.y;
241
242     // calculate number of clicks:
243
244     // This differs slightly from the WebKit code in WebKit/win/WebView.cpp
245     // where their original code looks buggy.
246     static int lastClickPositionX;
247     static int lastClickPositionY;
248     static WebMouseEvent::Button lastClickButton = WebMouseEvent::ButtonLeft;
249
250     double currentTime = result.timeStampSeconds;
251     bool cancelPreviousClick =
252         (abs(lastClickPositionX - result.x) > (GetSystemMetrics(SM_CXDOUBLECLK) / 2))
253         || (abs(lastClickPositionY - result.y) > (GetSystemMetrics(SM_CYDOUBLECLK) / 2))
254         || ((currentTime - gLastClickTime) * 1000.0 > GetDoubleClickTime());
255
256     if (result.type == WebInputEvent::MouseDown) {
257         if (!cancelPreviousClick && (result.button == lastClickButton))
258             ++gLastClickCount;
259         else {
260             gLastClickCount = 1;
261             lastClickPositionX = result.x;
262             lastClickPositionY = result.y;
263         }
264         gLastClickTime = currentTime;
265         lastClickButton = result.button;
266     } else if (result.type == WebInputEvent::MouseMove
267                || result.type == WebInputEvent::MouseLeave) {
268         if (cancelPreviousClick) {
269             gLastClickCount = 0;
270             lastClickPositionX = 0;
271             lastClickPositionY = 0;
272             gLastClickTime = 0;
273         }
274     }
275     result.clickCount = gLastClickCount;
276
277     // set modifiers:
278
279     if (wparam & MK_CONTROL)
280         result.modifiers |= WebInputEvent::ControlKey;
281     if (wparam & MK_SHIFT)
282         result.modifiers |= WebInputEvent::ShiftKey;
283     if (GetKeyState(VK_MENU) & 0x8000)
284         result.modifiers |= WebInputEvent::AltKey;
285     if (wparam & MK_LBUTTON)
286         result.modifiers |= WebInputEvent::LeftButtonDown;
287     if (wparam & MK_MBUTTON)
288         result.modifiers |= WebInputEvent::MiddleButtonDown;
289     if (wparam & MK_RBUTTON)
290         result.modifiers |= WebInputEvent::RightButtonDown;
291
292     return result;
293 }
294
295 // WebMouseWheelEvent ---------------------------------------------------------
296
297 WebMouseWheelEvent WebInputEventFactory::mouseWheelEvent(HWND hwnd, UINT message,
298                                                          WPARAM wparam, LPARAM lparam)
299 {
300     WebMouseWheelEvent result; //(WebInputEvent::Uninitialized());
301
302     result.type = WebInputEvent::MouseWheel;
303
304     // TODO(pkasting): http://b/1117926 Are we guaranteed that the message that
305     // GetMessageTime() refers to is the same one that we're passed in? Perhaps
306     // one of the construction parameters should be the time passed by the
307     // caller, who would know for sure.
308     result.timeStampSeconds = GetMessageTime() / 1000.0;
309
310     result.button = WebMouseEvent::ButtonNone;
311
312     // Get key state, coordinates, and wheel delta from event.
313     typedef SHORT (WINAPI *GetKeyStateFunction)(int key);
314     GetKeyStateFunction getKeyState;
315     UINT keyState;
316     float wheelDelta;
317     bool horizontalScroll = false;
318     if ((message == WM_VSCROLL) || (message == WM_HSCROLL)) {
319         // Synthesize mousewheel event from a scroll event.  This is needed to
320         // simulate middle mouse scrolling in some laptops.  Use GetAsyncKeyState
321         // for key state since we are synthesizing the input event.
322         getKeyState = GetAsyncKeyState;
323         keyState = 0;
324         if (getKeyState(VK_SHIFT))
325             keyState |= MK_SHIFT;
326         if (getKeyState(VK_CONTROL))
327             keyState |= MK_CONTROL;
328         // NOTE: There doesn't seem to be a way to query the mouse button state
329         // in this case.
330
331         POINT cursorPosition = {0};
332         GetCursorPos(&cursorPosition);
333         result.globalX = cursorPosition.x;
334         result.globalY = cursorPosition.y;
335
336         switch (LOWORD(wparam)) {
337         case SB_LINEUP:    // == SB_LINELEFT
338             wheelDelta = WHEEL_DELTA;
339             break;
340         case SB_LINEDOWN:  // == SB_LINERIGHT
341             wheelDelta = -WHEEL_DELTA;
342             break;
343         case SB_PAGEUP:
344             wheelDelta = 1;
345             result.scrollByPage = true;
346             break;
347         case SB_PAGEDOWN:
348             wheelDelta = -1;
349             result.scrollByPage = true;
350             break;
351         default:  // We don't supoprt SB_THUMBPOSITION or SB_THUMBTRACK here.
352             wheelDelta = 0;
353             break;
354         }
355
356         if (message == WM_HSCROLL)
357             horizontalScroll = true;
358     } else {
359         // Non-synthesized event; we can just read data off the event.
360         getKeyState = GetKeyState;
361         keyState = GET_KEYSTATE_WPARAM(wparam);
362
363         result.globalX = static_cast<short>(LOWORD(lparam));
364         result.globalY = static_cast<short>(HIWORD(lparam));
365
366         wheelDelta = static_cast<float>(GET_WHEEL_DELTA_WPARAM(wparam));
367         if (message == WM_MOUSEHWHEEL) {
368             horizontalScroll = true;
369             wheelDelta = -wheelDelta;  // Windows is <- -/+ ->, WebKit <- +/- ->.
370         }
371     }
372     if (keyState & MK_SHIFT)
373         horizontalScroll = true;
374
375     // Set modifiers based on key state.
376     if (keyState & MK_SHIFT)
377         result.modifiers |= WebInputEvent::ShiftKey;
378     if (keyState & MK_CONTROL)
379         result.modifiers |= WebInputEvent::ControlKey;
380     if (getKeyState(VK_MENU) & 0x8000)
381         result.modifiers |= WebInputEvent::AltKey;
382     if (keyState & MK_LBUTTON)
383         result.modifiers |= WebInputEvent::LeftButtonDown;
384     if (keyState & MK_MBUTTON)
385         result.modifiers |= WebInputEvent::MiddleButtonDown;
386     if (keyState & MK_RBUTTON)
387         result.modifiers |= WebInputEvent::RightButtonDown;
388
389     // Set coordinates by translating event coordinates from screen to client.
390     POINT clientPoint = { result.globalX, result.globalY };
391     MapWindowPoints(0, hwnd, &clientPoint, 1);
392     result.x = clientPoint.x;
393     result.y = clientPoint.y;
394     result.windowX = result.x;
395     result.windowY = result.y;
396
397     // Convert wheel delta amount to a number of pixels to scroll.
398     //
399     // How many pixels should we scroll per line?  Gecko uses the height of the
400     // current line, which means scroll distance changes as you go through the
401     // page or go to different pages.  IE 7 is ~50 px/line, although the value
402     // seems to vary slightly by page and zoom level.  Since IE 7 has a smoothing
403     // algorithm on scrolling, it can get away with slightly larger scroll values
404     // without feeling jerky.  Here we use 100 px per three lines (the default
405     // scroll amount is three lines per wheel tick).
406     static const float scrollbarPixelsPerLine = 100.0f / 3.0f;
407     wheelDelta /= WHEEL_DELTA;
408     float scrollDelta = wheelDelta;
409     if (horizontalScroll) {
410         unsigned long scrollChars = defaultScrollCharsPerWheelDelta;
411         SystemParametersInfo(SPI_GETWHEELSCROLLCHARS, 0, &scrollChars, 0);
412         // TODO(pkasting): Should probably have a different multiplier
413         // scrollbarPixelsPerChar here.
414         scrollDelta *= static_cast<float>(scrollChars) * scrollbarPixelsPerLine;
415     } else {
416         unsigned long scrollLines = defaultScrollLinesPerWheelDelta;
417         SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &scrollLines, 0);
418         if (scrollLines == WHEEL_PAGESCROLL)
419             result.scrollByPage = true;
420         if (!result.scrollByPage)
421             scrollDelta *= static_cast<float>(scrollLines) * scrollbarPixelsPerLine;
422     }
423
424     // Set scroll amount based on above calculations.  WebKit expects positive
425     // deltaY to mean "scroll up" and positive deltaX to mean "scroll left".
426     if (horizontalScroll) {
427         result.deltaX = scrollDelta;
428         result.wheelTicksX = wheelDelta;
429     } else {
430         result.deltaY = scrollDelta;
431         result.wheelTicksY = wheelDelta;
432     }
433
434     return result;
435 }
436
437 } // namespace WebKit